Blob Blame History Raw
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "RenderThread.h"
#include "nsThreadUtils.h"
#include "mtransport/runnable_utils.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/webrender/RendererOGL.h"
#include "mozilla/webrender/RenderTextureHost.h"
#include "mozilla/widget/CompositorWidget.h"

namespace mozilla {
namespace wr {

static StaticRefPtr<RenderThread> sRenderThread;

RenderThread::RenderThread(base::Thread* aThread)
    : mThread(aThread),
      mFrameCountMapLock("RenderThread.mFrameCountMapLock"),
      mRenderTextureMapLock("RenderThread.mRenderTextureMapLock"),
      mHasShutdown(false) {}

RenderThread::~RenderThread() { delete mThread; }

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

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

  base::Thread* thread = new base::Thread("Renderer");

  base::Thread::Options options;
  // TODO(nical): The compositor thread has a bunch of specific options, see
  // which ones make sense here.
  if (!thread->StartWithOptions(options)) {
    delete thread;
    return;
  }

  sRenderThread = new RenderThread(thread);
}

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

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

  layers::SynchronousTask task("RenderThread");
  RefPtr<Runnable> runnable =
      WrapRunnable(RefPtr<RenderThread>(sRenderThread.get()),
                   &RenderThread::ShutDownTask, &task);
  sRenderThread->Loop()->PostTask(runnable.forget());
  task.Wait();

  sRenderThread = nullptr;
}

void RenderThread::ShutDownTask(layers::SynchronousTask* aTask) {
  layers::AutoCompleteTask complete(aTask);
  MOZ_ASSERT(IsInRenderThread());
}

// static
MessageLoop* RenderThread::Loop() {
  return sRenderThread ? sRenderThread->mThread->message_loop() : nullptr;
}

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

void RenderThread::AddRenderer(wr::WindowId aWindowId,
                               UniquePtr<RendererOGL> aRenderer) {
  MOZ_ASSERT(IsInRenderThread());

  if (mHasShutdown) {
    return;
  }

  mRenderers[aWindowId] = Move(aRenderer);

  MutexAutoLock lock(mFrameCountMapLock);
  mWindowInfos.Put(AsUint64(aWindowId), WindowInfo());
}

void RenderThread::RemoveRenderer(wr::WindowId aWindowId) {
  MOZ_ASSERT(IsInRenderThread());

  if (mHasShutdown) {
    return;
  }

  mRenderers.erase(aWindowId);

  MutexAutoLock lock(mFrameCountMapLock);
  mWindowInfos.Remove(AsUint64(aWindowId));
}

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();
}

void RenderThread::NewFrameReady(wr::WindowId aWindowId) {
  if (mHasShutdown) {
    return;
  }

  if (!IsInRenderThread()) {
    Loop()->PostTask(NewRunnableMethod<wr::WindowId>(
        "wr::RenderThread::NewFrameReady", this, &RenderThread::NewFrameReady,
        aWindowId));
    return;
  }

  if (IsDestroyed(aWindowId)) {
    return;
  }

  UpdateAndRender(aWindowId);
  DecPendingFrameCount(aWindowId);
}

void RenderThread::WakeUp(wr::WindowId aWindowId) {
  if (mHasShutdown) {
    return;
  }

  if (!IsInRenderThread()) {
    Loop()->PostTask(NewRunnableMethod<wr::WindowId>(
        "wr::RenderThread::WakeUp", this, &RenderThread::WakeUp, aWindowId));
    return;
  }

  if (IsDestroyed(aWindowId)) {
    return;
  }

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

void RenderThread::RunEvent(wr::WindowId aWindowId,
                            UniquePtr<RendererEvent> aEvent) {
  if (!IsInRenderThread()) {
    Loop()->PostTask(
        NewRunnableMethod<wr::WindowId, UniquePtr<RendererEvent>&&>(
            "wr::RenderThread::RunEvent", this, &RenderThread::RunEvent,
            aWindowId, Move(aEvent)));
    return;
  }

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

static void NotifyDidRender(layers::CompositorBridgeParentBase* aBridge,
                            wr::WrPipelineInfo* aInfo, TimeStamp aStart,
                            TimeStamp aEnd) {
  wr::WrPipelineId pipeline;
  wr::WrEpoch epoch;
  while (wr_pipeline_info_next_epoch(aInfo, &pipeline, &epoch)) {
    aBridge->NotifyDidCompositeToPipeline(pipeline, epoch, aStart, aEnd);
  }
  while (wr_pipeline_info_next_removed_pipeline(aInfo, &pipeline)) {
    aBridge->NotifyPipelineRemoved(pipeline);
  }

  wr_pipeline_info_delete(aInfo);
}

void RenderThread::UpdateAndRender(wr::WindowId aWindowId, bool aReadback) {
  AUTO_PROFILER_TRACING("Paint", "Composite");
  MOZ_ASSERT(IsInRenderThread());

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

  auto& renderer = it->second;
  TimeStamp start = TimeStamp::Now();

  bool ret = renderer->UpdateAndRender(aReadback);
  if (!ret) {
    // Render did not happen, do not call NotifyDidRender.
    return;
  }

  TimeStamp end = TimeStamp::Now();

  auto info = renderer->FlushPipelineInfo();
  layers::CompositorThreadHolder::Loop()->PostTask(
      NewRunnableFunction("NotifyDidRenderRunnable", &NotifyDidRender,
                          renderer->GetCompositorBridge(), info, start, end));
}

void RenderThread::Pause(wr::WindowId aWindowId) {
  MOZ_ASSERT(IsInRenderThread());

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

bool RenderThread::Resume(wr::WindowId aWindowId) {
  MOZ_ASSERT(IsInRenderThread());

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

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.

  MutexAutoLock lock(mFrameCountMapLock);
  WindowInfo info;
  if (!mWindowInfos.Get(AsUint64(aWindowId), &info)) {
    MOZ_ASSERT(false);
    return true;
  }

  if (info.mPendingCount > maxFrameCount) {
    return true;
  }
  MOZ_ASSERT(info.mPendingCount >= info.mRenderingCount);
  return info.mPendingCount > info.mRenderingCount;
}

bool RenderThread::IsDestroyed(wr::WindowId aWindowId) {
  MutexAutoLock lock(mFrameCountMapLock);
  WindowInfo info;
  if (!mWindowInfos.Get(AsUint64(aWindowId), &info)) {
    return true;
  }

  return info.mIsDestroyed;
}

void RenderThread::SetDestroyed(wr::WindowId aWindowId) {
  MutexAutoLock lock(mFrameCountMapLock);
  WindowInfo info;
  if (!mWindowInfos.Get(AsUint64(aWindowId), &info)) {
    MOZ_ASSERT(false);
    return;
  }
  info.mIsDestroyed = true;
  mWindowInfos.Put(AsUint64(aWindowId), info);
}

void RenderThread::IncPendingFrameCount(wr::WindowId aWindowId) {
  MutexAutoLock lock(mFrameCountMapLock);
  // Get the old count.
  WindowInfo info;
  if (!mWindowInfos.Get(AsUint64(aWindowId), &info)) {
    MOZ_ASSERT(false);
    return;
  }
  // Update pending frame count.
  info.mPendingCount = info.mPendingCount + 1;
  mWindowInfos.Put(AsUint64(aWindowId), info);
}

void RenderThread::IncRenderingFrameCount(wr::WindowId aWindowId) {
  MutexAutoLock lock(mFrameCountMapLock);
  // Get the old count.
  WindowInfo info;
  if (!mWindowInfos.Get(AsUint64(aWindowId), &info)) {
    MOZ_ASSERT(false);
    return;
  }
  // Update rendering frame count.
  info.mRenderingCount = info.mRenderingCount + 1;
  mWindowInfos.Put(AsUint64(aWindowId), info);
}

void RenderThread::DecPendingFrameCount(wr::WindowId aWindowId) {
  MutexAutoLock lock(mFrameCountMapLock);
  // Get the old count.
  WindowInfo info;
  if (!mWindowInfos.Get(AsUint64(aWindowId), &info)) {
    MOZ_ASSERT(false);
    return;
  }
  MOZ_ASSERT(info.mPendingCount > 0);
  MOZ_ASSERT(info.mRenderingCount > 0);
  if (info.mPendingCount <= 0) {
    return;
  }
  // Update frame counts.
  info.mPendingCount = info.mPendingCount - 1;
  info.mRenderingCount = info.mRenderingCount - 1;
  mWindowInfos.Put(AsUint64(aWindowId), info);
}

void RenderThread::RegisterExternalImage(
    uint64_t aExternalImageId, already_AddRefed<RenderTextureHost> aTexture) {
  MutexAutoLock lock(mRenderTextureMapLock);

  if (mHasShutdown) {
    return;
  }
  MOZ_ASSERT(!mRenderTextures.GetWeak(aExternalImageId));
  mRenderTextures.Put(aExternalImageId, Move(aTexture));
}

void RenderThread::UnregisterExternalImage(uint64_t aExternalImageId) {
  MutexAutoLock lock(mRenderTextureMapLock);
  if (mHasShutdown) {
    return;
  }
  MOZ_ASSERT(mRenderTextures.GetWeak(aExternalImageId));
  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;
    mRenderTextures.Remove(aExternalImageId, getter_AddRefs(texture));
    Loop()->PostTask(NewRunnableMethod<RefPtr<RenderTextureHost>>(
        "RenderThread::DeferredRenderTextureHostDestroy", this,
        &RenderThread::DeferredRenderTextureHostDestroy, Move(texture)));
  } else {
    mRenderTextures.Remove(aExternalImageId);
  }
}

void RenderThread::DeferredRenderTextureHostDestroy(RefPtr<RenderTextureHost>) {
  // Do nothing. Just decrease the ref-count of RenderTextureHost.
}

RenderTextureHost* RenderThread::GetRenderTexture(
    wr::WrExternalImageId aExternalImageId) {
  MOZ_ASSERT(IsInRenderThread());

  MutexAutoLock lock(mRenderTextureMapLock);
  MOZ_ASSERT(mRenderTextures.GetWeak(aExternalImageId.mHandle));
  return mRenderTextures.GetWeak(aExternalImageId.mHandle);
}

WebRenderProgramCache* RenderThread::ProgramCache() {
  MOZ_ASSERT(IsInRenderThread());

  if (!mProgramCache) {
    mProgramCache = MakeUnique<WebRenderProgramCache>();
  }
  return mProgramCache.get();
}

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

WebRenderThreadPool::~WebRenderThreadPool() {
  wr_thread_pool_delete(mThreadPool);
}

WebRenderProgramCache::WebRenderProgramCache() {
  mProgramCache = wr_program_cache_new();
}

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

}  // namespace wr
}  // namespace mozilla

extern "C" {

static void NewFrameReady(mozilla::wr::WrWindowId aWindowId) {
  mozilla::wr::RenderThread::Get()->IncRenderingFrameCount(aWindowId);
  mozilla::wr::RenderThread::Get()->NewFrameReady(
      mozilla::wr::WindowId(aWindowId));
}

void wr_notifier_wake_up(mozilla::wr::WrWindowId aWindowId) {
  mozilla::wr::RenderThread::Get()->WakeUp(mozilla::wr::WindowId(aWindowId));
}

void wr_notifier_new_frame_ready(mozilla::wr::WrWindowId aWindowId) {
  NewFrameReady(aWindowId);
}

void wr_notifier_new_scroll_frame_ready(mozilla::wr::WrWindowId aWindowId,
                                        bool aCompositeNeeded) {
  // If we sent a transaction that contained both scrolling updates and a
  // GenerateFrame, we can get this function called with aCompositeNeeded=true
  // instead of wr_notifier_new_frame_ready. In that case we want to update the
  // rendering.
  if (aCompositeNeeded) {
    NewFrameReady(aWindowId);
  }
}

void wr_notifier_external_event(mozilla::wr::WrWindowId aWindowId,
                                size_t aRawEvent) {
  mozilla::UniquePtr<mozilla::wr::RendererEvent> evt(
      reinterpret_cast<mozilla::wr::RendererEvent*>(aRawEvent));
  mozilla::wr::RenderThread::Get()->RunEvent(mozilla::wr::WindowId(aWindowId),
                                             mozilla::Move(evt));
}

}  // extern C