chromium/gpu/command_buffer/service/command_buffer_service.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/command_buffer_service.h"

#include <stddef.h>
#include <stdint.h>

#include <limits>
#include <memory>

#include "base/logging.h"
#include "base/memory/page_size.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_provider.h"
#include "base/trace_event/memory_dump_request_args.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "gpu/command_buffer/common/cmd_buffer_common.h"
#include "gpu/command_buffer/common/command_buffer_shared.h"
#include "gpu/command_buffer/service/transfer_buffer_manager.h"
#include "gpu/config/gpu_finch_features.h"

#if BUILDFLAG(IS_MAC)
#include <mach/mach_vm.h>
#include <mach/vm_purgable.h>
#include <mach/vm_statistics.h>

#include "base/no_destructor.h"
#include "base/process/process_metrics.h"
#include "base/trace_event/process_memory_dump.h"
#endif

namespace gpu {

#if BUILDFLAG(IS_MAC)
namespace {
class AppleGpuMemoryDumpProvider
    : public base::trace_event::MemoryDumpProvider {
 public:
  AppleGpuMemoryDumpProvider();
  bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
                    base::trace_event::ProcessMemoryDump* pmd) override;

 private:
  // NoDestructor only.
  ~AppleGpuMemoryDumpProvider() override = default;
};

AppleGpuMemoryDumpProvider::AppleGpuMemoryDumpProvider() {
  base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
      this, "CommandBuffer", nullptr);
}

bool AppleGpuMemoryDumpProvider::OnMemoryDump(
    const base::trace_event::MemoryDumpArgs& args,
    base::trace_event::ProcessMemoryDump* pmd) {
  // Collect IOSurface total memory usage.
  size_t surface_virtual_size = 0;
  size_t surface_resident_size = 0;
  size_t surface_swapped_out_size = 0;
  size_t surface_dirty_size = 0;
  size_t surface_nonpurgeable_size = 0;
  size_t surface_purgeable_size = 0;

  // And IOAccelerator. Per vm_statistics.h in XNU, this is used to
  // "differentiate memory needed by GPU drivers and frameworks from generic
  // IOKit allocations". See xnu-1456.1.26/osfmk/mach/vm_statistics.h.
  size_t accelerator_virtual_size = 0;
  size_t accelerator_resident_size = 0;
  size_t accelerator_swapped_out_size = 0;
  size_t accelerator_dirty_size = 0;
  size_t accelerator_nonpurgeable_size = 0;
  size_t accelerator_purgeable_size = 0;

  task_t task = mach_task_self();
  mach_vm_address_t address = 0;
  mach_vm_size_t size = 0;

  while (true) {
    address += size;

    // GetBasicInfo is faster than querying the extended attributes. Query this
    // first to filter out regions that cannot correspond to IOSurfaces.
    vm_region_basic_info_64 basic_info;
    base::MachVMRegionResult result =
        base::GetBasicInfo(task, &size, &address, &basic_info);
    if (result == base::MachVMRegionResult::Finished) {
      break;
    } else if (result == base::MachVMRegionResult::Error) {
      return false;
    }

    // All IOSurfaces and IOAccelerator allocations seen locally (M1 laptop)
    // have rw-/rw- permissions. More distinctive characteristics require the
    // extended info, which are more expensive to query.
    const vm_prot_t rw = VM_PROT_READ | VM_PROT_WRITE;
    if (basic_info.protection != rw || basic_info.max_protection != rw)
      continue;

    // Candidate, need the extended info to get the user tag, but also the page
    // status breakdown.
    vm_region_extended_info_data_t info;
    mach_port_t object_name;
    mach_msg_type_number_t count;

    count = VM_REGION_EXTENDED_INFO_COUNT;
    kern_return_t ret = mach_vm_region(
        task, &address, &size, VM_REGION_EXTENDED_INFO,
        reinterpret_cast<vm_region_info_t>(&info), &count, &object_name);
    // No regions above the requested address.
    if (ret == KERN_INVALID_ADDRESS)
      break;

    if (ret != KERN_SUCCESS)
      return false;

    if (info.user_tag != VM_MEMORY_IOSURFACE &&
        info.user_tag != VM_MEMORY_IOACCELERATOR) {
      continue;
    }

    int purgeable_state = 0;
    ret = mach_vm_purgable_control(task, address, VM_PURGABLE_GET_STATE,
                                   &purgeable_state);

    purgeable_state = purgeable_state & VM_PURGABLE_STATE_MASK;

    switch (info.user_tag) {
      case VM_MEMORY_IOSURFACE:
        surface_virtual_size += size;
        surface_resident_size += info.pages_resident * base::GetPageSize();
        surface_swapped_out_size +=
            info.pages_swapped_out * base::GetPageSize();
        surface_dirty_size += info.pages_dirtied * base::GetPageSize();
        if (purgeable_state == VM_PURGABLE_VOLATILE ||
            purgeable_state == VM_PURGABLE_EMPTY) {
          surface_purgeable_size += size;
        } else {
          surface_nonpurgeable_size += size;
        }
        break;
      case VM_MEMORY_IOACCELERATOR:
        accelerator_virtual_size += size;
        accelerator_resident_size += info.pages_resident * base::GetPageSize();
        accelerator_swapped_out_size +=
            info.pages_swapped_out * base::GetPageSize();
        accelerator_dirty_size += info.pages_dirtied * base::GetPageSize();
        if (purgeable_state == VM_PURGABLE_VOLATILE ||
            purgeable_state == VM_PURGABLE_EMPTY) {
          accelerator_purgeable_size += size;
        } else {
          accelerator_nonpurgeable_size += size;
        }
        break;
    }
  }

  auto* dump = pmd->CreateAllocatorDump("iosurface");
  dump->AddScalar("virtual_size", "bytes", surface_virtual_size);
  dump->AddScalar("resident_size", "bytes", surface_resident_size);
  dump->AddScalar("swapped_out_size", "bytes", surface_swapped_out_size);
  dump->AddScalar("dirty_size", "bytes", surface_dirty_size);
  dump->AddScalar("size", "bytes", surface_virtual_size);
  // Some IOSurfaces have a non-trivial difference between their mapped size
  // and their "dirty" size, possibly because some of it has been marked
  // purgeable, and has been purged (rather than swapped out). Report resident
  // + swapped, as it is the fraction of memory which is: (a) using actual
  // memory, and (b) counted in private memory footprint.
  //
  // Note: not using "dirty_size", as it doesn't contain the swapped out part.
  dump->AddScalar("resident_swapped", "bytes",
                  surface_resident_size + surface_swapped_out_size);
  dump->AddScalar("nonpurgeable_size", "bytes", surface_nonpurgeable_size);
  dump->AddScalar("purgeable_size", "bytes", surface_purgeable_size);

  // Ditto for IOAccelerator.
  dump = pmd->CreateAllocatorDump("ioaccelerator");
  dump->AddScalar("virtual_size", "bytes", accelerator_virtual_size);
  dump->AddScalar("resident_size", "bytes", accelerator_resident_size);
  dump->AddScalar("swapped_out_size", "bytes", accelerator_swapped_out_size);
  dump->AddScalar("dirty_size", "bytes", accelerator_dirty_size);
  dump->AddScalar("size", "bytes", accelerator_virtual_size);
  dump->AddScalar("resident_swapped", "bytes",
                  accelerator_resident_size + accelerator_swapped_out_size);
  dump->AddScalar("nonpurgeable_size", "bytes", accelerator_nonpurgeable_size);
  dump->AddScalar("purgeable_size", "bytes", accelerator_purgeable_size);

  return true;
}
}  // namespace
#endif

// Context switching leads to a render pass break in ANGLE/Vulkan. The command
// buffer has a 20-command limit before it forces a context switch. This
// experiment tests a 100-command limit.
int GetCommandBufferSliceSize() {}

CommandBufferService::CommandBufferService(CommandBufferServiceClient* client,
                                           MemoryTracker* memory_tracker)
    :{}

CommandBufferService::~CommandBufferService() = default;

void CommandBufferService::UpdateState() {}

void CommandBufferService::Flush(int32_t put_offset,
                                 AsyncAPIInterface* handler) {}

void CommandBufferService::SetGetBuffer(int32_t transfer_buffer_id) {}

void CommandBufferService::SetSharedStateBuffer(
    std::unique_ptr<BufferBacking> shared_state_buffer) {}

CommandBuffer::State CommandBufferService::GetState() {}

void CommandBufferService::SetReleaseCount(uint64_t release_count) {}

scoped_refptr<Buffer> CommandBufferService::CreateTransferBuffer(
    uint32_t size,
    int32_t* id,
    uint32_t alignment) {}

void CommandBufferService::DestroyTransferBuffer(int32_t id) {}

scoped_refptr<Buffer> CommandBufferService::GetTransferBuffer(int32_t id) {}

bool CommandBufferService::RegisterTransferBuffer(
    int32_t id,
    scoped_refptr<Buffer> buffer) {}

scoped_refptr<Buffer> CommandBufferService::CreateTransferBufferWithId(
    uint32_t size,
    int32_t id,
    uint32_t alignment) {}

void CommandBufferService::SetToken(int32_t token) {}

void CommandBufferService::SetParseError(error::Error error) {}

void CommandBufferService::SetContextLostReason(
    error::ContextLostReason reason) {}

bool CommandBufferService::ShouldYield() {}

void CommandBufferService::SetScheduled(bool scheduled) {}

size_t CommandBufferService::GetSharedMemoryBytesAllocated() const {}

}  // namespace gpu