d7e4243b创建于 2012年9月17日历史提交
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/common/gpu/gpu_memory_manager.h"

#if defined(ENABLE_GPU)

#include <algorithm>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/message_loop.h"
#include "base/process_util.h"
#include "base/string_number_conversions.h"
#include "content/common/gpu/gpu_command_buffer_stub.h"
#include "content/common/gpu/gpu_memory_allocation.h"
#include "content/common/gpu/gpu_memory_tracking.h"
#include "gpu/command_buffer/service/gpu_switches.h"

namespace {

const int kDelayedScheduleManageTimeoutMs = 67;

bool IsInSameContextShareGroupAsAnyOf(
    const GpuCommandBufferStubBase* stub,
    const std::vector<GpuCommandBufferStubBase*>& stubs) {
  for (std::vector<GpuCommandBufferStubBase*>::const_iterator it =
      stubs.begin(); it != stubs.end(); ++it) {
    if (stub->IsInSameContextShareGroup(**it))
      return true;
  }
  return false;
}

void AssignMemoryAllocations(
    GpuMemoryManager::StubMemoryStatMap* stub_memory_stats,
    const std::vector<GpuCommandBufferStubBase*>& stubs,
    GpuMemoryAllocation allocation,
    bool visible) {
  for (std::vector<GpuCommandBufferStubBase*>::const_iterator it =
          stubs.begin();
      it != stubs.end();
      ++it) {
    (*it)->SetMemoryAllocation(allocation);
    (*stub_memory_stats)[*it].allocation = allocation;
    (*stub_memory_stats)[*it].visible = visible;
  }
}

}

size_t GpuMemoryManager::CalculateBonusMemoryAllocationBasedOnSize(
    gfx::Size size) const {
  const int kViewportMultiplier = 16;
  const unsigned int kComponentsPerPixel = 4; // GraphicsContext3D::RGBA
  const unsigned int kBytesPerComponent = 1; // sizeof(GC3Dubyte)

  if (size.IsEmpty())
    return 0;

  size_t limit = kViewportMultiplier * size.width() * size.height() *
                 kComponentsPerPixel * kBytesPerComponent;
  if (limit < GetMinimumTabAllocation())
    limit = GetMinimumTabAllocation();
  else if (limit > GetAvailableGpuMemory())
    limit = GetAvailableGpuMemory();
  return limit - GetMinimumTabAllocation();
}

GpuMemoryManager::GpuMemoryManager(GpuMemoryManagerClient* client,
        size_t max_surfaces_with_frontbuffer_soft_limit)
    : client_(client),
      manage_immediate_scheduled_(false),
      max_surfaces_with_frontbuffer_soft_limit_(
          max_surfaces_with_frontbuffer_soft_limit),
      bytes_available_gpu_memory_(0),
      bytes_allocated_current_(0),
      bytes_allocated_historical_max_(0) {
  CommandLine* command_line = CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kForceGpuMemAvailableMb)) {
    base::StringToSizeT(
      command_line->GetSwitchValueASCII(switches::kForceGpuMemAvailableMb),
      &bytes_available_gpu_memory_);
    bytes_available_gpu_memory_ *= 1024 * 1024;
  } else {
#if defined(OS_ANDROID)
    bytes_available_gpu_memory_ = 64 * 1024 * 1024;
#else
#if defined(OS_CHROMEOS)
    bytes_available_gpu_memory_ = 1024 * 1024 * 1024;
#else
    bytes_available_gpu_memory_ = 256 * 1024 * 1024;
#endif
#endif
  }
}

GpuMemoryManager::~GpuMemoryManager() {
  DCHECK(tracking_groups_.empty());
}

bool GpuMemoryManager::StubWithSurfaceComparator::operator()(
    GpuCommandBufferStubBase* lhs,
    GpuCommandBufferStubBase* rhs) {
  DCHECK(lhs->has_surface_state() && rhs->has_surface_state());
  const GpuCommandBufferStubBase::SurfaceState& lhs_ss = lhs->surface_state();
  const GpuCommandBufferStubBase::SurfaceState& rhs_ss = rhs->surface_state();
  if (lhs_ss.visible)
    return !rhs_ss.visible || (lhs_ss.last_used_time > rhs_ss.last_used_time);
  else
    return !rhs_ss.visible && (lhs_ss.last_used_time > rhs_ss.last_used_time);
};

void GpuMemoryManager::ScheduleManage(bool immediate) {
  if (manage_immediate_scheduled_)
    return;
  if (immediate) {
    MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&GpuMemoryManager::Manage, AsWeakPtr()));
    manage_immediate_scheduled_ = true;
    if (!delayed_manage_callback_.IsCancelled())
      delayed_manage_callback_.Cancel();
  } else {
    if (!delayed_manage_callback_.IsCancelled())
      return;
    delayed_manage_callback_.Reset(base::Bind(&GpuMemoryManager::Manage,
                                              AsWeakPtr()));
    MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      delayed_manage_callback_.callback(),
      base::TimeDelta::FromMilliseconds(kDelayedScheduleManageTimeoutMs));
  }
}

void GpuMemoryManager::TrackMemoryAllocatedChange(size_t old_size,
                                                  size_t new_size) {
  if (new_size < old_size) {
    size_t delta = old_size - new_size;
    DCHECK(bytes_allocated_current_ >= delta);
    bytes_allocated_current_ -= delta;
  } else {
    size_t delta = new_size - old_size;
    bytes_allocated_current_ += delta;
    if (bytes_allocated_current_ > bytes_allocated_historical_max_) {
      bytes_allocated_historical_max_ = bytes_allocated_current_;
    }
  }
  if (new_size != old_size) {
    TRACE_COUNTER1("gpu",
                   "GpuMemoryUsage",
                   bytes_allocated_current_);
  }
}

void GpuMemoryManager::AddTrackingGroup(
    GpuMemoryTrackingGroup* tracking_group) {
  tracking_groups_.insert(tracking_group);
}

void GpuMemoryManager::RemoveTrackingGroup(
    GpuMemoryTrackingGroup* tracking_group) {
  tracking_groups_.erase(tracking_group);
}

void GpuMemoryManager::GetVideoMemoryUsageStats(
    content::GPUVideoMemoryUsageStats& video_memory_usage_stats) const {
  // For each context group, assign its memory usage to its PID
  video_memory_usage_stats.process_map.clear();
  for (std::set<GpuMemoryTrackingGroup*>::const_iterator i =
       tracking_groups_.begin(); i != tracking_groups_.end(); ++i) {
    const GpuMemoryTrackingGroup* tracking_group = (*i);
    video_memory_usage_stats.process_map[
        tracking_group->GetPid()].video_memory += tracking_group->GetSize();
  }

  // Assign the total across all processes in the GPU process
  video_memory_usage_stats.process_map[
      base::GetCurrentProcId()].video_memory = bytes_allocated_current_;
  video_memory_usage_stats.process_map[
      base::GetCurrentProcId()].has_duplicates = true;
}

// The current Manage algorithm simply classifies contexts (stubs) into
// "foreground", "background", or "hibernated" categories.
// For each of these three categories, there are predefined memory allocation
// limits and front/backbuffer states.
//
// Stubs may or may not have a surfaces, and the rules are different for each.
//
// The rules for categorizing contexts with a surface are:
//  1. Foreground: All visible surfaces.
//                 * Must have both front and back buffer.
//
//  2. Background: Non visible surfaces, which have not surpassed the
//                 max_surfaces_with_frontbuffer_soft_limit_ limit.
//                 * Will have only a frontbuffer.
//
//  3. Hibernated: Non visible surfaces, which have surpassed the
//                 max_surfaces_with_frontbuffer_soft_limit_ limit.
//                 * Will not have either buffer.
//
// The considerations for categorizing contexts without a surface are:
//  1. These contexts do not track {visibility,last_used_time}, so cannot
//     sort them directly.
//  2. These contexts may be used by, and thus affect, other contexts, and so
//     cannot be less visible than any affected context.
//  3. Contexts belong to share groups within which resources can be shared.
//
// As such, the rule for categorizing contexts without a surface is:
//  1. Find the most visible context-with-a-surface within each
//     context-without-a-surface's share group, and inherit its visibilty.
void GpuMemoryManager::Manage() {
  manage_immediate_scheduled_ = false;
  delayed_manage_callback_.Cancel();

  // Create stub lists by separating out the two types received from client
  std::vector<GpuCommandBufferStubBase*> stubs_with_surface;
  std::vector<GpuCommandBufferStubBase*> stubs_without_surface;
  {
    std::vector<GpuCommandBufferStubBase*> stubs;
    client_->AppendAllCommandBufferStubs(stubs);

    for (std::vector<GpuCommandBufferStubBase*>::iterator it = stubs.begin();
        it != stubs.end(); ++it) {
      GpuCommandBufferStubBase* stub = *it;
      if (!stub->client_has_memory_allocation_changed_callback())
        continue;
      if (stub->has_surface_state())
        stubs_with_surface.push_back(stub);
      else
        stubs_without_surface.push_back(stub);
    }
  }

  // Sort stubs with surface into {visibility,last_used_time} order using
  // custom comparator
  std::sort(stubs_with_surface.begin(),
            stubs_with_surface.end(),
            StubWithSurfaceComparator());
  DCHECK(std::unique(stubs_with_surface.begin(), stubs_with_surface.end()) ==
         stubs_with_surface.end());

  // Separate stubs into memory allocation sets.
  std::vector<GpuCommandBufferStubBase*> stubs_with_surface_foreground,
                                         stubs_with_surface_background,
                                         stubs_with_surface_hibernated,
                                         stubs_without_surface_foreground,
                                         stubs_without_surface_background,
                                         stubs_without_surface_hibernated;

  for (size_t i = 0; i < stubs_with_surface.size(); ++i) {
    GpuCommandBufferStubBase* stub = stubs_with_surface[i];
    DCHECK(stub->has_surface_state());
    if (stub->surface_state().visible)
      stubs_with_surface_foreground.push_back(stub);
    else if (i < max_surfaces_with_frontbuffer_soft_limit_)
      stubs_with_surface_background.push_back(stub);
    else
      stubs_with_surface_hibernated.push_back(stub);
  }
  for (std::vector<GpuCommandBufferStubBase*>::const_iterator it =
      stubs_without_surface.begin(); it != stubs_without_surface.end(); ++it) {
    GpuCommandBufferStubBase* stub = *it;
    DCHECK(!stub->has_surface_state());

    // Stubs without surfaces have deduced allocation state using the state
    // of surface stubs which are in the same context share group.
    if (IsInSameContextShareGroupAsAnyOf(stub, stubs_with_surface_foreground))
      stubs_without_surface_foreground.push_back(stub);
    else if (IsInSameContextShareGroupAsAnyOf(
        stub, stubs_with_surface_background))
      stubs_without_surface_background.push_back(stub);
    else
      stubs_without_surface_hibernated.push_back(stub);
  }

  size_t bonus_allocation = 0;
#if !defined(OS_ANDROID)
  // Calculate bonus allocation by splitting remainder of global limit equally
  // after giving out the minimum to those that need it.
  size_t num_stubs_need_mem = stubs_with_surface_foreground.size() +
                              stubs_without_surface_foreground.size() +
                              stubs_without_surface_background.size();
  size_t base_allocation_size = GetMinimumTabAllocation() * num_stubs_need_mem;
  if (base_allocation_size < GetAvailableGpuMemory() &&
      !stubs_with_surface_foreground.empty())
    bonus_allocation = (GetAvailableGpuMemory() - base_allocation_size) /
                       stubs_with_surface_foreground.size();
#else
  // On android, calculate bonus allocation based on surface size.
  if (!stubs_with_surface_foreground.empty())
    bonus_allocation = CalculateBonusMemoryAllocationBasedOnSize(
        stubs_with_surface_foreground[0]->GetSurfaceSize());
#endif
  size_t stubs_with_surface_foreground_allocation = GetMinimumTabAllocation() +
                                                    bonus_allocation;
  if (stubs_with_surface_foreground_allocation >= GetMaximumTabAllocation())
    stubs_with_surface_foreground_allocation = GetMaximumTabAllocation();

  stub_memory_stats_for_last_manage_.clear();

  // Now give out allocations to everyone.
  AssignMemoryAllocations(
      &stub_memory_stats_for_last_manage_,
      stubs_with_surface_foreground,
      GpuMemoryAllocation(stubs_with_surface_foreground_allocation,
          GpuMemoryAllocation::kHasFrontbuffer |
          GpuMemoryAllocation::kHasBackbuffer),
      true);

  AssignMemoryAllocations(
      &stub_memory_stats_for_last_manage_,
      stubs_with_surface_background,
      GpuMemoryAllocation(0, GpuMemoryAllocation::kHasFrontbuffer),
      false);

  AssignMemoryAllocations(
      &stub_memory_stats_for_last_manage_,
      stubs_with_surface_hibernated,
      GpuMemoryAllocation(0, GpuMemoryAllocation::kHasNoBuffers),
      false);

  AssignMemoryAllocations(
      &stub_memory_stats_for_last_manage_,
      stubs_without_surface_foreground,
      GpuMemoryAllocation(GetMinimumTabAllocation(),
          GpuMemoryAllocation::kHasNoBuffers),
      true);

  AssignMemoryAllocations(
      &stub_memory_stats_for_last_manage_,
      stubs_without_surface_background,
      GpuMemoryAllocation(GetMinimumTabAllocation(),
          GpuMemoryAllocation::kHasNoBuffers),
      false);

  AssignMemoryAllocations(
      &stub_memory_stats_for_last_manage_,
      stubs_without_surface_hibernated,
      GpuMemoryAllocation(0, GpuMemoryAllocation::kHasNoBuffers),
      false);
}

#endif