chromium/gpu/ipc/service/image_decode_accelerator_stub_unittest.cc

// Copyright 2018 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/ipc/service/image_decode_accelerator_stub.h"

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

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/atomicops.h"
#include "base/check_op.h"
#include "base/containers/queue.h"
#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "base/trace_event/memory_allocator_dump.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_request_args.h"
#include "base/trace_event/process_memory_dump.h"
#include "cc/paint/image_transfer_cache_entry.h"
#include "cc/paint/transfer_cache_entry.h"
#include "gpu/command_buffer/common/buffer.h"
#include "gpu/command_buffer/common/capabilities.h"
#include "gpu/command_buffer/common/constants.h"
#include "gpu/command_buffer/common/context_creation_attribs.h"
#include "gpu/command_buffer/common/context_result.h"
#include "gpu/command_buffer/common/discardable_handle.h"
#include "gpu/command_buffer/common/scheduling_priority.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/common/sync_token.h"
#include "gpu/command_buffer/service/context_group.h"
#include "gpu/command_buffer/service/decoder_context.h"
#include "gpu/command_buffer/service/mocks.h"
#include "gpu/command_buffer/service/scheduler.h"
#include "gpu/command_buffer/service/sequence_id.h"
#include "gpu/command_buffer/service/service_transfer_cache.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/shared_image_backing_factory.h"
#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
#include "gpu/command_buffer/service/shared_image/test_image_backing.h"
#include "gpu/command_buffer/service/sync_point_manager.h"
#include "gpu/command_buffer/service/transfer_buffer_manager.h"
#include "gpu/config/gpu_driver_bug_workarounds.h"
#include "gpu/config/gpu_finch_features.h"
#include "gpu/config/gpu_info.h"
#include "gpu/ipc/common/command_buffer_id.h"
#include "gpu/ipc/common/gpu_channel.mojom.h"
#include "gpu/ipc/common/surface_handle.h"
#include "gpu/ipc/service/command_buffer_stub.h"
#include "gpu/ipc/service/gpu_channel.h"
#include "gpu/ipc/service/gpu_channel_manager.h"
#include "gpu/ipc/service/gpu_channel_test_common.h"
#include "gpu/ipc/service/image_decode_accelerator_worker.h"
#include "skia/ext/skia_memory_dump_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkSize.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/gpu_memory_buffer.h"
#include "ui/gl/gl_bindings.h"
#include "url/gurl.h"

using testing::InSequence;
using testing::Mock;
using testing::NiceMock;
using testing::StrictMock;

namespace gpu {
class MemoryTracker;

namespace {

// The size of a decoded buffer to report for a successful decode.
constexpr size_t kDecodedBufferByteSize = 123u;

// The byte size Skia is expected to report for a buffer object.
constexpr uint64_t kSkiaBufferObjectSize = 32768;

struct ExpectedCacheEntry {
  uint32_t id = 0u;
  SkISize dimensions;
};

std::unique_ptr<MemoryTracker> CreateMockMemoryTracker() {
  return std::make_unique<NiceMock<gles2::MockMemoryTracker>>();
}

scoped_refptr<Buffer> MakeBufferForTesting() {
  return MakeMemoryBuffer(sizeof(base::subtle::Atomic32));
}

uint64_t GetMemoryDumpByteSize(
    const base::trace_event::MemoryAllocatorDump* dump,
    const std::string& entry_name) {
  DCHECK(dump);
  auto entry_it =
      base::ranges::find(dump->entries(), entry_name,
                         &base::trace_event::MemoryAllocatorDump::Entry::name);
  if (entry_it != dump->entries().cend()) {
    EXPECT_EQ(std::string(base::trace_event::MemoryAllocatorDump::kUnitsBytes),
              entry_it->units);
    EXPECT_EQ(base::trace_event::MemoryAllocatorDump::Entry::EntryType::kUint64,
              entry_it->entry_type);
    return entry_it->value_uint64;
  }
  EXPECT_TRUE(false);
  return 0u;
}

base::CheckedNumeric<uint64_t> GetExpectedTotalMippedSizeForPlanarImage(
    const cc::ServiceImageTransferCacheEntry* decode_entry) {
  base::CheckedNumeric<uint64_t> safe_total_image_size = 0u;
  for (const auto& plane_image : decode_entry->plane_images()) {
    safe_total_image_size +=
        base::strict_cast<uint64_t>(plane_image->textureSize());
  }
  return safe_total_image_size;
}

class TestSharedImageBackingFactory : public SharedImageBackingFactory {
 public:
  TestSharedImageBackingFactory() : SharedImageBackingFactory(kUsageAll) {}

  // SharedImageBackingFactory implementation.
  std::unique_ptr<SharedImageBacking> CreateSharedImage(
      const Mailbox& mailbox,
      viz::SharedImageFormat format,
      SurfaceHandle surface_handle,
      const gfx::Size& size,
      const gfx::ColorSpace& color_space,
      GrSurfaceOrigin surface_origin,
      SkAlphaType alpha_type,
      SharedImageUsageSet usage,
      std::string debug_label,
      bool is_thread_safe) override {
    NOTREACHED_IN_MIGRATION();
    return nullptr;
  }
  std::unique_ptr<SharedImageBacking> CreateSharedImage(
      const Mailbox& mailbox,
      viz::SharedImageFormat format,
      const gfx::Size& size,
      const gfx::ColorSpace& color_space,
      GrSurfaceOrigin surface_origin,
      SkAlphaType alpha_type,
      SharedImageUsageSet usage,
      std::string debug_label,
      bool is_thread_safe,
      base::span<const uint8_t> pixel_data) override {
    NOTREACHED_IN_MIGRATION();
    return nullptr;
  }
  std::unique_ptr<SharedImageBacking> CreateSharedImage(
      const Mailbox& mailbox,
      viz::SharedImageFormat format,
      const gfx::Size& size,
      const gfx::ColorSpace& color_space,
      GrSurfaceOrigin surface_origin,
      SkAlphaType alpha_type,
      SharedImageUsageSet usage,
      std::string debug_label,
      gfx::GpuMemoryBufferHandle handle) override {
    auto test_image_backing = std::make_unique<TestImageBacking>(
        mailbox, format, size, color_space, surface_origin, alpha_type, usage,
        0);

    // If the backing is not cleared, SkiaImageRepresentation errors out
    // when trying to create the scoped read access.
    test_image_backing->SetCleared();

    return std::move(test_image_backing);
  }
  bool IsSupported(SharedImageUsageSet usage,
                   viz::SharedImageFormat format,
                   const gfx::Size& size,
                   bool thread_safe,
                   gfx::GpuMemoryBufferType gmb_type,
                   GrContextType gr_context_type,
                   base::span<const uint8_t> pixel_data) override {
    return true;
  }
  SharedImageBackingType GetBackingType() override {
    return SharedImageBackingType::kTest;
  }
};

}  // namespace

// This mock allows individual tests to decide asynchronously when to finish a
// decode by using the FinishOneDecode() method.
class MockImageDecodeAcceleratorWorker : public ImageDecodeAcceleratorWorker {
 public:
  MockImageDecodeAcceleratorWorker(gfx::BufferFormat format_for_decodes)
      : format_for_decodes_(format_for_decodes) {}

  MockImageDecodeAcceleratorWorker(const MockImageDecodeAcceleratorWorker&) =
      delete;
  MockImageDecodeAcceleratorWorker& operator=(
      const MockImageDecodeAcceleratorWorker&) = delete;

  void Decode(std::vector<uint8_t> encoded_data,
              const gfx::Size& output_size,
              CompletedDecodeCB decode_cb) override {
    pending_decodes_.push(PendingDecode{output_size, std::move(decode_cb)});
    DoDecode(output_size);
  }

  void FinishOneDecode(bool success) {
    if (pending_decodes_.empty())
      return;
    PendingDecode next_decode = std::move(pending_decodes_.front());
    pending_decodes_.pop();
    if (success) {
      // We give out a dummy GpuMemoryBufferHandle as the result: since we mock
      // the SharedImage backing in these tests, the only requirement is that
      // the NativePixmapHandle has the right number of planes.
      auto decode_result = std::make_unique<DecodeResult>();
      decode_result->handle.type = gfx::GpuMemoryBufferType::NATIVE_PIXMAP;
      for (size_t plane = 0; plane < gfx::NumberOfPlanesForLinearBufferFormat(
                                         format_for_decodes_);
           plane++) {
        decode_result->handle.native_pixmap_handle.planes.emplace_back(
            0 /* stride */, 0 /* offset */, 0 /* size */, base::ScopedFD());
      }
      decode_result->visible_size = next_decode.output_size;
      decode_result->buffer_format = format_for_decodes_;
      decode_result->buffer_byte_size = kDecodedBufferByteSize;
      std::move(next_decode.decode_cb).Run(std::move(decode_result));
    } else {
      std::move(next_decode.decode_cb).Run(nullptr);
    }
  }

  MOCK_METHOD1(DoDecode, void(const gfx::Size&));
  MOCK_METHOD0(GetSupportedProfiles,
               std::vector<ImageDecodeAcceleratorSupportedProfile>());

 private:
  struct PendingDecode {
    gfx::Size output_size;
    CompletedDecodeCB decode_cb;
  };

  const gfx::BufferFormat format_for_decodes_;
  base::queue<PendingDecode> pending_decodes_;
};

const int kChannelId = 1;

const int32_t kCommandBufferRouteId =
    static_cast<int32_t>(GpuChannelReservedRoutes::kMaxValue) + 1;

// Test fixture: the general strategy for testing is to have a GPU channel test
// infrastructure (provided by GpuChannelTestCommon), ask the channel to handle
// decode requests, and expect sync token releases, invocations to the
// ImageDecodeAcceleratorWorker functionality, and transfer cache entry
// creation.
class ImageDecodeAcceleratorStubTest
    : public GpuChannelTestCommon,
      public ::testing::WithParamInterface<gfx::BufferFormat> {
 public:
  ImageDecodeAcceleratorStubTest()
      : GpuChannelTestCommon(false /* use_stub_bindings */),
        image_decode_accelerator_worker_(GetParam()) {}

  ImageDecodeAcceleratorStubTest(const ImageDecodeAcceleratorStubTest&) =
      delete;
  ImageDecodeAcceleratorStubTest& operator=(
      const ImageDecodeAcceleratorStubTest&) = delete;

  ~ImageDecodeAcceleratorStubTest() override = default;

  SyncPointManager* sync_point_manager() const {
    return channel_manager()->sync_point_manager();
  }

  ServiceTransferCache* GetServiceTransferCache() {
    ContextResult context_result;
    scoped_refptr<SharedContextState> shared_context_state =
        channel_manager()->GetSharedContextState(&context_result);
    if (context_result != ContextResult::kSuccess || !shared_context_state) {
      return nullptr;
    }
    return shared_context_state->transfer_cache();
  }

  int GetRasterDecoderId() {
    GpuChannel* channel = channel_manager()->LookupChannel(kChannelId);
    DCHECK(channel);
    CommandBufferStub* command_buffer =
        channel->LookupCommandBuffer(kCommandBufferRouteId);
    if (!command_buffer || !command_buffer->decoder_context())
      return -1;
    return command_buffer->decoder_context()->GetRasterDecoderId();
  }

  void SetUp() override {
    GpuChannelTestCommon::SetUp();

    // TODO(andrescj): get rid of the |feature_list_| when the feature is
    // enabled by default.
    feature_list_.InitAndEnableFeature(
        features::kVaapiJpegImageDecodeAcceleration);
    channel_manager()->SetImageDecodeAcceleratorWorkerForTesting(
        &image_decode_accelerator_worker_);

    // Register Skia's memory dump provider so that we can inspect its reported
    // memory usage.
    base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
        skia::SkiaMemoryDumpProvider::GetInstance(), "Skia", nullptr);

    // Initialize the GrContext so that texture uploading works.
    ContextResult context_result;
    scoped_refptr<SharedContextState> shared_context_state =
        channel_manager()->GetSharedContextState(&context_result);
    ASSERT_EQ(ContextResult::kSuccess, context_result);
    ASSERT_TRUE(shared_context_state);
    shared_context_state->InitializeSkia(GpuPreferences(),
                                         GpuDriverBugWorkarounds());

    GpuChannel* channel = CreateChannel(kChannelId, false /* is_gpu_host */);
    ASSERT_TRUE(channel);
    channel->shared_image_stub()
        ->factory()
        ->RegisterSharedImageBackingFactoryForTesting(&test_factory_);

    // Create a raster command buffer so that the ImageDecodeAcceleratorStub can
    // have access to a TransferBufferManager. Note that we mock the
    // MemoryTracker because GpuCommandBufferMemoryTracker uses a timer that
    // would make RunTasksUntilIdle() run forever.
    CommandBufferStub::SetMemoryTrackerFactoryForTesting(
        base::BindRepeating(&CreateMockMemoryTracker));
    auto init_params = mojom::CreateCommandBufferParams::New();
    init_params->share_group_id = MSG_ROUTING_NONE;
    init_params->stream_id = 0;
    init_params->stream_priority = SchedulingPriority::kNormal;
    init_params->attribs = ContextCreationAttribs();
    init_params->attribs.enable_gles2_interface = false;
    init_params->attribs.enable_raster_interface = true;
    init_params->attribs.bind_generates_resource = false;
    init_params->active_url = GURL();
    ContextResult result = ContextResult::kTransientFailure;
    Capabilities capabilities;
    GLCapabilities gl_capabilities;
    CreateCommandBuffer(*channel, std::move(init_params), kCommandBufferRouteId,
                        GetSharedMemoryRegion(), &result, &capabilities,
                        &gl_capabilities);
    ASSERT_EQ(ContextResult::kSuccess, result);
    CommandBufferStub* command_buffer =
        channel->LookupCommandBuffer(kCommandBufferRouteId);
    ASSERT_TRUE(command_buffer);

    // Make sure there are no pending tasks before starting the test. Command
    // buffer creation creates some throw-away Mojo endpoints that will post
    // some tasks.
    base::RunLoop().RunUntilIdle();
    ASSERT_TRUE(task_environment().MainThreadIsIdle());
  }

  void TearDown() override {
    // Make sure the channel is destroyed before the
    // |image_decode_accelerator_worker_| is destroyed.
    channel_manager()->DestroyAllChannels();
  }

  // Intended to run as a task in the GPU scheduler (in the raster sequence):
  // registers |buffer| in the TransferBufferManager and releases the sync token
  // corresponding to |handle_release_count|.
  void RegisterDiscardableHandleBuffer(int32_t shm_id,
                                       scoped_refptr<Buffer> buffer,
                                       uint64_t handle_release_count) {
    GpuChannel* channel = channel_manager()->LookupChannel(kChannelId);
    DCHECK(channel);
    CommandBufferStub* command_buffer =
        channel->LookupCommandBuffer(kCommandBufferRouteId);
    CHECK(command_buffer);
    command_buffer->RegisterTransferBufferForTest(shm_id, std::move(buffer));
    command_buffer->OnFenceSyncRelease(handle_release_count);
  }

  // Creates a discardable handle and schedules a task in the GPU scheduler (in
  // the raster sequence) to register the handle's buffer and release the sync
  // token corresponding to |handle_release_count| (see the
  // RegisterDiscardableHandleBuffer() method). Returns an invalid handle if the
  // command buffer doesn't exist.
  ClientDiscardableHandle CreateDiscardableHandle(
      uint64_t handle_release_count) {
    GpuChannel* channel = channel_manager()->LookupChannel(kChannelId);
    DCHECK(channel);
    CommandBufferStub* command_buffer =
        channel->LookupCommandBuffer(kCommandBufferRouteId);
    if (!command_buffer)
      return ClientDiscardableHandle();
    ClientDiscardableHandle handle(MakeBufferForTesting() /* buffer */,
                                   0u /* byte_offset */,
                                   GetNextBufferId() /* shm_id */);
    scheduler()->ScheduleTask(Scheduler::Task(
        command_buffer->sequence_id(),
        base::BindOnce(
            &ImageDecodeAcceleratorStubTest::RegisterDiscardableHandleBuffer,
            weak_ptr_factory_.GetWeakPtr(), handle.shm_id(),
            handle.BufferForTesting(), handle_release_count) /* closure */,
        std::vector<SyncToken>() /* sync_token_fences */));
    return handle;
  }

  // Sends a decode request IPC and returns a sync token that is expected to be
  // released upon the completion of the decode. The caller is responsible for
  // keeping track of the release count for the decode sync token
  // (|decode_release_count|), the transfer cache entry ID
  // (|transfer_cache_entry_id|), and the release count of the sync token that
  // is signaled after the discardable handle's buffer has been registered in
  // the TransferBufferManager. If the discardable handle can't be created, this
  // function returns an empty sync token.
  SyncToken SendDecodeRequest(const gfx::Size& output_size,
                              uint64_t decode_release_count,
                              uint32_t transfer_cache_entry_id,
                              uint64_t handle_release_count,
                              bool needs_mips = false) {
    GpuChannel* channel = channel_manager()->LookupChannel(kChannelId);
    DCHECK(channel);

    // Create the decode sync token for the decode request so that we can test
    // that it's actually released.
    SyncToken decode_sync_token(
        CommandBufferNamespace::GPU_IO,
        CommandBufferIdFromChannelAndRoute(
            kChannelId, static_cast<int32_t>(
                            GpuChannelReservedRoutes::kImageDecodeAccelerator)),
        decode_release_count);

    // Create a discardable handle and schedule its buffer's registration.
    ClientDiscardableHandle handle =
        CreateDiscardableHandle(handle_release_count);
    if (!handle.IsValid())
      return SyncToken();

    // Send the IPC decode request.
    auto decode_params = mojom::ScheduleImageDecodeParams::New();
    decode_params->output_size = output_size;
    decode_params->raster_decoder_route_id = kCommandBufferRouteId;
    decode_params->transfer_cache_entry_id = transfer_cache_entry_id;
    decode_params->discardable_handle_shm_id = handle.shm_id();
    decode_params->discardable_handle_shm_offset = handle.byte_offset();
    decode_params->discardable_handle_release_count = handle_release_count;
    decode_params->needs_mips = needs_mips;
    channel->GetGpuChannelForTesting().ScheduleImageDecode(
        std::move(decode_params), decode_sync_token.release_count());
    return decode_sync_token;
  }

  void RunTasksUntilIdle() { task_environment().RunUntilIdle(); }

  void CheckTransferCacheEntries(
      const std::vector<ExpectedCacheEntry>& expected_entries) {
    ServiceTransferCache* transfer_cache = GetServiceTransferCache();
    ASSERT_TRUE(transfer_cache);

    // First, check the number of entries and early out if 0 entries are
    // expected.
    const size_t num_actual_cache_entries =
        transfer_cache->entries_count_for_testing();
    ASSERT_EQ(expected_entries.size(), num_actual_cache_entries);
    if (expected_entries.empty())
      return;

    // Then, check the dimensions of the entries to make sure they are as
    // expected.
    int raster_decoder_id = GetRasterDecoderId();
    ASSERT_GE(raster_decoder_id, 0);
    for (size_t i = 0; i < num_actual_cache_entries; i++) {
      auto* decode_entry = static_cast<cc::ServiceImageTransferCacheEntry*>(
          transfer_cache->GetEntry(ServiceTransferCache::EntryKey(
              raster_decoder_id, cc::TransferCacheEntryType::kImage,
              expected_entries[i].id)));
      ASSERT_TRUE(decode_entry);
      ASSERT_EQ(gfx::NumberOfPlanesForLinearBufferFormat(GetParam()),
                decode_entry->plane_images().size());
      for (size_t plane = 0; plane < decode_entry->plane_images().size();
           plane++) {
        ASSERT_TRUE(decode_entry->plane_images()[plane]);
        EXPECT_TRUE(decode_entry->plane_images()[plane]->isTextureBacked());
      }
      ASSERT_TRUE(decode_entry->image());
      EXPECT_EQ(expected_entries[i].dimensions.width(),
                decode_entry->image()->dimensions().width());
      EXPECT_EQ(expected_entries[i].dimensions.height(),
                decode_entry->image()->dimensions().height());
    }
  }

  cc::ServiceImageTransferCacheEntry* RunSimpleDecode(bool needs_mips) {
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(100, 100)))
        .Times(1);
    const SyncToken decode_sync_token = SendDecodeRequest(
        gfx::Size(100, 100) /* output_size */, 1u /* decode_release_count */,
        1u /* transfer_cache_entry_id */, 1u /* handle_release_count */,
        needs_mips);
    if (!decode_sync_token.HasData())
      return nullptr;
    image_decode_accelerator_worker_.FinishOneDecode(true);
    RunTasksUntilIdle();
    if (!sync_point_manager()->IsSyncTokenReleased(decode_sync_token))
      return nullptr;
    ServiceTransferCache* transfer_cache = GetServiceTransferCache();
    if (!transfer_cache)
      return nullptr;
    const int raster_decoder_id = GetRasterDecoderId();
    if (raster_decoder_id < 0)
      return nullptr;
    auto* decode_entry = static_cast<cc::ServiceImageTransferCacheEntry*>(
        transfer_cache->GetEntry(ServiceTransferCache::EntryKey(
            raster_decoder_id, cc::TransferCacheEntryType::kImage,
            1u /* entry_id */)));
    if (!Mock::VerifyAndClear(&image_decode_accelerator_worker_))
      return nullptr;
    return decode_entry;
  }

  // Requests a |detail_level| process memory dump and checks:
  // - The total memory reported by the transfer cache.
  // - The total GPU resources memory reported by Skia. Skia memory allocator
  //   dumps that share a global allocator dump with a transfer cache entry are
  //   not counted (and we check that the Skia dump importance is less than the
  //   corresponding transfer cache dump in that case).
  // - The average transfer cache image entry byte size (this is only checked
  //   for background-level memory dumps).
  void ExpectProcessMemoryDump(
      base::trace_event::MemoryDumpLevelOfDetail detail_level,
      uint64_t expected_total_transfer_cache_size,
      uint64_t expected_total_skia_gpu_resources_size,
      uint64_t expected_avg_image_size) {
    // Request a process memory dump.
    base::trace_event::MemoryDumpRequestArgs dump_args{};
    dump_args.dump_guid = 1234u;
    dump_args.dump_type =
        base::trace_event::MemoryDumpType::kExplicitlyTriggered;
    dump_args.level_of_detail = detail_level;
    dump_args.determinism = base::trace_event::MemoryDumpDeterminism::kForceGc;
    std::unique_ptr<base::trace_event::ProcessMemoryDump> dump;
    base::RunLoop run_loop;
    base::trace_event::MemoryDumpManager::GetInstance()->CreateProcessDump(
        dump_args,
        base::BindOnce(
            [](std::unique_ptr<base::trace_event::ProcessMemoryDump>* out_pmd,
               base::RepeatingClosure quit_closure, bool success,
               uint64_t dump_guid,
               std::unique_ptr<base::trace_event::ProcessMemoryDump> pmd) {
              if (success)
                *out_pmd = std::move(pmd);
              quit_closure.Run();
            },
            &dump, run_loop.QuitClosure()));
    RunTasksUntilIdle();
    run_loop.Run();

    // Check the transfer cache dumps are as expected.
    ServiceTransferCache* cache = GetServiceTransferCache();
    ASSERT_TRUE(cache);
    // This map will later allow us to answer the following question easily:
    // which transfer cache entry memory dump points to a given shared global
    // allocator dump?
    std::map<
        base::trace_event::MemoryAllocatorDumpGuid,
        std::pair<base::trace_event::ProcessMemoryDump::MemoryAllocatorDumpEdge,
                  base::trace_event::MemoryAllocatorDump*>>
        shared_dump_to_transfer_cache_entry_dump;
    std::string transfer_cache_dump_name =
        base::StringPrintf("gpu/transfer_cache/cache_0x%" PRIXPTR,
                           reinterpret_cast<uintptr_t>(cache));
    if (detail_level ==
        base::trace_event::MemoryDumpLevelOfDetail::kBackground) {
      auto transfer_cache_dump_it =
          dump->allocator_dumps().find(transfer_cache_dump_name);
      ASSERT_NE(dump->allocator_dumps().end(), transfer_cache_dump_it);
      EXPECT_EQ(expected_total_transfer_cache_size,
                GetMemoryDumpByteSize(
                    transfer_cache_dump_it->second.get(),
                    base::trace_event::MemoryAllocatorDump::kNameSize));

      std::string avg_image_size_dump_name =
          transfer_cache_dump_name + "/avg_image_size";
      auto avg_image_size_dump_it =
          dump->allocator_dumps().find(avg_image_size_dump_name);
      ASSERT_NE(dump->allocator_dumps().end(), avg_image_size_dump_it);
      EXPECT_EQ(expected_avg_image_size,
                GetMemoryDumpByteSize(avg_image_size_dump_it->second.get(),
                                      "average_size"));
    } else {
      DCHECK_EQ(base::trace_event::MemoryDumpLevelOfDetail::kDetailed,
                detail_level);
      base::CheckedNumeric<uint64_t> safe_actual_transfer_cache_total_size(0u);
      std::string entry_dump_prefix =
          transfer_cache_dump_name + "/gpu/entry_0x";
      for (const auto& allocator_dump : dump->allocator_dumps()) {
        if (base::StartsWith(allocator_dump.first, entry_dump_prefix,
                             base::CompareCase::SENSITIVE)) {
          ASSERT_TRUE(allocator_dump.second);
          safe_actual_transfer_cache_total_size += GetMemoryDumpByteSize(
              allocator_dump.second.get(),
              base::trace_event::MemoryAllocatorDump::kNameSize);

          // If the dump name for this entry does not end in /dma_buf (i.e., we
          // haven't requested mipmaps from Skia), the allocator dump for this
          // cache entry should point to a shared global allocator dump (i.e.,
          // shared with Skia). Let's save this association in
          // |shared_dump_to_transfer_cache_entry_dump| for later.
          ASSERT_FALSE(allocator_dump.second->guid().empty());
          auto edge_it =
              dump->allocator_dumps_edges().find(allocator_dump.second->guid());
          ASSERT_EQ(base::EndsWith(allocator_dump.first, "/dma_buf",
                                   base::CompareCase::SENSITIVE),
                    dump->allocator_dumps_edges().end() == edge_it);
          if (edge_it != dump->allocator_dumps_edges().end()) {
            ASSERT_FALSE(edge_it->second.target.empty());
            ASSERT_EQ(shared_dump_to_transfer_cache_entry_dump.end(),
                      shared_dump_to_transfer_cache_entry_dump.find(
                          edge_it->second.target));
            shared_dump_to_transfer_cache_entry_dump[edge_it->second.target] =
                std::make_pair(edge_it->second, allocator_dump.second.get());
          }
        }
      }
      ASSERT_TRUE(safe_actual_transfer_cache_total_size.IsValid());
      EXPECT_EQ(expected_total_transfer_cache_size,
                safe_actual_transfer_cache_total_size.ValueOrDie());
    }

    // Check that the Skia dumps are as expected. We won't count Skia dumps that
    // point to a global allocator dump that's shared with a transfer cache
    // dump.
    base::CheckedNumeric<uint64_t> safe_actual_total_skia_gpu_resources_size(
        0u);
    for (const auto& allocator_dump : dump->allocator_dumps()) {
      if (base::StartsWith(allocator_dump.first, "skia/gpu_resources",
                           base::CompareCase::SENSITIVE)) {
        ASSERT_TRUE(allocator_dump.second);
        uint64_t skia_allocator_dump_size = GetMemoryDumpByteSize(
            allocator_dump.second.get(),
            base::trace_event::MemoryAllocatorDump::kNameSize);

        // If this dump points to a global allocator dump that's shared with a
        // transfer cache dump, we won't count it.
        ASSERT_FALSE(allocator_dump.second->guid().empty());
        auto edge_it =
            dump->allocator_dumps_edges().find(allocator_dump.second->guid());
        if (edge_it != dump->allocator_dumps_edges().end()) {
          ASSERT_FALSE(edge_it->second.target.empty());
          auto transfer_cache_dump_it =
              shared_dump_to_transfer_cache_entry_dump.find(
                  edge_it->second.target);
          if (transfer_cache_dump_it !=
              shared_dump_to_transfer_cache_entry_dump.end()) {
            // Not counting the Skia dump is only valid if its importance is
            // less than the transfer cache dump and the values of the dumps are
            // the same.
            EXPECT_EQ(skia_allocator_dump_size,
                      GetMemoryDumpByteSize(
                          transfer_cache_dump_it->second.second,
                          base::trace_event::MemoryAllocatorDump::kNameSize));
            EXPECT_LT(edge_it->second.importance,
                      transfer_cache_dump_it->second.first.importance);
            continue;
          }
        }

        safe_actual_total_skia_gpu_resources_size += skia_allocator_dump_size;
      }
    }
    ASSERT_TRUE(safe_actual_total_skia_gpu_resources_size.IsValid());
    EXPECT_EQ(expected_total_skia_gpu_resources_size,
              safe_actual_total_skia_gpu_resources_size.ValueOrDie());
  }

 protected:
  StrictMock<MockImageDecodeAcceleratorWorker> image_decode_accelerator_worker_;

 private:
  TestSharedImageBackingFactory test_factory_;
  base::test::ScopedFeatureList feature_list_;
  base::WeakPtrFactory<ImageDecodeAcceleratorStubTest> weak_ptr_factory_{this};
};

// Tests the following flow: two decode requests are sent. One of the decodes is
// completed. This should cause one sync token to be released and the scheduler
// sequence to be disabled. Then, the second decode is completed. This should
// cause the other sync token to be released.
TEST_P(ImageDecodeAcceleratorStubTest,
       MultipleDecodesCompletedAfterSequenceIsDisabled) {
  {
    InSequence call_sequence;
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(100, 100)))
        .Times(1);
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(200, 200)))
        .Times(1);
  }
  const SyncToken decode1_sync_token = SendDecodeRequest(
      gfx::Size(100, 100) /* output_size */, 1u /* decode_release_count */,
      1u /* transfer_cache_entry_id */, 1u /* handle_release_count */);
  ASSERT_TRUE(decode1_sync_token.HasData());
  const SyncToken decode2_sync_token = SendDecodeRequest(
      gfx::Size(200, 200) /* output_size */, 2u /* decode_release_count */,
      2u /* transfer_cache_entry_id */, 2u /* handle_release_count */);
  ASSERT_TRUE(decode2_sync_token.HasData());

  // A decode sync token should not be released before a decode is finished.
  RunTasksUntilIdle();
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode1_sync_token));
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode2_sync_token));

  // Only the first decode sync token should be released after the first decode
  // is finished.
  image_decode_accelerator_worker_.FinishOneDecode(true);
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode1_sync_token));
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode2_sync_token));

  // The second decode sync token should be released after the second decode is
  // finished.
  image_decode_accelerator_worker_.FinishOneDecode(true);
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode1_sync_token));
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode2_sync_token));

  // Check that the decoded images are in the transfer cache.
  CheckTransferCacheEntries(
      {{1u, SkISize::Make(100, 100)}, {2u, SkISize::Make(200, 200)}});
}

// Tests the following flow: three decode requests are sent. The first decode
// completes which should cause the scheduler sequence to be enabled. Right
// after that (while the sequence is still enabled), the other two decodes
// complete. At the end, all the sync tokens should be released.
TEST_P(ImageDecodeAcceleratorStubTest,
       MultipleDecodesCompletedWhileSequenceIsEnabled) {
  {
    InSequence call_sequence;
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(100, 100)))
        .Times(1);
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(200, 200)))
        .Times(1);
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(300, 300)))
        .Times(1);
  }
  const SyncToken decode1_sync_token = SendDecodeRequest(
      gfx::Size(100, 100) /* output_size */, 1u /* decode_release_count */,
      1u /* transfer_cache_entry_id */, 1u /* handle_release_count */);
  ASSERT_TRUE(decode1_sync_token.HasData());
  const SyncToken decode2_sync_token = SendDecodeRequest(
      gfx::Size(200, 200) /* output_size */, 2u /* decode_release_count */,
      2u /* transfer_cache_entry_id */, 2u /* handle_release_count */);
  ASSERT_TRUE(decode2_sync_token.HasData());
  const SyncToken decode3_sync_token = SendDecodeRequest(
      gfx::Size(300, 300) /* output_size */, 3u /* decode_release_count */,
      3u /* transfer_cache_entry_id */, 3u /* handle_release_count */);
  ASSERT_TRUE(decode3_sync_token.HasData());

  // A decode sync token should not be released before a decode is finished.
  RunTasksUntilIdle();
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode1_sync_token));
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode2_sync_token));
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode3_sync_token));

  // All decode sync tokens should be released after completing all the decodes.
  image_decode_accelerator_worker_.FinishOneDecode(true);
  image_decode_accelerator_worker_.FinishOneDecode(true);
  image_decode_accelerator_worker_.FinishOneDecode(true);
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode1_sync_token));
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode2_sync_token));
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode3_sync_token));

  // Check that the decoded images are in the transfer cache.
  CheckTransferCacheEntries({{1u, SkISize::Make(100, 100)},
                             {2u, SkISize::Make(200, 200)},
                             {3u, SkISize::Make(300, 300)}});
}

// Tests the following flow: three decode requests are sent. The first decode
// fails, the second succeeds, and the third one fails.
TEST_P(ImageDecodeAcceleratorStubTest, FailedDecodes) {
  {
    InSequence call_sequence;
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(100, 100)))
        .Times(1);
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(200, 200)))
        .Times(1);
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(300, 300)))
        .Times(1);
  }
  const SyncToken decode1_sync_token = SendDecodeRequest(
      gfx::Size(100, 100) /* output_size */, 1u /* decode_release_count */,
      1u /* transfer_cache_entry_id */, 1u /* handle_release_count */);
  ASSERT_TRUE(decode1_sync_token.HasData());
  const SyncToken decode2_sync_token = SendDecodeRequest(
      gfx::Size(200, 200) /* output_size */, 2u /* decode_release_count */,
      2u /* transfer_cache_entry_id */, 2u /* handle_release_count */);
  ASSERT_TRUE(decode2_sync_token.HasData());
  const SyncToken decode3_sync_token = SendDecodeRequest(
      gfx::Size(300, 300) /* output_size */, 3u /* decode_release_count */,
      3u /* transfer_cache_entry_id */, 3u /* handle_release_count */);
  ASSERT_TRUE(decode3_sync_token.HasData());

  // A decode sync token should not be released before a decode is finished.
  RunTasksUntilIdle();
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode1_sync_token));
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode2_sync_token));
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode3_sync_token));

  // All decode sync tokens should be released after completing all the decodes.
  image_decode_accelerator_worker_.FinishOneDecode(false);
  image_decode_accelerator_worker_.FinishOneDecode(true);
  image_decode_accelerator_worker_.FinishOneDecode(false);
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode1_sync_token));
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode2_sync_token));
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode3_sync_token));

  // There should only be one image in the transfer cache (the one that
  // succeeded).
  CheckTransferCacheEntries({{2u, SkISize::Make(200, 200)}});
}

TEST_P(ImageDecodeAcceleratorStubTest, OutOfOrderDecodeSyncTokens) {
  sync_point_manager()->set_suppress_fatal_log_for_testing();
  {
    InSequence call_sequence;
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(100, 100)))
        .Times(1);
    EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(200, 200)))
        .Times(1);
  }
  const SyncToken decode1_sync_token = SendDecodeRequest(
      gfx::Size(100, 100) /* output_size */, 2u /* decode_release_count */,
      1u /* transfer_cache_entry_id */, 1u /* handle_release_count */);
  ASSERT_TRUE(decode1_sync_token.HasData());

  const SyncToken decode2_sync_token = SendDecodeRequest(
      gfx::Size(200, 200) /* output_size */, 1u /* decode_release_count */,
      2u /* transfer_cache_entry_id */, 2u /* handle_release_count */);
  ASSERT_TRUE(decode2_sync_token.HasData());

  // A decode sync token should not be released before a decode is finished.
  RunTasksUntilIdle();
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode1_sync_token));
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode2_sync_token));

  // Since the sync tokens are out of order, releasing the first one should also
  // release the second one.
  image_decode_accelerator_worker_.FinishOneDecode(true);
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode1_sync_token));
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode2_sync_token));

  // We only expect the first image in the transfer cache.
  CheckTransferCacheEntries({{1u, SkISize::Make(100, 100)}});

  // Finishing the second decode should not "unrelease" the first sync token.
  image_decode_accelerator_worker_.FinishOneDecode(true);
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode1_sync_token));
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode2_sync_token));
  CheckTransferCacheEntries(
      {{1u, SkISize::Make(100, 100)}, {2u, SkISize::Make(200, 200)}});
}

TEST_P(ImageDecodeAcceleratorStubTest, ZeroReleaseCountDecodeSyncToken) {
  sync_point_manager()->set_suppress_fatal_log_for_testing();
  EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(100, 100)))
      .Times(1);
  const SyncToken decode_sync_token = SendDecodeRequest(
      gfx::Size(100, 100) /* output_size */, 0u /* decode_release_count */,
      1u /* transfer_cache_entry_id */, 1u /* handle_release_count */);
  ASSERT_TRUE(decode_sync_token.HasData());

  // A zero-release count sync token is always considered released.
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode_sync_token));

  // Even though the release count is not really valid, we can still finish the
  // decode.
  image_decode_accelerator_worker_.FinishOneDecode(true);
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode_sync_token));
  CheckTransferCacheEntries({{1u, SkISize::Make(100, 100)}});
}

TEST_P(ImageDecodeAcceleratorStubTest, ZeroWidthOutputSize) {
  EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(0, 100)))
      .Times(1);
  const SyncToken decode_sync_token = SendDecodeRequest(
      gfx::Size(0, 100) /* output_size */, 1u /* decode_release_count */,
      1u /* transfer_cache_entry_id */, 1u /* handle_release_count */);
  ASSERT_TRUE(decode_sync_token.HasData());

  // A decode sync token should not be released before a decode is finished.
  RunTasksUntilIdle();
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode_sync_token));

  // Even though the output size is not valid, we can still finish the decode.
  // We just shouldn't get any entries in the transfer cache.
  image_decode_accelerator_worker_.FinishOneDecode(true);
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode_sync_token));
  CheckTransferCacheEntries({});
}

TEST_P(ImageDecodeAcceleratorStubTest, ZeroHeightOutputSize) {
  EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(100, 0)))
      .Times(1);
  const SyncToken decode_sync_token = SendDecodeRequest(
      gfx::Size(100, 0) /* output_size */, 1u /* decode_release_count */,
      1u /* transfer_cache_entry_id */, 1u /* handle_release_count */);
  ASSERT_TRUE(decode_sync_token.HasData());

  // A decode sync token should not be released before a decode is finished.
  RunTasksUntilIdle();
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode_sync_token));

  // Even though the output size is not valid, we can still finish the decode.
  // We just shouldn't get any entries in the transfer cache.
  image_decode_accelerator_worker_.FinishOneDecode(true);
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode_sync_token));
  CheckTransferCacheEntries({});
}

// Tests that we wait for a discardable handle's buffer to be registered before
// we attempt to process the corresponding completed decode.
TEST_P(ImageDecodeAcceleratorStubTest, WaitForDiscardableHandleRegistration) {
  EXPECT_CALL(image_decode_accelerator_worker_, DoDecode(gfx::Size(100, 100)))
      .Times(1);

  // First, we disable the raster sequence so that we can control when to
  // register the discardable handle's buffer by re-enabling the sequence.
  GpuChannel* channel = channel_manager()->LookupChannel(kChannelId);
  ASSERT_TRUE(channel);
  const CommandBufferStub* command_buffer =
      channel->LookupCommandBuffer(kCommandBufferRouteId);
  ASSERT_TRUE(command_buffer);
  const SequenceId raster_sequence_id = command_buffer->sequence_id();
  scheduler()->DisableSequence(raster_sequence_id);

  // Now we can send the decode request. This schedules the registration of the
  // discardable handle, but it won't actually be registered until we re-enable
  // the raster sequence later on.
  const SyncToken decode_sync_token = SendDecodeRequest(
      gfx::Size(100, 100) /* output_size */, 1u /* decode_release_count */,
      1u /* transfer_cache_entry_id */, 1u /* handle_release_count */);
  ASSERT_TRUE(decode_sync_token.HasData());

  // A decode sync token should not be released before a decode is finished.
  RunTasksUntilIdle();
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode_sync_token));

  // Even when a decode is finished, the decode sync token shouldn't be released
  // before the discardable handle's buffer is registered.
  image_decode_accelerator_worker_.FinishOneDecode(true);
  RunTasksUntilIdle();
  EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(decode_sync_token));

  // Now let's register the discardable handle's buffer by re-enabling the
  // raster sequence. This should trigger the processing of the completed decode
  // and the subsequent release of the decode sync token.
  scheduler()->EnableSequence(raster_sequence_id);
  RunTasksUntilIdle();
  EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(decode_sync_token));

  // Check that the decoded images are in the transfer cache.
  CheckTransferCacheEntries({{1u, SkISize::Make(100, 100)}});
}

TEST_P(ImageDecodeAcceleratorStubTest, MemoryReportDetailedForUnmippedDecode) {
  cc::ServiceImageTransferCacheEntry* decode_entry =
      RunSimpleDecode(false /* needs_mips */);
  ASSERT_TRUE(decode_entry);
  ExpectProcessMemoryDump(
      base::trace_event::MemoryDumpLevelOfDetail::kDetailed,
      base::strict_cast<uint64_t>(
          kDecodedBufferByteSize) /* expected_total_transfer_cache_size */,
      0u /* expected_total_skia_gpu_resources_size */,
      0u /* expected_avg_image_size */);
}

TEST_P(ImageDecodeAcceleratorStubTest,
       MemoryReportBackgroundForUnmippedDecode) {
  cc::ServiceImageTransferCacheEntry* decode_entry =
      RunSimpleDecode(false /* needs_mips */);
  ASSERT_TRUE(decode_entry);
  ExpectProcessMemoryDump(
      base::trace_event::MemoryDumpLevelOfDetail::kBackground,
      base::strict_cast<uint64_t>(
          kDecodedBufferByteSize) /* expected_total_transfer_cache_size */,
      0u /* expected_total_skia_gpu_resources_size */,
      base::strict_cast<uint64_t>(
          kDecodedBufferByteSize) /* expected_avg_image_size */);
}

TEST_P(ImageDecodeAcceleratorStubTest, MemoryReportDetailedForMippedDecode) {
  cc::ServiceImageTransferCacheEntry* decode_entry =
      RunSimpleDecode(true /* needs_mips */);
  ASSERT_TRUE(decode_entry);
  ASSERT_EQ(gfx::NumberOfPlanesForLinearBufferFormat(GetParam()),
            decode_entry->plane_images().size());
  base::CheckedNumeric<uint64_t> safe_expected_total_transfer_cache_size =
      GetExpectedTotalMippedSizeForPlanarImage(decode_entry);
  ASSERT_TRUE(safe_expected_total_transfer_cache_size.IsValid());
  ExpectProcessMemoryDump(
      base::trace_event::MemoryDumpLevelOfDetail::kDetailed,
      safe_expected_total_transfer_cache_size.ValueOrDie(),
      kSkiaBufferObjectSize /* expected_total_skia_gpu_resources_size */,
      0u /* expected_avg_image_size */);
}

TEST_P(ImageDecodeAcceleratorStubTest, MemoryReportBackgroundForMippedDecode) {
  cc::ServiceImageTransferCacheEntry* decode_entry =
      RunSimpleDecode(true /* needs_mips */);
  ASSERT_TRUE(decode_entry);
  ASSERT_EQ(gfx::NumberOfPlanesForLinearBufferFormat(GetParam()),
            decode_entry->plane_images().size());
  base::CheckedNumeric<uint64_t> safe_expected_total_transfer_cache_size =
      GetExpectedTotalMippedSizeForPlanarImage(decode_entry);
  ASSERT_TRUE(safe_expected_total_transfer_cache_size.IsValid());
  ExpectProcessMemoryDump(
      base::trace_event::MemoryDumpLevelOfDetail::kBackground,
      safe_expected_total_transfer_cache_size.ValueOrDie(),
      kSkiaBufferObjectSize,
      safe_expected_total_transfer_cache_size
          .ValueOrDie() /* expected_avg_image_size */);
}

TEST_P(ImageDecodeAcceleratorStubTest,
       MemoryReportDetailedForDeferredMippedDecode) {
  cc::ServiceImageTransferCacheEntry* decode_entry =
      RunSimpleDecode(false /* needs_mips */);
  ASSERT_TRUE(decode_entry);
  decode_entry->EnsureMips();
  ASSERT_EQ(gfx::NumberOfPlanesForLinearBufferFormat(GetParam()),
            decode_entry->plane_images().size());
  base::CheckedNumeric<uint64_t> safe_expected_total_transfer_cache_size =
      GetExpectedTotalMippedSizeForPlanarImage(decode_entry);
  ASSERT_TRUE(safe_expected_total_transfer_cache_size.IsValid());
  ExpectProcessMemoryDump(
      base::trace_event::MemoryDumpLevelOfDetail::kDetailed,
      safe_expected_total_transfer_cache_size.ValueOrDie(),
      kSkiaBufferObjectSize /* expected_total_skia_gpu_resources_size */,
      0u /* expected_avg_image_size */);
}

TEST_P(ImageDecodeAcceleratorStubTest,
       MemoryReportBackgroundForDeferredMippedDecode) {
  cc::ServiceImageTransferCacheEntry* decode_entry =
      RunSimpleDecode(false /* needs_mips */);
  ASSERT_TRUE(decode_entry);
  decode_entry->EnsureMips();
  ASSERT_EQ(gfx::NumberOfPlanesForLinearBufferFormat(GetParam()),
            decode_entry->plane_images().size());
  // For a deferred mip request, the transfer cache doesn't update its size
  // computation, so it reports memory as if no mips had been generated.
  ExpectProcessMemoryDump(
      base::trace_event::MemoryDumpLevelOfDetail::kBackground,
      base::strict_cast<uint64_t>(
          kDecodedBufferByteSize) /* expected_total_transfer_cache_size */,
      kSkiaBufferObjectSize,
      base::strict_cast<uint64_t>(
          kDecodedBufferByteSize) /* expected_avg_image_size */);
}

// TODO(andrescj): test the deletion of transfer cache entries.

INSTANTIATE_TEST_SUITE_P(
    All,
    ImageDecodeAcceleratorStubTest,
    ::testing::Values(gfx::BufferFormat::YVU_420,
                      gfx::BufferFormat::YUV_420_BIPLANAR));

}  // namespace gpu