/* 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 "base/task.h"
#include "GeckoProfiler.h"
#include "gfxPlatform.h"
#include "GfxInfoBase.h"
#include "GLContext.h"
#include "RenderThread.h"
#include "nsThread.h"
#include "nsThreadUtils.h"
#include "transport/runnable_utils.h"
#include "mozilla/BackgroundHangMonitor.h"
#include "mozilla/Components.h"
#include "mozilla/layers/AsyncImagePipelineManager.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/glean/GfxMetrics.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/CompositorManagerParent.h"
#include "mozilla/layers/Fence.h"
#include "mozilla/layers/WebRenderBridgeParent.h"
#include "mozilla/layers/SharedSurfacesParent.h"
#include "mozilla/layers/SurfacePool.h"
#include "mozilla/layers/SynchronousTask.h"
#include "mozilla/PerfStats.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/webrender/RendererOGL.h"
#include "mozilla/webrender/RenderTextureHost.h"
#include "mozilla/widget/CompositorWidget.h"
#include "OGLShaderProgram.h"

#ifdef XP_WIN
#  include "GLContextEGL.h"
#  include "GLLibraryEGL.h"
#  include "mozilla/widget/WinCompositorWindowThread.h"
#  include "mozilla/gfx/DeviceManagerDx.h"
#  include "mozilla/webrender/DCLayerTree.h"
// #  include "nsWindowsHelpers.h"
// #  include <d3d11.h>
#endif

#ifdef MOZ_WIDGET_ANDROID
#  include "GLLibraryEGL.h"
#  include "mozilla/webrender/RenderAndroidSurfaceTextureHost.h"
#endif

#ifdef MOZ_WIDGET_GTK
#  include "mozilla/WidgetUtilsGtk.h"
#  include "GLLibraryEGL.h"
#endif

using namespace mozilla;

static already_AddRefed<gl::GLContext> CreateGLContext(nsACString& aError);

MOZ_DEFINE_MALLOC_SIZE_OF(WebRenderRendererMallocSizeOf)

namespace mozilla::wr {

LazyLogModule gRenderThreadLog("RenderThread");
// Should be called only on RenderThread, since LazyLogModule is not thread safe
#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__))

static StaticRefPtr<RenderThread> sRenderThread;
static mozilla::BackgroundHangMonitor* sBackgroundHangMonitor;
#ifdef DEBUG
static bool sRenderThreadEverStarted = false;
#endif
size_t RenderThread::sRendererCount = 0;
size_t RenderThread::sActiveRendererCount = 0;

#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK)
static bool USE_DEDICATED_GLYPH_RASTER_THREAD = true;
#else
static bool USE_DEDICATED_GLYPH_RASTER_THREAD = false;
#endif

RenderThread::RenderThread(RefPtr<nsIThread> aThread)
    : mThread(std::move(aThread)),
      mThreadPool(false),
      mThreadPoolLP(true),
      mChunkPool(wr_chunk_pool_new()),
      mGlyphRasterThread(USE_DEDICATED_GLYPH_RASTER_THREAD),
      mSingletonGLIsForHardwareWebRender(true),
      mBatteryInfo("RenderThread.mBatteryInfo"),
      mWindowInfos("RenderThread.mWindowInfos"),
      mRenderTextureMapLock("RenderThread.mRenderTextureMapLock"),
      mHasShutdown(false),
      mHandlingDeviceReset(false),
      mHandlingWebRenderError(false) {}

RenderThread::~RenderThread() {
  MOZ_ASSERT(mRenderTexturesDeferred.empty());
  wr_chunk_pool_delete(mChunkPool);
}

// static
RenderThread* RenderThread::Get() { return sRenderThread; }

// static
void RenderThread::Start(uint32_t aNamespace) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!sRenderThread);

#ifdef DEBUG
  // Check to ensure nobody will try to ever start us more than once during
  // the process' lifetime (in particular after ShutDown).
  MOZ_ASSERT(!sRenderThreadEverStarted);
  sRenderThreadEverStarted = true;
#endif

  // When the CanvasRenderer thread is disabled, WebGL may be handled on this
  // thread, requiring a bigger stack size. See: CanvasManagerParent::Init
  //
  // This is 4M, which is higher than the default 256K.
  // Increased with bug 1753349 to accommodate the `chromium/5359` branch of
  // ANGLE, which has large peak stack usage for some pathological shader
  // compilations.
  //
  // Previously increased to 512K to accommodate Mesa in bug 1753340.
  //
  // Previously increased to 320K to avoid a stack overflow in the
  // Intel Vulkan driver initialization in bug 1716120.
  //
  // Note: we only override it if it's limited already.
  uint32_t stackSize = nsIThreadManager::DEFAULT_STACK_SIZE;
  if (stackSize && !gfx::gfxVars::SupportsThreadsafeGL()) {
    stackSize = std::max(stackSize, 4096U << 10);
  }
#if !defined(__OPTIMIZE__)
  // swgl's draw_quad_spans will allocate ~1.5MB in no-opt builds
  // and the default thread stack size on macOS is 512KB
  stackSize = std::max(stackSize, 4 * 1024 * 1024U);
#endif

  RefPtr<nsIThread> thread;
  nsresult rv = NS_NewNamedThread(
      "Renderer", getter_AddRefs(thread),
      NS_NewRunnableFunction(
          "Renderer::BackgroundHanSetup",
          []() {
            sBackgroundHangMonitor = new mozilla::BackgroundHangMonitor(
                "Render",
                /* Timeout values are powers-of-two to enable us get better
                   data. 128ms is chosen for transient hangs because 8Hz should
                   be the minimally acceptable goal for Render
                   responsiveness (normal goal is 60Hz). */
                128,
                /* 2048ms is chosen for permanent hangs because it's longer than
                 * most Render hangs seen in the wild, but is short enough
                 * to not miss getting native hang stacks. */
                2048);
            nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
            nsThread* nsthread = static_cast<nsThread*>(thread.get());
            nsthread->SetUseHangMonitor(true);
            nsthread->SetPriority(nsISupportsPriority::PRIORITY_HIGH);
          }),
      {.stackSize = stackSize});

  if (NS_FAILED(rv)) {
    gfxCriticalNote << "Failed to create Renderer thread: "
                    << gfx::hexa((uint32_t)rv);
    return;
  }

  sRenderThread = new RenderThread(thread);
  CrashReporter::RegisterAnnotationUSize(
      CrashReporter::Annotation::GraphicsNumRenderers, &sRendererCount);
  CrashReporter::RegisterAnnotationUSize(
      CrashReporter::Annotation::GraphicsNumActiveRenderers,
      &sActiveRendererCount);
#ifdef XP_WIN
  widget::WinCompositorWindowThread::Start();
#endif
  layers::SharedSurfacesParent::Initialize();

  RefPtr<Runnable> runnable = WrapRunnable(
      RefPtr<RenderThread>(sRenderThread.get()), &RenderThread::InitDeviceTask);
  sRenderThread->PostRunnable(runnable.forget());
}

// static
void RenderThread::ShutDown() {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(sRenderThread);

  {
    MutexAutoLock lock(sRenderThread->mRenderTextureMapLock);
    sRenderThread->mHasShutdown = true;
  }

  RefPtr<Runnable> runnable = WrapRunnable(
      RefPtr<RenderThread>(sRenderThread.get()), &RenderThread::ShutDownTask);
  sRenderThread->PostRunnable(runnable.forget());

  // This will empty the thread queue and thus run the above runnable while
  // spinning the MT event loop.
  nsCOMPtr<nsIThread> oldThread = sRenderThread->GetRenderThread();
  oldThread->Shutdown();

  layers::SharedSurfacesParent::Shutdown();

#ifdef XP_WIN
  if (widget::WinCompositorWindowThread::Get()) {
    widget::WinCompositorWindowThread::ShutDown();
  }
#endif

  // We null this out only after we finished shutdown to give everbody the
  // chance to check for sRenderThread->mHasShutdown. Hopefully everybody
  // checks this before using us!
  sRenderThread = nullptr;
}

extern void ClearAllBlobImageResources();

void RenderThread::ShutDownTask() {
  MOZ_ASSERT(IsInRenderThread());
  LOG("RenderThread::ShutDownTask()");

  {
    // Clear RenderTextureHosts
    MutexAutoLock lock(mRenderTextureMapLock);
    mRenderTexturesDeferred.clear();
    mRenderTextures.clear();
    mSyncObjectNeededRenderTextures.clear();
    mRenderTextureOps.clear();
  }

  // These must be destroyed before the thread pools as they hold a reference to
  // the worker threads.
  mShaders = nullptr;
  mProgramCache = nullptr;

  // Destroy the thread pools, waiting for the worker threads to join in
  // leak-checking / ASAN / etc builds. In normal builds we don't care and just
  // want to shut down quickly.
#ifdef NS_FREE_PERMANENT_DATA
  const bool joinWorkers = true;
#else
  const bool joinWorkers = false;
#endif
  mThreadPool.Destroy(joinWorkers);
  mThreadPoolLP.Destroy(joinWorkers);

  // Releasing on the render thread will allow us to avoid dispatching to remove
  // remaining textures from the texture map.
  layers::SharedSurfacesParent::ShutdownRenderThread();

#ifdef XP_WIN
  DCLayerTree::Shutdown();
#endif

  ClearAllBlobImageResources();
  ClearSingletonGL();
  ClearSharedSurfacePool();
}

// static
bool RenderThread::IsInRenderThread() {
  return sRenderThread && sRenderThread->mThread == NS_GetCurrentThread();
}

// static
already_AddRefed<nsIThread> RenderThread::GetRenderThread() {
  nsCOMPtr<nsIThread> thread;
  if (sRenderThread) {
    thread = sRenderThread->mThread;
  }
  return thread.forget();
}

void RenderThread::DoAccumulateMemoryReport(
    MemoryReport aReport,
    const RefPtr<MemoryReportPromise::Private>& aPromise) {
  MOZ_ASSERT(IsInRenderThread());

  for (auto& r : mRenderers) {
    r.second->AccumulateMemoryReport(&aReport);
  }

  // Note memory used by the shader cache, which is shared across all WR
  // instances.
  MOZ_ASSERT(aReport.shader_cache == 0);
  if (mProgramCache) {
    aReport.shader_cache = wr_program_cache_report_memory(
        mProgramCache->Raw(), &WebRenderRendererMallocSizeOf);
  }

  size_t renderTextureMemory = 0;
  {
    MutexAutoLock lock(mRenderTextureMapLock);
    for (const auto& entry : mRenderTextures) {
      renderTextureMemory += entry.second->Bytes();
    }
  }
  aReport.render_texture_hosts = renderTextureMemory;

  aPromise->Resolve(aReport, __func__);
}

// static
RefPtr<MemoryReportPromise> RenderThread::AccumulateMemoryReport(
    MemoryReport aInitial) {
  RefPtr<MemoryReportPromise::Private> p =
      new MemoryReportPromise::Private(__func__);
  MOZ_ASSERT(!IsInRenderThread());
  if (!Get()) {
    // This happens when the GPU process fails to start and we fall back to the
    // basic compositor in the parent process. We could assert against this if
    // we made the webrender detection code in gfxPlatform.cpp smarter. See bug
    // 1494430 comment 12.
    NS_WARNING("No render thread, returning empty memory report");
    p->Resolve(aInitial, __func__);
    return p;
  }

  Get()->PostRunnable(
      NewRunnableMethod<MemoryReport, RefPtr<MemoryReportPromise::Private>>(
          "wr::RenderThread::DoAccumulateMemoryReport", Get(),
          &RenderThread::DoAccumulateMemoryReport, aInitial, p));

  return p;
}

void RenderThread::SetBatteryInfo(const hal::BatteryInformation& aBatteryInfo) {
  MOZ_ASSERT(XRE_IsGPUProcess());

  auto batteryInfo = mBatteryInfo.Lock();
  batteryInfo.ref() = Some(aBatteryInfo);
}

bool RenderThread::GetPowerIsCharging() {
  MOZ_ASSERT(XRE_IsGPUProcess());

  auto batteryInfo = mBatteryInfo.Lock();
  if (batteryInfo.ref().isSome()) {
    return batteryInfo.ref().ref().charging();
  }

  gfxCriticalNoteOnce << "BatteryInfo is not set";
  MOZ_ASSERT_UNREACHABLE("unexpected to be called");
  return false;
}

void RenderThread::AddRenderer(wr::WindowId aWindowId,
                               UniquePtr<RendererOGL> aRenderer) {
  MOZ_ASSERT(IsInRenderThread());
  LOG("RenderThread::AddRenderer() aWindowId %" PRIx64 "", AsUint64(aWindowId));

  if (mHasShutdown) {
    return;
  }

  mRenderers[aWindowId] = std::move(aRenderer);
  sRendererCount = mRenderers.size();

  auto windows = mWindowInfos.Lock();
  windows->emplace(AsUint64(aWindowId), new WindowInfo());
  mWrNotifierEventsQueues.emplace(AsUint64(aWindowId),
                                  new std::queue<WrNotifierEvent>);
}

void RenderThread::RemoveRenderer(wr::WindowId aWindowId) {
  MOZ_ASSERT(IsInRenderThread());
  LOG("RenderThread::RemoveRenderer() aWindowId %" PRIx64 "",
      AsUint64(aWindowId));

  if (mHasShutdown) {
    return;
  }

  mRenderers.erase(aWindowId);
  sRendererCount = mRenderers.size();

  if (mRenderers.empty()) {
    if (mHandlingDeviceReset) {
      ClearSingletonGL();
    }
    mHandlingDeviceReset = false;
    mHandlingWebRenderError = false;
  }

  auto windows = mWindowInfos.Lock();
  auto it = windows->find(AsUint64(aWindowId));
  MOZ_ASSERT(it != windows->end());
  windows->erase(it);

  // Defer std::deque<WrNotifierEvent> remove, RemoveRenderer() is called in
  // HandleWrNotifierEvents().
  RefPtr<Runnable> runnable =
      NS_NewRunnableFunction("RenderThread::RemoveRenderer", [aWindowId]() {
        auto* self = RenderThread::Get();
        auto it = self->mWrNotifierEventsQueues.find(AsUint64(aWindowId));
        if (it == self->mWrNotifierEventsQueues.end()) {
          return;
        }
        self->mWrNotifierEventsQueues.erase(it);
      });
  RenderThread::Get()->PostRunnable(runnable.forget());
}

RendererOGL* RenderThread::GetRenderer(wr::WindowId aWindowId) {
  MOZ_ASSERT(IsInRenderThread());

  auto it = mRenderers.find(aWindowId);
  MOZ_ASSERT(it != mRenderers.end());

  if (it == mRenderers.end()) {
    return nullptr;
  }

  return it->second.get();
}

size_t RenderThread::RendererCount() const {
  MOZ_ASSERT(IsInRenderThread());
  return mRenderers.size();
}

void RenderThread::UpdateActiveRendererCount() {
  MOZ_ASSERT(IsInRenderThread());
  size_t num_active = 0;
  for (const auto& it : mRenderers) {
    if (!it.second->IsPaused()) {
      num_active++;
    }
  }
  sActiveRendererCount = num_active;
}

void RenderThread::WrNotifierEvent_WakeUp(WrWindowId aWindowId,
                                          bool aCompositeNeeded) {
  auto windows = mWindowInfos.Lock();
  auto it = windows->find(AsUint64(aWindowId));
  if (it == windows->end()) {
    MOZ_ASSERT(false);
    return;
  }

  WindowInfo* info = it->second.get();

  info->mPendingWrNotifierEvents.emplace(
      WrNotifierEvent::WakeUp(aCompositeNeeded));
  PostWrNotifierEvents(aWindowId, info);
}

void RenderThread::WrNotifierEvent_NewFrameReady(
    WrWindowId aWindowId, wr::FramePublishId aPublishId,
    const wr::FrameReadyParams* aParams) {
  auto windows = mWindowInfos.Lock();
  auto it = windows->find(AsUint64(aWindowId));
  if (it == windows->end()) {
    MOZ_ASSERT(false);
    return;
  }
  WindowInfo* info = it->second.get();

  info->mPendingWrNotifierEvents.emplace(
      WrNotifierEvent::NewFrameReady(aPublishId, aParams));
  PostWrNotifierEvents(aWindowId, info);
}

void RenderThread::WrNotifierEvent_ExternalEvent(WrWindowId aWindowId,
                                                 size_t aRawEvent) {
  UniquePtr<RendererEvent> evt(reinterpret_cast<RendererEvent*>(aRawEvent));
  {
    auto windows = mWindowInfos.Lock();
    auto it = windows->find(AsUint64(aWindowId));
    if (it == windows->end()) {
      MOZ_ASSERT(false);
      return;
    }
    WindowInfo* info = it->second.get();

    info->mPendingWrNotifierEvents.emplace(
        WrNotifierEvent::ExternalEvent(std::move(evt)));
    PostWrNotifierEvents(aWindowId, info);
  }
}

void RenderThread::PostWrNotifierEvents(WrWindowId aWindowId) {
  {
    auto windows = mWindowInfos.Lock();
    auto it = windows->find(AsUint64(aWindowId));
    if (it == windows->end()) {
      MOZ_ASSERT(false);
      return;
    }
    WindowInfo* info = it->second.get();
    PostWrNotifierEvents(aWindowId, info);
  }
}

void RenderThread::PostWrNotifierEvents(WrWindowId aWindowId,
                                        WindowInfo* aInfo) {
  // Runnable has already been triggered.
  if (aInfo->mWrNotifierEventsRunnable) {
    return;
  }

  // Runnable has not been triggered yet.
  RefPtr<nsIRunnable> runnable = NewRunnableMethod<WrWindowId>(
      "RenderThread::HandleWrNotifierEvents", this,
      &RenderThread::HandleWrNotifierEvents, aWindowId);
  aInfo->mWrNotifierEventsRunnable = runnable;
  PostRunnable(runnable.forget());
}

void RenderThread::HandleWrNotifierEvents(WrWindowId aWindowId) {
  MOZ_ASSERT(IsInRenderThread());

  auto eventsIt = mWrNotifierEventsQueues.find(AsUint64(aWindowId));
  if (eventsIt == mWrNotifierEventsQueues.end()) {
    return;
  }
  auto* events = eventsIt->second.get();

  {
    auto windows = mWindowInfos.Lock();
    auto infoIt = windows->find(AsUint64(aWindowId));
    if (infoIt == windows->end()) {
      MOZ_ASSERT(false);
      return;
    }
    WindowInfo* info = infoIt->second.get();
    info->mWrNotifierEventsRunnable = nullptr;

    if (events->empty() && !info->mPendingWrNotifierEvents.empty()) {
      events->swap(info->mPendingWrNotifierEvents);
    }
  }

  bool handleNext = true;

  while (!events->empty() && handleNext) {
    auto& front = events->front();
    switch (front.mTag) {
      case WrNotifierEvent::Tag::WakeUp:
        WrNotifierEvent_HandleWakeUp(aWindowId, front.FrameReadyParams());
        handleNext = false;
        break;
      case WrNotifierEvent::Tag::NewFrameReady:
        WrNotifierEvent_HandleNewFrameReady(aWindowId, front.PublishId(),
                                            front.FrameReadyParams());
        handleNext = false;
        break;
      case WrNotifierEvent::Tag::ExternalEvent:
        WrNotifierEvent_HandleExternalEvent(aWindowId, front.ExternalEvent());
        break;
    }
    events->pop();
  }

  {
    auto windows = mWindowInfos.Lock();
    auto it = windows->find(AsUint64(aWindowId));
    if (it == windows->end()) {
      return;
    }
    WindowInfo* info = it->second.get();

    if (!events->empty() || !info->mPendingWrNotifierEvents.empty()) {
      PostWrNotifierEvents(aWindowId, info);
    }
  }
}

void RenderThread::WrNotifierEvent_HandleWakeUp(
    wr::WindowId aWindowId, const wr::FrameReadyParams& aParams) {
  MOZ_ASSERT(IsInRenderThread());
  MOZ_ASSERT(!aParams.tracked);
  HandleFrameOneDoc(aWindowId, aParams, Nothing());
}

void RenderThread::WrNotifierEvent_HandleNewFrameReady(
    wr::WindowId aWindowId, wr::FramePublishId aPublishId,
    const wr::FrameReadyParams& aParams) {
  MOZ_ASSERT(IsInRenderThread());

  HandleFrameOneDoc(aWindowId, aParams, Some(aPublishId));
}

void RenderThread::WrNotifierEvent_HandleExternalEvent(
    wr::WindowId aWindowId, UniquePtr<RendererEvent> aRendererEvent) {
  MOZ_ASSERT(IsInRenderThread());

  RunEvent(aWindowId, std::move(aRendererEvent), /* aViaWebRender */ true);
}

void RenderThread::BeginRecordingForWindow(wr::WindowId aWindowId,
                                           const TimeStamp& aRecordingStart,
                                           wr::PipelineId aRootPipelineId) {
  MOZ_ASSERT(IsInRenderThread());
  RendererOGL* renderer = GetRenderer(aWindowId);
  MOZ_ASSERT(renderer);

  renderer->BeginRecording(aRecordingStart, aRootPipelineId);
}

Maybe<layers::FrameRecording> RenderThread::EndRecordingForWindow(
    wr::WindowId aWindowId) {
  MOZ_ASSERT(IsInRenderThread());

  RendererOGL* renderer = GetRenderer(aWindowId);
  MOZ_ASSERT(renderer);
  return renderer->EndRecording();
}

void RenderThread::HandleFrameOneDoc(wr::WindowId aWindowId,
                                     const wr::FrameReadyParams& aParams,
                                     Maybe<FramePublishId> aPublishId) {
  MOZ_ASSERT(IsInRenderThread());

  if (mHasShutdown) {
    return;
  }

  HandleFrameOneDocInner(aWindowId, aParams, aPublishId);

  if (aParams.tracked) {
    DecPendingFrameCount(aWindowId);
  }
}

void RenderThread::HandleFrameOneDocInner(wr::WindowId aWindowId,
                                          const wr::FrameReadyParams& aParams,
                                          Maybe<FramePublishId> aPublishId) {
  if (IsDestroyed(aWindowId)) {
    return;
  }

  if (mHandlingDeviceReset) {
    return;
  }

  PendingFrameInfo frame;
  if (aParams.tracked) {
    // scope lock
    auto windows = mWindowInfos.Lock();
    auto it = windows->find(AsUint64(aWindowId));
    if (it == windows->end()) {
      MOZ_ASSERT(false);
      return;
    }

    WindowInfo* info = it->second.get();
    PendingFrameInfo& frameInfo = info->mPendingFrames.front();

    frame = frameInfo;
  } else {
    // Just give the frame info default values.
    frame = {TimeStamp::Now(), VsyncId()};
  }

  // Sadly this doesn't include the lock, since we don't have the frame there
  // yet.
  glean::wr::time_to_render_start.AccumulateRawDuration(TimeStamp::Now() -
                                                        frame.mStartTime);

  // It is for ensuring that PrepareForUse() is called before
  // RenderTextureHost::Lock().
  HandleRenderTextureOps();

  if (aPublishId.isSome()) {
    SetFramePublishId(aWindowId, aPublishId.ref());
  }

  RendererStats stats = {0};

  UpdateAndRender(aWindowId, frame.mStartId, frame.mStartTime, aParams,
                  /* aReadbackSize */ Nothing(),
                  /* aReadbackFormat */ Nothing(),
                  /* aReadbackBuffer */ Nothing(), &stats);

  // The start time is from WebRenderBridgeParent::CompositeToTarget. From that
  // point until now (when the frame is finally pushed to the screen) is
  // equivalent to the COMPOSITE_TIME metric in the non-WR codepath.
  TimeDuration compositeDuration = TimeStamp::Now() - frame.mStartTime;
  mozilla::glean::gfx::composite_time.AccumulateRawDuration(compositeDuration);
  PerfStats::RecordMeasurement(PerfStats::Metric::Compositing,
                               compositeDuration);
  if (stats.frame_build_time > 0.0) {
    TimeDuration fbTime =
        TimeDuration::FromMilliseconds(stats.frame_build_time);
    mozilla::glean::wr::framebuild_time.AccumulateRawDuration(fbTime);
    PerfStats::RecordMeasurement(PerfStats::Metric::FrameBuilding, fbTime);
  }
}

void RenderThread::SetClearColor(wr::WindowId aWindowId, wr::ColorF aColor) {
  if (mHasShutdown) {
    return;
  }

  if (!IsInRenderThread()) {
    PostRunnable(NewRunnableMethod<wr::WindowId, wr::ColorF>(
        "wr::RenderThread::SetClearColor", this, &RenderThread::SetClearColor,
        aWindowId, aColor));
    return;
  }

  if (IsDestroyed(aWindowId)) {
    return;
  }

  auto it = mRenderers.find(aWindowId);
  MOZ_ASSERT(it != mRenderers.end());
  if (it != mRenderers.end()) {
    wr_renderer_set_clear_color(it->second->GetRenderer(), aColor);
  }
}

void RenderThread::SetProfilerUI(wr::WindowId aWindowId,
                                 const nsACString& aUI) {
  if (mHasShutdown) {
    return;
  }

  if (!IsInRenderThread()) {
    PostRunnable(NewRunnableMethod<wr::WindowId, nsCString>(
        "wr::RenderThread::SetProfilerUI", this, &RenderThread::SetProfilerUI,
        aWindowId, nsCString(aUI)));
    return;
  }

  auto it = mRenderers.find(aWindowId);
  if (it != mRenderers.end()) {
    it->second->SetProfilerUI(aUI);
  }
}

void RenderThread::PostEvent(wr::WindowId aWindowId,
                             UniquePtr<RendererEvent> aEvent) {
  PostRunnable(
      NewRunnableMethod<wr::WindowId, UniquePtr<RendererEvent>&&, bool>(
          "wr::RenderThread::PostEvent", this, &RenderThread::RunEvent,
          aWindowId, std::move(aEvent), /* aViaWebRender */ false));
}

void RenderThread::RunEvent(wr::WindowId aWindowId,
                            UniquePtr<RendererEvent> aEvent,
                            bool aViaWebRender) {
  MOZ_ASSERT(IsInRenderThread());

#ifndef DEBUG
  const auto maxDurationMs = 2 * 1000;
  const auto start = TimeStamp::Now();
  const auto delayMs = static_cast<uint32_t>(
      (start - aEvent->mCreationTimeStamp).ToMilliseconds());
  // Check for the delay only if RendererEvent is delivered without using
  // WebRender. Its delivery via WebRender can be very slow.
  if (aViaWebRender && (delayMs > maxDurationMs)) {
    gfxCriticalNote << "Calling " << aEvent->Name()
                    << "::Run: is delayed: " << delayMs;
  }
#endif

  aEvent->Run(*this, aWindowId);
  aEvent = nullptr;

#ifndef DEBUG
  const auto end = TimeStamp::Now();
  const auto durationMs = static_cast<uint32_t>((end - start).ToMilliseconds());
  if (durationMs > maxDurationMs) {
    gfxCriticalNote << "NewRenderer::Run is slow: " << durationMs;
  }
#endif
}

static void NotifyDidRender(layers::CompositorBridgeParent* aBridge,
                            const RefPtr<const WebRenderPipelineInfo>& aInfo,
                            VsyncId aCompositeStartId,
                            TimeStamp aCompositeStart, TimeStamp aRenderStart,
                            TimeStamp aEnd, bool aRender,
                            RendererStats aStats) {
  if (aRender && aBridge->GetWrBridge()) {
    // We call this here to mimic the behavior in LayerManagerComposite, as to
    // not change what Talos measures. That is, we do not record an empty frame
    // as a frame.
    aBridge->GetWrBridge()->RecordFrame();
  }

  aBridge->NotifyDidRender(aCompositeStartId, aCompositeStart, aRenderStart,
                           aEnd, &aStats);

  for (const auto& epoch : aInfo->Raw().epochs) {
    aBridge->NotifyPipelineRendered(epoch.pipeline_id, epoch.epoch,
                                    aCompositeStartId, aCompositeStart,
                                    aRenderStart, aEnd, &aStats);
  }

  if (aBridge->GetWrBridge()) {
    aBridge->GetWrBridge()->RetrySkippedComposite();
  }
}

static void NotifyDidStartRender(layers::CompositorBridgeParent* aBridge) {
  if (aBridge->GetWrBridge()) {
    aBridge->GetWrBridge()->RetrySkippedComposite();
  }
}

void RenderThread::SetFramePublishId(wr::WindowId aWindowId,
                                     FramePublishId aPublishId) {
  MOZ_ASSERT(IsInRenderThread());

  auto it = mRenderers.find(aWindowId);
  MOZ_ASSERT(it != mRenderers.end());
  if (it == mRenderers.end()) {
    return;
  }
  auto& renderer = it->second;

  renderer->SetFramePublishId(aPublishId);
}

void RenderThread::UpdateAndRender(
    wr::WindowId aWindowId, const VsyncId& aStartId,
    const TimeStamp& aStartTime, const wr::FrameReadyParams& aParams,
    const Maybe<gfx::IntSize>& aReadbackSize,
    const Maybe<wr::ImageFormat>& aReadbackFormat,
    const Maybe<Range<uint8_t>>& aReadbackBuffer, RendererStats* aStats,
    bool* aNeedsYFlip) {
  AUTO_PROFILER_LABEL("RenderThread::UpdateAndRender", GRAPHICS);
  MOZ_ASSERT(IsInRenderThread());
  MOZ_ASSERT(aParams.render || aReadbackBuffer.isNothing());

  auto it = mRenderers.find(aWindowId);
  MOZ_ASSERT(it != mRenderers.end());
  if (it == mRenderers.end()) {
    return;
  }

  TimeStamp start = TimeStamp::Now();

  auto& renderer = it->second;

  std::string markerName = "Composite #" + std::to_string(AsUint64(aWindowId));
  AutoProfilerTracing tracingCompositeMarker(
      markerName.c_str(), geckoprofiler::category::GRAPHICS,
      Some(renderer->GetCompositorBridge()->GetInnerWindowId()));

  bool render = aParams.render;
  if (renderer->IsPaused()) {
    render = false;
  }
  LOG("RenderThread::UpdateAndRender() aWindowId %" PRIx64 " aRender %d",
      AsUint64(aWindowId), render);

  layers::CompositorThread()->Dispatch(
      NewRunnableFunction("NotifyDidStartRenderRunnable", &NotifyDidStartRender,
                          renderer->GetCompositorBridge()));

  wr::RenderedFrameId latestFrameId;
  if (render) {
    latestFrameId = renderer->UpdateAndRender(aReadbackSize, aReadbackFormat,
                                              aReadbackBuffer, aNeedsYFlip,
                                              aParams, aStats);
  } else {
    renderer->Update();
  }
  // Check graphics reset status even when rendering is skipped.
  renderer->CheckGraphicsResetStatus(
      gfx::DeviceResetDetectPlace::WR_POST_UPDATE,
      /* aForce */ false);

  TimeStamp end = TimeStamp::Now();
  RefPtr<const WebRenderPipelineInfo> info = renderer->GetLastPipelineInfo();

  layers::CompositorThread()->Dispatch(
      NewRunnableFunction("NotifyDidRenderRunnable", &NotifyDidRender,
                          renderer->GetCompositorBridge(), info, aStartId,
                          aStartTime, start, end, render, *aStats));

  RefPtr<layers::Fence> fence;

  if (latestFrameId.IsValid()) {
    fence = renderer->GetAndResetReleaseFence();

    // Wait for GPU after posting NotifyDidRender, since the wait is not
    // necessary for the NotifyDidRender.
    // The wait is necessary for Textures recycling of AsyncImagePipelineManager
    // and for avoiding GPU queue is filled with too much tasks.
    // WaitForGPU's implementation is different for each platform.
    auto timerId = glean::wr::gpu_wait_time.Start();
    renderer->WaitForGPU();
    glean::wr::gpu_wait_time.StopAndAccumulate(std::move(timerId));
  } else {
    // Update frame id for NotifyPipelinesUpdated() when rendering does not
    // happen, either because rendering was not requested or the frame was
    // canceled. Rendering can sometimes be canceled if UpdateAndRender is
    // called when the window is not yet ready (not mapped or 0 size).
    latestFrameId = renderer->UpdateFrameId();
  }

  RenderedFrameId lastCompletedFrameId = renderer->GetLastCompletedFrameId();

  RefPtr<layers::AsyncImagePipelineManager> pipelineMgr =
      renderer->GetCompositorBridge()->GetAsyncImagePipelineManager();
  // pipelineMgr should always be non-null here because it is only nulled out
  // after the WebRenderAPI instance for the CompositorBridgeParent is
  // destroyed, and that destruction blocks until the renderer thread has
  // removed the relevant renderer. And after that happens we should never reach
  // this code at all; it would bail out at the mRenderers.find check above.
  MOZ_ASSERT(pipelineMgr);
  pipelineMgr->NotifyPipelinesUpdated(info, latestFrameId, lastCompletedFrameId,
                                      std::move(fence));
}

void RenderThread::Pause(wr::WindowId aWindowId) {
  MOZ_ASSERT(IsInRenderThread());
  LOG("RenderThread::Pause() aWindowId %" PRIx64 "", AsUint64(aWindowId));

  auto it = mRenderers.find(aWindowId);
  MOZ_ASSERT(it != mRenderers.end());
  if (it == mRenderers.end()) {
    gfxCriticalNote << "RenderThread cannot find renderer for window "
                    << gfx::hexa(aWindowId) << " to pause.";
    return;
  }
  auto& renderer = it->second;
  renderer->Pause();

  UpdateActiveRendererCount();
}

bool RenderThread::Resume(wr::WindowId aWindowId) {
  MOZ_ASSERT(IsInRenderThread());
  LOG("enderThread::Resume() aWindowId %" PRIx64 "", AsUint64(aWindowId));

  auto it = mRenderers.find(aWindowId);
  MOZ_ASSERT(it != mRenderers.end());
  if (it == mRenderers.end()) {
    gfxCriticalNote << "RenderThread cannot find renderer for window "
                    << gfx::hexa(aWindowId) << " to resume.";
    return false;
  }
  auto& renderer = it->second;
  bool resumed = renderer->Resume();

  UpdateActiveRendererCount();

  return resumed;
}

void RenderThread::NotifyIdle() {
  if (!IsInRenderThread()) {
    PostRunnable(NS_NewRunnableFunction("RenderThread::NotifyIdle", []() {
      if (auto* rt = RenderThread::Get()) {
        rt->NotifyIdle();
      }
    }));
    return;
  }

  wr_chunk_pool_purge(mChunkPool);
}

bool RenderThread::TooManyPendingFrames(wr::WindowId aWindowId) {
  const int64_t maxFrameCount = 1;

  // Too many pending frames if pending frames exit more than maxFrameCount
  // or if RenderBackend is still processing a frame.

  auto windows = mWindowInfos.Lock();
  auto it = windows->find(AsUint64(aWindowId));
  if (it == windows->end()) {
    MOZ_ASSERT(false);
    return true;
  }
  WindowInfo* info = it->second.get();

  if (info->PendingCount() > maxFrameCount) {
    return true;
  }
  // If there is no ongoing frame build, we accept a new frame.
  return info->mPendingFrameBuild > 0;
}

bool RenderThread::IsDestroyed(wr::WindowId aWindowId) {
  auto windows = mWindowInfos.Lock();
  auto it = windows->find(AsUint64(aWindowId));
  if (it == windows->end()) {
    return true;
  }

  return it->second->mIsDestroyed;
}

void RenderThread::SetDestroyed(wr::WindowId aWindowId) {
  auto windows = mWindowInfos.Lock();
  auto it = windows->find(AsUint64(aWindowId));
  if (it == windows->end()) {
    MOZ_ASSERT(false);
    return;
  }
  it->second->mIsDestroyed = true;
}

void RenderThread::IncPendingFrameCount(wr::WindowId aWindowId,
                                        const VsyncId& aStartId,
                                        const TimeStamp& aStartTime) {
  auto windows = mWindowInfos.Lock();
  auto it = windows->find(AsUint64(aWindowId));
  if (it == windows->end()) {
    MOZ_ASSERT(false);
    return;
  }
  it->second->mPendingFrameBuild++;
  it->second->mPendingFrames.push(PendingFrameInfo{aStartTime, aStartId});
}

void RenderThread::DecPendingFrameBuildCount(wr::WindowId aWindowId) {
  auto windows = mWindowInfos.Lock();
  auto it = windows->find(AsUint64(aWindowId));
  if (it == windows->end()) {
    MOZ_ASSERT(false);
    return;
  }
  WindowInfo* info = it->second.get();
  MOZ_RELEASE_ASSERT(info->mPendingFrameBuild >= 1);
  info->mPendingFrameBuild--;
}

void RenderThread::DecPendingFrameCount(wr::WindowId aWindowId) {
  auto windows = mWindowInfos.Lock();
  auto it = windows->find(AsUint64(aWindowId));
  if (it == windows->end()) {
    MOZ_ASSERT(false);
    return;
  }
  WindowInfo* info = it->second.get();
  info->mPendingFrames.pop();
}

void RenderThread::RegisterExternalImage(
    const wr::ExternalImageId& aExternalImageId,
    already_AddRefed<RenderTextureHost> aTexture) {
  MutexAutoLock lock(mRenderTextureMapLock);

  if (mHasShutdown) {
    return;
  }
  MOZ_ASSERT(mRenderTextures.find(aExternalImageId) == mRenderTextures.end());
  RefPtr<RenderTextureHost> texture = aTexture;
  if (texture->SyncObjectNeeded()) {
    mSyncObjectNeededRenderTextures.emplace(aExternalImageId, texture);
  }
  auto [it, inserted] = mRenderTextures.emplace(aExternalImageId, texture);
  MOZ_RELEASE_ASSERT(inserted, "ExternalImageId collision");

#ifdef DEBUG
  int32_t maxAllowedIncrease =
      StaticPrefs::gfx_testing_assert_render_textures_increase();

  if (maxAllowedIncrease <= 0) {
    mRenderTexturesLastTime = -1;
  } else {
    if (mRenderTexturesLastTime < 0) {
      mRenderTexturesLastTime = static_cast<int32_t>(mRenderTextures.size());
    }
    MOZ_ASSERT((static_cast<int32_t>(mRenderTextures.size()) -
                mRenderTexturesLastTime) < maxAllowedIncrease);
  }
#endif
}

void RenderThread::UnregisterExternalImage(
    const wr::ExternalImageId& aExternalImageId) {
  MutexAutoLock lock(mRenderTextureMapLock);
  if (mHasShutdown) {
    return;
  }
  auto it = mRenderTextures.find(aExternalImageId);
  if (it == mRenderTextures.end()) {
    return;
  }

  auto& texture = it->second;
  if (texture->SyncObjectNeeded()) {
    MOZ_RELEASE_ASSERT(
        mSyncObjectNeededRenderTextures.erase(aExternalImageId) == 1);
  }

  if (!IsInRenderThread()) {
    // The RenderTextureHost should be released in render thread. So, post the
    // deletion task here.
    // The shmem and raw buffer are owned by compositor ipc channel. It's
    // possible that RenderTextureHost is still exist after the shmem/raw buffer
    // deletion. Then the buffer in RenderTextureHost becomes invalid. It's fine
    // for this situation. Gecko will only release the buffer if WR doesn't need
    // it. So, no one will access the invalid buffer in RenderTextureHost.
    RefPtr<RenderTextureHost> texture = it->second;
    mRenderTextures.erase(it);
    mRenderTexturesDeferred.emplace_back(std::move(texture));
    PostRunnable(NewRunnableMethod(
        "RenderThread::DeferredRenderTextureHostDestroy", this,
        &RenderThread::DeferredRenderTextureHostDestroy));
  } else {
    mRenderTextures.erase(it);
  }
}

void RenderThread::DestroyExternalImagesSyncWait(
    const std::vector<wr::ExternalImageId>&& aIds) {
  if (!IsInRenderThread()) {
    layers::SynchronousTask task("Destroy external images");

    RefPtr<Runnable> runnable = NS_NewRunnableFunction(
        "RenderThread::DestroyExternalImagesSyncWait::Runnable",
        [&task, ids = std::move(aIds)]() {
          layers::AutoCompleteTask complete(&task);
          RenderThread::Get()->DestroyExternalImages(std::move(ids));
        });

    PostRunnable(runnable.forget());
    task.Wait();
    return;
  }
  DestroyExternalImages(std::move(aIds));
}

void RenderThread::DestroyExternalImages(
    const std::vector<wr::ExternalImageId>&& aIds) {
  MOZ_ASSERT(IsInRenderThread());

  std::vector<RefPtr<RenderTextureHost>> hosts;
  {
    MutexAutoLock lock(mRenderTextureMapLock);
    if (mHasShutdown) {
      return;
    }

    for (auto& id : aIds) {
      auto it = mRenderTextures.find(id);
      if (it == mRenderTextures.end()) {
        continue;
      }
      hosts.emplace_back(it->second);
    }
  }

  for (auto& host : hosts) {
    host->Destroy();
  }
}

void RenderThread::PrepareForUse(const wr::ExternalImageId& aExternalImageId) {
  AddRenderTextureOp(RenderTextureOp::PrepareForUse, aExternalImageId);
}

void RenderThread::NotifyNotUsed(const wr::ExternalImageId& aExternalImageId) {
  AddRenderTextureOp(RenderTextureOp::NotifyNotUsed, aExternalImageId);
}

void RenderThread::NotifyForUse(const wr::ExternalImageId& aExternalImageId) {
  AddRenderTextureOp(RenderTextureOp::NotifyForUse, aExternalImageId);
}

void RenderThread::AddRenderTextureOp(
    RenderTextureOp aOp, const wr::ExternalImageId& aExternalImageId) {
  MOZ_ASSERT(!IsInRenderThread());

  MutexAutoLock lock(mRenderTextureMapLock);

  auto it = mRenderTextures.find(aExternalImageId);
  MOZ_ASSERT(it != mRenderTextures.end());
  if (it == mRenderTextures.end()) {
    return;
  }

  RefPtr<RenderTextureHost> texture = it->second;
  mRenderTextureOps.emplace_back(aOp, std::move(texture));

  if (mRenderTextureOpsRunnable) {
    // Runnable was already triggered
    return;
  }

  RefPtr<nsIRunnable> runnable =
      NewRunnableMethod("RenderThread::HandleRenderTextureOps", this,
                        &RenderThread::HandleRenderTextureOps);
  mRenderTextureOpsRunnable = runnable;
  PostRunnable(runnable.forget());
}

void RenderThread::HandleRenderTextureOps() {
  MOZ_ASSERT(IsInRenderThread());

  std::list<std::pair<RenderTextureOp, RefPtr<RenderTextureHost>>>
      renderTextureOps;
  {
    MutexAutoLock lock(mRenderTextureMapLock);
    mRenderTextureOps.swap(renderTextureOps);
    mRenderTextureOpsRunnable = nullptr;
  }

  for (auto& it : renderTextureOps) {
    switch (it.first) {
      case RenderTextureOp::PrepareForUse:
        it.second->PrepareForUse();
        break;
      case RenderTextureOp::NotifyForUse:
        it.second->NotifyForUse();
        break;
      case RenderTextureOp::NotifyNotUsed:
        it.second->NotifyNotUsed();
        break;
    }
  }
}

RefPtr<RenderTextureHostUsageInfo> RenderThread::GetOrMergeUsageInfo(
    const wr::ExternalImageId& aExternalImageId,
    RefPtr<RenderTextureHostUsageInfo> aUsageInfo) {
  MutexAutoLock lock(mRenderTextureMapLock);
  if (mHasShutdown) {
    return nullptr;
  }
  auto it = mRenderTextures.find(aExternalImageId);
  if (it == mRenderTextures.end()) {
    return nullptr;
  }

  auto& texture = it->second;
  return texture->GetOrMergeUsageInfo(lock, aUsageInfo);
}

void RenderThread::UnregisterExternalImageDuringShutdown(
    const wr::ExternalImageId& aExternalImageId) {
  MOZ_ASSERT(IsInRenderThread());
  MutexAutoLock lock(mRenderTextureMapLock);
  MOZ_ASSERT(mHasShutdown);
  mRenderTextures.erase(aExternalImageId);
}

bool RenderThread::SyncObjectNeeded() {
  MOZ_ASSERT(IsInRenderThread());
  MutexAutoLock lock(mRenderTextureMapLock);
  return !mSyncObjectNeededRenderTextures.empty();
}

void RenderThread::DeferredRenderTextureHostDestroy() {
  MutexAutoLock lock(mRenderTextureMapLock);
  mRenderTexturesDeferred.clear();
}

RenderTextureHost* RenderThread::GetRenderTexture(
    const wr::ExternalImageId& aExternalImageId) {
  MutexAutoLock lock(mRenderTextureMapLock);
  auto it = mRenderTextures.find(aExternalImageId);
  MOZ_ASSERT(it != mRenderTextures.end());
  if (it == mRenderTextures.end()) {
    return nullptr;
  }
  return it->second;
}

std::tuple<RenderTextureHost*, RefPtr<RenderTextureHostUsageInfo>>
RenderThread::GetRenderTextureAndUsageInfo(
    const wr::ExternalImageId& aExternalImageId) {
  MutexAutoLock lock(mRenderTextureMapLock);
  auto it = mRenderTextures.find(aExternalImageId);
  MOZ_ASSERT(it != mRenderTextures.end());
  if (it == mRenderTextures.end()) {
    return {};
  }
  return {it->second, it->second->GetTextureHostUsageInfo(lock)};
}

void RenderThread::InitDeviceTask() {
  MOZ_ASSERT(IsInRenderThread());
  MOZ_ASSERT(!mSingletonGL);
  LOG("RenderThread::InitDeviceTask()");

  const auto start = TimeStamp::Now();

  if (gfx::gfxVars::UseSoftwareWebRender()) {
    // Ensure we don't instantiate any shared GL context when SW-WR is used.
    return;
  }

  nsAutoCString err;
  CreateSingletonGL(err);
  if (gfx::gfxVars::UseWebRenderProgramBinaryDisk()) {
    mProgramCache = MakeUnique<WebRenderProgramCache>(ThreadPool().Raw());
  }
  // Query the shared GL context to force the
  // lazy initialization to happen now.
  SingletonGL();

#ifdef MOZ_WIDGET_ANDROID
  // On Android we must report the GL context's strings to gfxInfo. This allows
  // gfxInfo to avoid creating a GL context during startup.
  if (mSingletonGL) {
    gfx::GfxInfoGLStrings strings(mSingletonGL->VendorString(),
                                  mSingletonGL->RendererString(),
                                  mSingletonGL->VersionString(),
                                  mSingletonGL->ExtensionStrings().Clone());
    if (XRE_IsGPUProcess()) {
      gfx::GPUParent::GetSingleton()->ReportGLStrings(std::move(strings));
    } else if (nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service()) {
      static_cast<widget::GfxInfoBase*>(gfxInfo.get())
          ->ReportGLStrings(std::move(strings));
    }
  }
#endif

  const auto maxDurationMs = 3 * 1000;
  const auto end = TimeStamp::Now();
  const auto durationMs = static_cast<uint32_t>((end - start).ToMilliseconds());
  if (durationMs > maxDurationMs) {
    gfxCriticalNoteOnce << "RenderThread::InitDeviceTask is slow: "
                        << durationMs;
  }
}

void RenderThread::BeginShaderWarmupIfNeeded() {
  if (mShaders && gfx::gfxVars::ShouldWarmUpWebRenderProgramBinaries()) {
    PostResumeShaderWarmupRunnable();
  }
}

void RenderThread::PostResumeShaderWarmupRunnable() {
  RefPtr<Runnable> runnable =
      NewRunnableMethod("RenderThread::ResumeShaderWarmup", this,
                        &RenderThread::ResumeShaderWarmup);
  PostRunnable(runnable.forget());
}

void RenderThread::ResumeShaderWarmup() {
  if (mShaders) {
    bool needAnotherWarmupStep = mShaders->ResumeWarmup();
    if (needAnotherWarmupStep) {
      PostResumeShaderWarmupRunnable();
    }
  }
}

void RenderThread::PostRunnable(already_AddRefed<nsIRunnable> aRunnable) {
  nsCOMPtr<nsIRunnable> runnable = aRunnable;
  mThread->Dispatch(runnable.forget());
}

/* static */ void RenderThread::PostHandleDeviceReset(
    gfx::DeviceResetDetectPlace aPlace, gfx::DeviceResetReason aReason) {
  MOZ_ASSERT(!IsInRenderThread());
  auto* renderThread = Get();
  if (!renderThread) {
    gfx::GPUProcessManager::NotifyDeviceReset(aReason, aPlace);
    return;
  }

  renderThread->PostRunnable(
      NewRunnableMethod<gfx::DeviceResetDetectPlace, gfx::DeviceResetReason>(
          "wr::RenderThread::HandleDeviceReset", renderThread,
          &RenderThread::HandleDeviceReset, aPlace, aReason));
}

void RenderThread::HandleDeviceReset(gfx::DeviceResetDetectPlace aPlace,
                                     gfx::DeviceResetReason aReason) {
  MOZ_ASSERT(IsInRenderThread());

  if (mHandlingDeviceReset) {
    return;
  }

  mHandlingDeviceReset = true;

  {
    MutexAutoLock lock(mRenderTextureMapLock);
    mRenderTexturesDeferred.clear();
    for (const auto& entry : mRenderTextures) {
      entry.second->ClearCachedResources();
    }
  }

  gfx::GPUProcessManager::NotifyDeviceReset(aReason, aPlace);
}

bool RenderThread::IsHandlingDeviceReset() {
  MOZ_ASSERT(IsInRenderThread());
  return mHandlingDeviceReset;
}

void RenderThread::SimulateDeviceReset() {
  if (!IsInRenderThread()) {
    PostRunnable(NewRunnableMethod("RenderThread::SimulateDeviceReset", this,
                                   &RenderThread::SimulateDeviceReset));
  } else {
    // When this function is called GPUProcessManager::SimulateDeviceReset()
    // already triggers destroying all CompositorSessions before re-creating
    // them.
    HandleDeviceReset(gfx::DeviceResetDetectPlace::WR_SIMULATE,
                      gfx::DeviceResetReason::FORCED_RESET);
  }
}

static void DoNotifyWebRenderError(WebRenderError aError) {
  layers::CompositorManagerParent::NotifyWebRenderError(aError);
}

void RenderThread::NotifyWebRenderError(WebRenderError aError) {
  MOZ_ASSERT(IsInRenderThread());

  layers::CompositorThread()->Dispatch(NewRunnableFunction(
      "DoNotifyWebRenderErrorRunnable", &DoNotifyWebRenderError, aError));
}

void RenderThread::HandleWebRenderError(WebRenderError aError) {
  MOZ_ASSERT(IsInRenderThread());
  if (mHandlingWebRenderError) {
    return;
  }

  NotifyWebRenderError(aError);

  {
    MutexAutoLock lock(mRenderTextureMapLock);
    mRenderTexturesDeferred.clear();
    for (const auto& entry : mRenderTextures) {
      entry.second->ClearCachedResources();
    }
  }
  mHandlingWebRenderError = true;
  // WebRender is going to be disabled by
  // GPUProcessManager::NotifyWebRenderError()
}

bool RenderThread::IsHandlingWebRenderError() {
  MOZ_ASSERT(IsInRenderThread());
  return mHandlingWebRenderError;
}

gl::GLContext* RenderThread::SingletonGL() {
  nsAutoCString err;
  auto* gl = SingletonGL(err);
  if (!err.IsEmpty()) {
    gfxCriticalNote << err.get();
  }
  return gl;
}

void RenderThread::CreateSingletonGL(nsACString& aError) {
  MOZ_ASSERT(IsInRenderThread());
  LOG("RenderThread::CreateSingletonGL()");

  mSingletonGL = CreateGLContext(aError);
  mSingletonGLIsForHardwareWebRender = !gfx::gfxVars::UseSoftwareWebRender();
}

gl::GLContext* RenderThread::SingletonGL(nsACString& aError) {
  MOZ_ASSERT(IsInRenderThread());
  if (!mSingletonGL) {
    CreateSingletonGL(aError);
    mShaders = nullptr;
  }
  if (mSingletonGL && mSingletonGLIsForHardwareWebRender && !mShaders) {
    mShaders = MakeUnique<WebRenderShaders>(mSingletonGL, mProgramCache.get());
  }

  return mSingletonGL.get();
}

gl::GLContext* RenderThread::SingletonGLForCompositorOGL() {
  MOZ_RELEASE_ASSERT(gfx::gfxVars::UseSoftwareWebRender());

  if (mSingletonGLIsForHardwareWebRender) {
    // Clear singleton GL, since GLContext is for hardware WebRender.
    ClearSingletonGL();
  }
  return SingletonGL();
}

void RenderThread::ClearSingletonGL() {
  MOZ_ASSERT(IsInRenderThread());
  LOG("RenderThread::ClearSingletonGL()");

  if (mSurfacePool) {
    mSurfacePool->DestroyGLResourcesForContext(mSingletonGL);
  }
  if (mProgramsForCompositorOGL) {
    mProgramsForCompositorOGL->Clear();
    mProgramsForCompositorOGL = nullptr;
  }
  mShaders = nullptr;
  mSingletonGL = nullptr;
}

RefPtr<layers::ShaderProgramOGLsHolder>
RenderThread::GetProgramsForCompositorOGL() {
  if (!mSingletonGL) {
    return nullptr;
  }

  if (!mProgramsForCompositorOGL) {
    mProgramsForCompositorOGL =
        MakeAndAddRef<layers::ShaderProgramOGLsHolder>(mSingletonGL);
  }
  return mProgramsForCompositorOGL;
}

RefPtr<layers::SurfacePool> RenderThread::SharedSurfacePool() {
#if defined(XP_DARWIN) || defined(MOZ_WAYLAND)
  if (!mSurfacePool) {
    size_t poolSizeLimit =
        StaticPrefs::gfx_webrender_compositor_surface_pool_size_AtStartup();
    mSurfacePool = layers::SurfacePool::Create(poolSizeLimit);
  }
#endif
  return mSurfacePool;
}

void RenderThread::ClearSharedSurfacePool() { mSurfacePool = nullptr; }

static void GLAPIENTRY DebugMessageCallback(GLenum aSource, GLenum aType,
                                            GLuint aId, GLenum aSeverity,
                                            GLsizei aLength,
                                            const GLchar* aMessage,
                                            const GLvoid* aUserParam) {
  constexpr const char* kContextLost = "Context has been lost.";

  if (StaticPrefs::gfx_webrender_gl_debug_message_critical_note_AtStartup() &&
      aSeverity == LOCAL_GL_DEBUG_SEVERITY_HIGH) {
    auto message = std::string(aMessage, aLength);
    // When content lost happned, error messages are flooded by its message.
    if (message != kContextLost) {
      gfxCriticalNote << message;
    } else {
      gfxCriticalNoteOnce << message;
    }
  }

  if (StaticPrefs::gfx_webrender_gl_debug_message_print_AtStartup()) {
    gl::GLContext* gl = (gl::GLContext*)aUserParam;
    gl->DebugCallback(aSource, aType, aId, aSeverity, aLength, aMessage);
  }
}

// static
void RenderThread::MaybeEnableGLDebugMessage(gl::GLContext* aGLContext) {
  if (!aGLContext) {
    return;
  }

  bool enableDebugMessage =
      StaticPrefs::gfx_webrender_gl_debug_message_critical_note_AtStartup() ||
      StaticPrefs::gfx_webrender_gl_debug_message_print_AtStartup();

  if (enableDebugMessage &&
      aGLContext->IsExtensionSupported(gl::GLContext::KHR_debug)) {
    aGLContext->fEnable(LOCAL_GL_DEBUG_OUTPUT);
    aGLContext->fDisable(LOCAL_GL_DEBUG_OUTPUT_SYNCHRONOUS);
    aGLContext->fDebugMessageCallback(&DebugMessageCallback, (void*)aGLContext);
    aGLContext->fDebugMessageControl(LOCAL_GL_DONT_CARE, LOCAL_GL_DONT_CARE,
                                     LOCAL_GL_DONT_CARE, 0, nullptr, true);
  }
}

WebRenderShaders::WebRenderShaders(gl::GLContext* gl,
                                   WebRenderProgramCache* programCache) {
  mGL = gl;
  mShaders =
      wr_shaders_new(gl, programCache ? programCache->Raw() : nullptr,
                     StaticPrefs::gfx_webrender_precache_shaders_AtStartup());
}

WebRenderShaders::~WebRenderShaders() {
  mGL->MakeCurrent();
  wr_shaders_delete(mShaders);
}

bool WebRenderShaders::ResumeWarmup() {
  mGL->MakeCurrent();
  return wr_shaders_resume_warmup(mShaders);
}

WebRenderThreadPool::WebRenderThreadPool(bool low_priority) {
  mThreadPool = wr_thread_pool_new(low_priority);
}

WebRenderThreadPool::~WebRenderThreadPool() { Destroy(false); }

void WebRenderThreadPool::Destroy(bool aJoinWorkers) {
  if (mThreadPool) {
    wr_thread_pool_delete(mThreadPool, aJoinWorkers);
    mThreadPool = nullptr;
  }
}

MaybeWebRenderGlyphRasterThread::MaybeWebRenderGlyphRasterThread(bool aEnable) {
  if (aEnable) {
    mThread = wr_glyph_raster_thread_new();
  } else {
    mThread = nullptr;
  }
}

MaybeWebRenderGlyphRasterThread::~MaybeWebRenderGlyphRasterThread() {
  if (mThread) {
    wr_glyph_raster_thread_delete(mThread);
  }
}

WebRenderProgramCache::WebRenderProgramCache(wr::WrThreadPool* aThreadPool) {
  MOZ_ASSERT(aThreadPool);

  nsAutoString path;
  if (gfx::gfxVars::UseWebRenderProgramBinaryDisk()) {
    path.Append(gfx::gfxVars::ProfDirectory());
  }
  mProgramCache = wr_program_cache_new(&path, aThreadPool);
  if (gfx::gfxVars::UseWebRenderProgramBinaryDisk()) {
    wr_try_load_startup_shaders_from_disk(mProgramCache);
  }
}

WebRenderProgramCache::~WebRenderProgramCache() {
  wr_program_cache_delete(mProgramCache);
}

}  // namespace mozilla::wr

#ifdef XP_WIN
static already_AddRefed<gl::GLContext> CreateGLContextANGLE(
    nsACString& aError) {
  const RefPtr<ID3D11Device> d3d11Device =
      gfx::DeviceManagerDx::Get()->GetCompositorDevice();
  if (!d3d11Device) {
    aError.Assign("RcANGLE(no compositor device for EGLDisplay)"_ns);
    return nullptr;
  }

  nsCString failureId;
  const auto lib = gl::GLLibraryEGL::Get(&failureId);
  if (!lib) {
    aError.Assign(
        nsPrintfCString("RcANGLE(load EGL lib failed: %s)", failureId.get()));
    return nullptr;
  }

  const auto egl = lib->CreateDisplay(d3d11Device.get());
  if (!egl) {
    aError.Assign(nsPrintfCString("RcANGLE(create EGLDisplay failed: %s)",
                                  failureId.get()));
    return nullptr;
  }

  gl::CreateContextFlags flags = gl::CreateContextFlags::PREFER_ES3;

  if (StaticPrefs::gfx_webrender_prefer_robustness_AtStartup()) {
    flags |= gl::CreateContextFlags::PREFER_ROBUSTNESS;
  }

  if (egl->IsExtensionSupported(
          gl::EGLExtension::MOZ_create_context_provoking_vertex_dont_care)) {
    flags |= gl::CreateContextFlags::PROVOKING_VERTEX_DONT_CARE;
  }

  // Create GLContext with dummy EGLSurface, the EGLSurface is not used.
  // Instread we override it with EGLSurface of SwapChain's back buffer.

  auto gl = gl::GLContextEGL::CreateWithoutSurface(egl, {flags}, &failureId);
  if (!gl || !gl->IsANGLE()) {
    aError.Assign(nsPrintfCString("RcANGLE(create GL context failed: %p, %s)",
                                  gl.get(), failureId.get()));
    return nullptr;
  }

  if (!gl->MakeCurrent()) {
    aError.Assign(
        nsPrintfCString("RcANGLE(make current GL context failed: %p, %x)",
                        gl.get(), gl->mEgl->mLib->fGetError()));
    return nullptr;
  }

  return gl.forget();
}
#endif

#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK)
static already_AddRefed<gl::GLContext> CreateGLContextEGL() {
  // Create GLContext with dummy EGLSurface.
  bool forHardwareWebRender = true;
  // SW-WR uses CompositorOGL in native compositor.
  if (gfx::gfxVars::UseSoftwareWebRender()) {
    forHardwareWebRender = false;
  }
  RefPtr<gl::GLContext> gl =
      gl::GLContextProviderEGL::CreateForCompositorWidget(
          nullptr, forHardwareWebRender, /* aForceAccelerated */ true);
  if (!gl || !gl->MakeCurrent()) {
    gfxCriticalNote << "Failed GL context creation for hardware WebRender: "
                    << forHardwareWebRender;
    return nullptr;
  }
  return gl.forget();
}
#endif

#ifdef XP_DARWIN
static already_AddRefed<gl::GLContext> CreateGLContextCGL() {
  nsCString failureUnused;
  return gl::GLContextProvider::CreateHeadless(
      {gl::CreateContextFlags::ALLOW_OFFLINE_RENDERER |
       gl::CreateContextFlags::FORBID_SOFTWARE},
      &failureUnused);
}
#endif

static already_AddRefed<gl::GLContext> CreateGLContext(nsACString& aError) {
  RefPtr<gl::GLContext> gl;

#ifdef XP_WIN
  if (gfx::gfxVars::UseWebRenderANGLE()) {
    gl = CreateGLContextANGLE(aError);
  }
#elif defined(MOZ_WIDGET_ANDROID)
  gl = CreateGLContextEGL();
#elif defined(MOZ_WIDGET_GTK)
  if (gfx::gfxVars::UseEGL()) {
    gl = CreateGLContextEGL();
  }
#elif XP_DARWIN
  gl = CreateGLContextCGL();
#endif

  wr::RenderThread::MaybeEnableGLDebugMessage(gl);

  return gl.forget();
}

extern "C" {

void wr_notifier_wake_up(mozilla::wr::WrWindowId aWindowId,
                         bool aCompositeNeeded) {
  // wake_up is used for things like propagating debug options or memory
  // pressure events, so we are not tracking pending frame counts.
  mozilla::wr::RenderThread::Get()->WrNotifierEvent_WakeUp(aWindowId,
                                                           aCompositeNeeded);
}

void wr_notifier_new_frame_ready(wr::WrWindowId aWindowId,
                                 wr::FramePublishId aPublishId,
                                 const wr::FrameReadyParams* aParams) {
  auto* renderThread = mozilla::wr::RenderThread::Get();
  if (aParams->tracked) {
    renderThread->DecPendingFrameBuildCount(aWindowId);
  }

  renderThread->WrNotifierEvent_NewFrameReady(aWindowId, aPublishId, aParams);
}

void wr_notifier_external_event(mozilla::wr::WrWindowId aWindowId,
                                size_t aRawEvent) {
  mozilla::wr::RenderThread::Get()->WrNotifierEvent_ExternalEvent(
      mozilla::wr::WindowId(aWindowId), aRawEvent);
}

static void NotifyScheduleRender(mozilla::wr::WrWindowId aWindowId,
                                 wr::RenderReasons aReasons) {
  RefPtr<mozilla::layers::CompositorBridgeParent> cbp = mozilla::layers::
      CompositorBridgeParent::GetCompositorBridgeParentFromWindowId(aWindowId);
  if (cbp) {
    cbp->ScheduleComposition(aReasons);
  }
}

void wr_schedule_render(mozilla::wr::WrWindowId aWindowId,
                        wr::RenderReasons aReasons) {
  layers::CompositorThread()->Dispatch(NewRunnableFunction(
      "NotifyScheduleRender", &NotifyScheduleRender, aWindowId, aReasons));
}

static void ScheduleFrameAfterSceneBuild(
    mozilla::wr::WrWindowId aWindowId,
    const RefPtr<const wr::WebRenderPipelineInfo>& aInfo) {
  RefPtr<mozilla::layers::CompositorBridgeParent> cbp = mozilla::layers::
      CompositorBridgeParent::GetCompositorBridgeParentFromWindowId(aWindowId);
  if (cbp) {
    cbp->ScheduleFrameAfterSceneBuild(aInfo);
  }
}

void wr_schedule_frame_after_scene_build(
    mozilla::wr::WrWindowId aWindowId,
    mozilla::wr::WrPipelineInfo* aPipelineInfo) {
  RefPtr<wr::WebRenderPipelineInfo> info = new wr::WebRenderPipelineInfo();
  info->Raw() = std::move(*aPipelineInfo);
  layers::CompositorThread()->Dispatch(
      NewRunnableFunction("ScheduleFrameAfterSceneBuild",
                          &ScheduleFrameAfterSceneBuild, aWindowId, info));
}

}  // extern C
