/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/trace_processor/importers/proto/gpu_event_parser.h"

#include <cinttypes>

#include "perfetto/ext/base/utils.h"
#include "perfetto/protozero/field.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"

#include "protos/perfetto/common/gpu_counter_descriptor.pbzero.h"
#include "protos/perfetto/trace/android/graphics_frame_event.pbzero.h"
#include "protos/perfetto/trace/gpu/gpu_counter_event.pbzero.h"
#include "protos/perfetto/trace/gpu/gpu_log.pbzero.h"
#include "protos/perfetto/trace/gpu/gpu_render_stage_event.pbzero.h"
#include "protos/perfetto/trace/gpu/vulkan_api_event.pbzero.h"
#include "protos/perfetto/trace/gpu/vulkan_memory_event.pbzero.h"
#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"

namespace perfetto {
namespace trace_processor {

namespace {

// https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkObjectType.html
typedef enum VkObjectType {
  VK_OBJECT_TYPE_UNKNOWN = 0,
  VK_OBJECT_TYPE_INSTANCE = 1,
  VK_OBJECT_TYPE_PHYSICAL_DEVICE = 2,
  VK_OBJECT_TYPE_DEVICE = 3,
  VK_OBJECT_TYPE_QUEUE = 4,
  VK_OBJECT_TYPE_SEMAPHORE = 5,
  VK_OBJECT_TYPE_COMMAND_BUFFER = 6,
  VK_OBJECT_TYPE_FENCE = 7,
  VK_OBJECT_TYPE_DEVICE_MEMORY = 8,
  VK_OBJECT_TYPE_BUFFER = 9,
  VK_OBJECT_TYPE_IMAGE = 10,
  VK_OBJECT_TYPE_EVENT = 11,
  VK_OBJECT_TYPE_QUERY_POOL = 12,
  VK_OBJECT_TYPE_BUFFER_VIEW = 13,
  VK_OBJECT_TYPE_IMAGE_VIEW = 14,
  VK_OBJECT_TYPE_SHADER_MODULE = 15,
  VK_OBJECT_TYPE_PIPELINE_CACHE = 16,
  VK_OBJECT_TYPE_PIPELINE_LAYOUT = 17,
  VK_OBJECT_TYPE_RENDER_PASS = 18,
  VK_OBJECT_TYPE_PIPELINE = 19,
  VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT = 20,
  VK_OBJECT_TYPE_SAMPLER = 21,
  VK_OBJECT_TYPE_DESCRIPTOR_POOL = 22,
  VK_OBJECT_TYPE_DESCRIPTOR_SET = 23,
  VK_OBJECT_TYPE_FRAMEBUFFER = 24,
  VK_OBJECT_TYPE_COMMAND_POOL = 25,
  VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION = 1000156000,
  VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE = 1000085000,
  VK_OBJECT_TYPE_SURFACE_KHR = 1000000000,
  VK_OBJECT_TYPE_SWAPCHAIN_KHR = 1000001000,
  VK_OBJECT_TYPE_DISPLAY_KHR = 1000002000,
  VK_OBJECT_TYPE_DISPLAY_MODE_KHR = 1000002001,
  VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT = 1000011000,
  VK_OBJECT_TYPE_OBJECT_TABLE_NVX = 1000086000,
  VK_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NVX = 1000086001,
  VK_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT = 1000128000,
  VK_OBJECT_TYPE_VALIDATION_CACHE_EXT = 1000160000,
  VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV = 1000165000,
  VK_OBJECT_TYPE_PERFORMANCE_CONFIGURATION_INTEL = 1000210000,
  VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_KHR =
      VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE,
  VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_KHR =
      VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION,
  VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF
} VkObjectType;

}  // anonymous namespace

GpuEventParser::GpuEventParser(TraceProcessorContext* context)
    : context_(context),
      vulkan_memory_tracker_(context),
      description_id_(context->storage->InternString("description")),
      gpu_render_stage_scope_id_(
          context->storage->InternString("gpu_render_stage")),
      gpu_log_track_name_id_(context_->storage->InternString("GPU Log")),
      gpu_log_scope_id_(context_->storage->InternString("gpu_log")),
      tag_id_(context_->storage->InternString("tag")),
      log_message_id_(context->storage->InternString("message")),
      log_severity_ids_{{context_->storage->InternString("UNSPECIFIED"),
                         context_->storage->InternString("VERBOSE"),
                         context_->storage->InternString("DEBUG"),
                         context_->storage->InternString("INFO"),
                         context_->storage->InternString("WARNING"),
                         context_->storage->InternString("ERROR"),
                         context_->storage->InternString(
                             "UNKNOWN_SEVERITY") /* must be last */}},
      vk_event_track_id_(context->storage->InternString("Vulkan Events")),
      vk_event_scope_id_(context->storage->InternString("vulkan_events")),
      vk_queue_submit_id_(context->storage->InternString("vkQueueSubmit")),
      gpu_mem_total_name_id_(context->storage->InternString("GPU Memory")),
      gpu_mem_total_unit_id_(context->storage->InternString(
          std::to_string(protos::pbzero::GpuCounterDescriptor::BYTE).c_str())),
      gpu_mem_total_global_desc_id_(context->storage->InternString(
          "Total GPU memory used by the entire system")),
      gpu_mem_total_proc_desc_id_(context->storage->InternString(
          "Total GPU memory used by this process")) {}

void GpuEventParser::ParseGpuCounterEvent(int64_t ts, ConstBytes blob) {
  protos::pbzero::GpuCounterEvent::Decoder event(blob.data, blob.size);

  protos::pbzero::GpuCounterDescriptor::Decoder descriptor(
      event.counter_descriptor());
  // Add counter spec to ID map.
  for (auto it = descriptor.specs(); it; ++it) {
    protos::pbzero::GpuCounterDescriptor_GpuCounterSpec::Decoder spec(*it);
    if (!spec.has_counter_id()) {
      PERFETTO_ELOG("Counter spec missing counter id");
      context_->storage->IncrementStats(stats::gpu_counters_invalid_spec);
      continue;
    }
    if (!spec.has_name()) {
      context_->storage->IncrementStats(stats::gpu_counters_invalid_spec);
      continue;
    }

    auto counter_id = spec.counter_id();
    auto name = spec.name();
    if (gpu_counter_track_ids_.find(counter_id) ==
        gpu_counter_track_ids_.end()) {
      auto desc = spec.description();

      StringId unit_id = kNullStringId;
      if (spec.has_numerator_units() || spec.has_denominator_units()) {
        char buffer[1024];
        base::StringWriter unit(buffer, sizeof(buffer));
        for (auto numer = spec.numerator_units(); numer; ++numer) {
          if (unit.pos()) {
            unit.AppendChar(':');
          }
          unit.AppendInt(*numer);
        }
        char sep = '/';
        for (auto denom = spec.denominator_units(); denom; ++denom) {
          unit.AppendChar(sep);
          unit.AppendInt(*denom);
          sep = ':';
        }
        unit_id = context_->storage->InternString(unit.GetStringView());
      }

      auto name_id = context_->storage->InternString(name);
      auto desc_id = context_->storage->InternString(desc);
      auto track_id = context_->track_tracker->CreateGpuCounterTrack(
          name_id, 0 /* gpu_id */, desc_id, unit_id);
      gpu_counter_track_ids_.emplace(counter_id, track_id);
      if (spec.has_groups()) {
        for (auto group = spec.groups(); group; ++group) {
          tables::GpuCounterGroupTable::Row row;
          row.group_id = *group;
          row.track_id = track_id;
          context_->storage->mutable_gpu_counter_group_table()->Insert(row);
        }
      } else {
        tables::GpuCounterGroupTable::Row row;
        row.group_id = protos::pbzero::GpuCounterDescriptor::UNCLASSIFIED;
        row.track_id = track_id;
        context_->storage->mutable_gpu_counter_group_table()->Insert(row);
      }
    } else {
      // Either counter spec was repeated or it came after counter data.
      PERFETTO_ELOG("Duplicated counter spec found. (counter_id=%d, name=%s)",
                    counter_id, name.ToStdString().c_str());
      context_->storage->IncrementStats(stats::gpu_counters_invalid_spec);
    }
  }

  for (auto it = event.counters(); it; ++it) {
    protos::pbzero::GpuCounterEvent_GpuCounter::Decoder counter(*it);
    if (counter.has_counter_id() &&
        (counter.has_int_value() || counter.has_double_value())) {
      auto counter_id = counter.counter_id();
      // Check missing counter_id
      if (gpu_counter_track_ids_.find(counter_id) ==
          gpu_counter_track_ids_.end()) {
        char buffer[64];
        base::StringWriter writer(buffer, sizeof(buffer));
        writer.AppendString("gpu_counter(");
        writer.AppendUnsignedInt(counter_id);
        writer.AppendString(")");
        auto name_id = context_->storage->InternString(writer.GetStringView());

        TrackId track = context_->track_tracker->CreateGpuCounterTrack(
            name_id, 0 /* gpu_id */);
        gpu_counter_track_ids_.emplace(counter_id, track);
        context_->storage->IncrementStats(stats::gpu_counters_missing_spec);
      }
      double counter_val = counter.has_int_value()
                               ? static_cast<double>(counter.int_value())
                               : counter.double_value();
      context_->event_tracker->PushCounter(ts, counter_val,
                                           gpu_counter_track_ids_[counter_id]);
    }
  }
}

const StringId GpuEventParser::GetFullStageName(
    PacketSequenceStateGeneration* sequence_state,
    const protos::pbzero::GpuRenderStageEvent_Decoder& event) const {
  StringId stage_name;
  if (event.has_stage_iid()) {
    auto stage_iid = event.stage_iid();
    auto* decoder = sequence_state->LookupInternedMessage<
        protos::pbzero::InternedData::kGpuSpecificationsFieldNumber,
        protos::pbzero::InternedGpuRenderStageSpecification>(stage_iid);
    if (!decoder) {
      return kNullStringId;
    }
    stage_name = context_->storage->InternString(decoder->name());
  } else {
    uint64_t stage_id = static_cast<uint64_t>(event.stage_id());

    if (stage_id < gpu_render_stage_ids_.size()) {
      stage_name = gpu_render_stage_ids_[static_cast<size_t>(stage_id)].first;
    } else {
      base::StackString<64> name("render stage(%" PRIu64 ")", stage_id);
      stage_name = context_->storage->InternString(name.string_view());
    }
  }
  return stage_name;
}

/**
 * Create a GPU render stage track based
 * GpuRenderStageEvent.Specifications.Description.
 */
void GpuEventParser::InsertGpuTrack(
    const protos::pbzero::
        GpuRenderStageEvent_Specifications_Description_Decoder& hw_queue) {
  StringId track_name = context_->storage->InternString(hw_queue.name());
  if (gpu_hw_queue_counter_ >= gpu_hw_queue_ids_.size() ||
      !gpu_hw_queue_ids_[gpu_hw_queue_counter_].has_value()) {
    tables::GpuTrackTable::Row track(track_name);
    track.scope = gpu_render_stage_scope_id_;
    track.description = context_->storage->InternString(hw_queue.description());
    if (gpu_hw_queue_counter_ >= gpu_hw_queue_ids_.size()) {
      gpu_hw_queue_ids_.emplace_back(
          context_->track_tracker->InternGpuTrack(track));
    } else {
      // If a gpu_render_stage_event is received before the specification, it is
      // possible that the slot has already been allocated.
      gpu_hw_queue_ids_[gpu_hw_queue_counter_] =
          context_->track_tracker->InternGpuTrack(track);
    }
  } else {
    // If a gpu_render_stage_event is received before the specification, a track
    // will be automatically generated.  In that case, update the name and
    // description.
    auto track_id = gpu_hw_queue_ids_[gpu_hw_queue_counter_];
    if (track_id.has_value()) {
      auto row = context_->storage->mutable_gpu_track_table()
                     ->id()
                     .IndexOf(track_id.value())
                     .value();
      context_->storage->mutable_gpu_track_table()->mutable_name()->Set(
          row, track_name);
      context_->storage->mutable_gpu_track_table()->mutable_description()->Set(
          row, context_->storage->InternString(hw_queue.description()));
    } else {
      tables::GpuTrackTable::Row track(track_name);
      track.scope = gpu_render_stage_scope_id_;
      track.description =
          context_->storage->InternString(hw_queue.description());
    }
  }
  ++gpu_hw_queue_counter_;
}
base::Optional<std::string> GpuEventParser::FindDebugName(
    int32_t vk_object_type,
    uint64_t vk_handle) const {
  auto map = debug_marker_names_.find(vk_object_type);
  if (map == debug_marker_names_.end()) {
    return base::nullopt;
  }

  auto name = map->second.find(vk_handle);
  if (name == map->second.end()) {
    return base::nullopt;
  } else {
    return name->second;
  }
}

const StringId GpuEventParser::ParseRenderSubpasses(
    const protos::pbzero::GpuRenderStageEvent_Decoder& event) const {
  if (!event.has_render_subpass_index_mask()) {
    return kNullStringId;
  }
  char buf[256];
  base::StringWriter writer(buf, sizeof(buf));
  uint32_t bit_index = 0;
  bool first = true;
  for (auto it = event.render_subpass_index_mask(); it; ++it) {
    auto subpasses_bits = *it;
    do {
      if ((subpasses_bits & 1) != 0) {
        if (!first) {
          writer.AppendChar(',');
        }
        first = false;
        writer.AppendUnsignedInt(bit_index);
      }
      subpasses_bits >>= 1;
      ++bit_index;
    } while (subpasses_bits != 0);
    // Round up to the next multiple of 64.
    bit_index = ((bit_index - 1) / 64 + 1) * 64;
  }
  return context_->storage->InternString(writer.GetStringView());
}

void GpuEventParser::ParseGpuRenderStageEvent(
    int64_t ts,
    PacketSequenceStateGeneration* sequence_state,
    ConstBytes blob) {
  protos::pbzero::GpuRenderStageEvent::Decoder event(blob.data, blob.size);

  if (event.has_specifications()) {
    protos::pbzero::GpuRenderStageEvent_Specifications::Decoder spec(
        event.specifications().data, event.specifications().size);
    for (auto it = spec.hw_queue(); it; ++it) {
      protos::pbzero::GpuRenderStageEvent_Specifications_Description::Decoder
          hw_queue(*it);
      if (hw_queue.has_name()) {
        InsertGpuTrack(hw_queue);
      }
    }
    for (auto it = spec.stage(); it; ++it) {
      protos::pbzero::GpuRenderStageEvent_Specifications_Description::Decoder
          stage(*it);
      if (stage.has_name()) {
        gpu_render_stage_ids_.emplace_back(std::make_pair(
            context_->storage->InternString(stage.name()),
            context_->storage->InternString(stage.description())));
      }
    }
  }

  auto args_callback = [this, &event,
                        sequence_state](ArgsTracker::BoundInserter* inserter) {
    if (event.has_stage_iid()) {
      size_t stage_iid = static_cast<size_t>(event.stage_iid());
      auto* decoder = sequence_state->LookupInternedMessage<
          protos::pbzero::InternedData::kGpuSpecificationsFieldNumber,
          protos::pbzero::InternedGpuRenderStageSpecification>(stage_iid);
      if (decoder) {
        // TODO: Add RenderStageCategory to gpu_slice table.
        inserter->AddArg(description_id_,
                         Variadic::String(context_->storage->InternString(
                             decoder->description())));
      }
    } else if (event.has_stage_id()) {
      size_t stage_id = static_cast<size_t>(event.stage_id());
      if (stage_id < gpu_render_stage_ids_.size()) {
        auto description = gpu_render_stage_ids_[stage_id].second;
        if (description != kNullStringId) {
          inserter->AddArg(description_id_, Variadic::String(description));
        }
      }
    }
    for (auto it = event.extra_data(); it; ++it) {
      protos::pbzero::GpuRenderStageEvent_ExtraData_Decoder datum(*it);
      StringId name_id = context_->storage->InternString(datum.name());
      StringId value = context_->storage->InternString(
          datum.has_value() ? datum.value() : base::StringView());
      inserter->AddArg(name_id, Variadic::String(value));
    }
  };

  if (event.has_event_id()) {
    TrackId track_id;
    uint64_t hw_queue_id = 0;
    if (event.has_hw_queue_iid()) {
      hw_queue_id = event.hw_queue_iid();
      auto* decoder = sequence_state->LookupInternedMessage<
          protos::pbzero::InternedData::kGpuSpecificationsFieldNumber,
          protos::pbzero::InternedGpuRenderStageSpecification>(hw_queue_id);
      if (!decoder) {
        // Skip
        return;
      }
      // TODO: Add RenderStageCategory to gpu_track table.
      tables::GpuTrackTable::Row track(
          context_->storage->InternString(decoder->name()));
      track.scope = gpu_render_stage_scope_id_;
      track.description =
          context_->storage->InternString(decoder->description());
      track_id = context_->track_tracker->InternGpuTrack(track);
    } else {
      uint32_t id = static_cast<uint32_t>(event.hw_queue_id());
      if (id < gpu_hw_queue_ids_.size() && gpu_hw_queue_ids_[id].has_value()) {
        track_id = gpu_hw_queue_ids_[id].value();
      } else {
        // If the event has a hw_queue_id that does not have a Specification,
        // create a new track for it.
        char buf[128];
        base::StringWriter writer(buf, sizeof(buf));
        writer.AppendLiteral("Unknown GPU Queue ");
        if (id > 1024) {
          // We don't expect this to happen, but just in case there is a corrupt
          // packet, make sure we don't allocate a ridiculous amount of memory.
          id = 1024;
          context_->storage->IncrementStats(
              stats::gpu_render_stage_parser_errors);
          PERFETTO_ELOG("Invalid hw_queue_id.");
        } else {
          writer.AppendInt(event.hw_queue_id());
        }
        StringId track_name =
            context_->storage->InternString(writer.GetStringView());
        tables::GpuTrackTable::Row track(track_name);
        track.scope = gpu_render_stage_scope_id_;
        track_id = context_->track_tracker->InternGpuTrack(track);
        gpu_hw_queue_ids_.resize(id + 1);
        gpu_hw_queue_ids_[id] = track_id;
      }
      hw_queue_id = id;
    }

    auto render_target_name =
        FindDebugName(VK_OBJECT_TYPE_FRAMEBUFFER, event.render_target_handle());
    auto render_target_name_id = render_target_name.has_value()
                                     ? context_->storage->InternString(
                                           render_target_name.value().c_str())
                                     : kNullStringId;
    auto render_pass_name =
        FindDebugName(VK_OBJECT_TYPE_RENDER_PASS, event.render_pass_handle());
    auto render_pass_name_id =
        render_pass_name.has_value()
            ? context_->storage->InternString(render_pass_name.value().c_str())
            : kNullStringId;
    auto command_buffer_name = FindDebugName(VK_OBJECT_TYPE_COMMAND_BUFFER,
                                             event.command_buffer_handle());
    auto command_buffer_name_id = command_buffer_name.has_value()
                                      ? context_->storage->InternString(
                                            command_buffer_name.value().c_str())
                                      : kNullStringId;

    tables::GpuSliceTable::Row row;
    row.ts = ts;
    row.track_id = track_id;
    row.name = GetFullStageName(sequence_state, event);
    row.dur = static_cast<int64_t>(event.duration());
    // TODO: Create table for graphics context and lookup
    // InternedGraphicsContext.
    row.context_id = static_cast<int64_t>(event.context());
    row.render_target = static_cast<int64_t>(event.render_target_handle());
    row.render_target_name = render_target_name_id;
    row.render_pass = static_cast<int64_t>(event.render_pass_handle());
    row.render_pass_name = render_pass_name_id;
    row.render_subpasses = ParseRenderSubpasses(event);
    row.command_buffer = static_cast<int64_t>(event.command_buffer_handle());
    row.command_buffer_name = command_buffer_name_id;
    row.submission_id = event.submission_id();
    row.hw_queue_id = static_cast<int64_t>(hw_queue_id);

    context_->slice_tracker->ScopedTyped(
        context_->storage->mutable_gpu_slice_table(), row, args_callback);
  }
}

void GpuEventParser::UpdateVulkanMemoryAllocationCounters(
    UniquePid upid,
    const VulkanMemoryEvent::Decoder& event) {
  StringId track_str_id = kNullStringId;
  TrackId track = kInvalidTrackId;
  auto allocation_scope = VulkanMemoryEvent::SCOPE_UNSPECIFIED;
  uint32_t memory_type = std::numeric_limits<uint32_t>::max();
  switch (event.source()) {
    case VulkanMemoryEvent::SOURCE_DRIVER:
      allocation_scope = static_cast<VulkanMemoryEvent::AllocationScope>(
          event.allocation_scope());
      if (allocation_scope == VulkanMemoryEvent::SCOPE_UNSPECIFIED)
        return;
      switch (event.operation()) {
        case VulkanMemoryEvent::OP_CREATE:
          vulkan_driver_memory_counters_[allocation_scope] +=
              event.memory_size();
          break;
        case VulkanMemoryEvent::OP_DESTROY:
          vulkan_driver_memory_counters_[allocation_scope] -=
              event.memory_size();
          break;
        case VulkanMemoryEvent::OP_UNSPECIFIED:
        case VulkanMemoryEvent::OP_BIND:
        case VulkanMemoryEvent::OP_DESTROY_BOUND:
        case VulkanMemoryEvent::OP_ANNOTATIONS:
          return;
      }
      track_str_id = vulkan_memory_tracker_.FindAllocationScopeCounterString(
          allocation_scope);
      track = context_->track_tracker->InternProcessCounterTrack(track_str_id,
                                                                 upid);
      context_->event_tracker->PushCounter(
          event.timestamp(),
          static_cast<double>(vulkan_driver_memory_counters_[allocation_scope]),
          track);
      break;

    case VulkanMemoryEvent::SOURCE_DEVICE_MEMORY:
      memory_type = static_cast<uint32_t>(event.memory_type());
      switch (event.operation()) {
        case VulkanMemoryEvent::OP_CREATE:
          vulkan_device_memory_counters_allocate_[memory_type] +=
              event.memory_size();
          break;
        case VulkanMemoryEvent::OP_DESTROY:
          vulkan_device_memory_counters_allocate_[memory_type] -=
              event.memory_size();
          break;
        case VulkanMemoryEvent::OP_UNSPECIFIED:
        case VulkanMemoryEvent::OP_BIND:
        case VulkanMemoryEvent::OP_DESTROY_BOUND:
        case VulkanMemoryEvent::OP_ANNOTATIONS:
          return;
      }
      track_str_id = vulkan_memory_tracker_.FindMemoryTypeCounterString(
          memory_type,
          VulkanMemoryTracker::DeviceCounterType::kAllocationCounter);
      track = context_->track_tracker->InternProcessCounterTrack(track_str_id,
                                                                 upid);
      context_->event_tracker->PushCounter(
          event.timestamp(),
          static_cast<double>(
              vulkan_device_memory_counters_allocate_[memory_type]),
          track);
      break;

    case VulkanMemoryEvent::SOURCE_BUFFER:
    case VulkanMemoryEvent::SOURCE_IMAGE:
      memory_type = static_cast<uint32_t>(event.memory_type());
      switch (event.operation()) {
        case VulkanMemoryEvent::OP_BIND:
          vulkan_device_memory_counters_bind_[memory_type] +=
              event.memory_size();
          break;
        case VulkanMemoryEvent::OP_DESTROY_BOUND:
          vulkan_device_memory_counters_bind_[memory_type] -=
              event.memory_size();
          break;
        case VulkanMemoryEvent::OP_UNSPECIFIED:
        case VulkanMemoryEvent::OP_CREATE:
        case VulkanMemoryEvent::OP_DESTROY:
        case VulkanMemoryEvent::OP_ANNOTATIONS:
          return;
      }
      track_str_id = vulkan_memory_tracker_.FindMemoryTypeCounterString(
          memory_type, VulkanMemoryTracker::DeviceCounterType::kBindCounter);
      track = context_->track_tracker->InternProcessCounterTrack(track_str_id,
                                                                 upid);
      context_->event_tracker->PushCounter(
          event.timestamp(),
          static_cast<double>(vulkan_device_memory_counters_bind_[memory_type]),
          track);
      break;
    case VulkanMemoryEvent::SOURCE_UNSPECIFIED:
    case VulkanMemoryEvent::SOURCE_DEVICE:
      return;
  }
}

void GpuEventParser::ParseVulkanMemoryEvent(
    PacketSequenceStateGeneration* sequence_state,
    ConstBytes blob) {
  using protos::pbzero::InternedData;
  VulkanMemoryEvent::Decoder vulkan_memory_event(blob.data, blob.size);
  tables::VulkanMemoryAllocationsTable::Row vulkan_memory_event_row;
  vulkan_memory_event_row.source = vulkan_memory_tracker_.FindSourceString(
      static_cast<VulkanMemoryEvent::Source>(vulkan_memory_event.source()));
  vulkan_memory_event_row.operation =
      vulkan_memory_tracker_.FindOperationString(
          static_cast<VulkanMemoryEvent::Operation>(
              vulkan_memory_event.operation()));
  vulkan_memory_event_row.timestamp = vulkan_memory_event.timestamp();
  vulkan_memory_event_row.upid =
      context_->process_tracker->GetOrCreateProcess(vulkan_memory_event.pid());
  if (vulkan_memory_event.has_device())
    vulkan_memory_event_row.device =
        static_cast<int64_t>(vulkan_memory_event.device());
  if (vulkan_memory_event.has_device_memory())
    vulkan_memory_event_row.device_memory =
        static_cast<int64_t>(vulkan_memory_event.device_memory());
  if (vulkan_memory_event.has_heap())
    vulkan_memory_event_row.heap = vulkan_memory_event.heap();
  if (vulkan_memory_event.has_memory_type())
    vulkan_memory_event_row.memory_type = vulkan_memory_event.memory_type();
  if (vulkan_memory_event.has_caller_iid()) {
    vulkan_memory_event_row.function_name =
        vulkan_memory_tracker_
            .GetInternedString<InternedData::kFunctionNamesFieldNumber>(
                sequence_state,
                static_cast<uint64_t>(vulkan_memory_event.caller_iid()));
  }
  if (vulkan_memory_event.has_object_handle())
    vulkan_memory_event_row.object_handle =
        static_cast<int64_t>(vulkan_memory_event.object_handle());
  if (vulkan_memory_event.has_memory_address())
    vulkan_memory_event_row.memory_address =
        static_cast<int64_t>(vulkan_memory_event.memory_address());
  if (vulkan_memory_event.has_memory_size())
    vulkan_memory_event_row.memory_size =
        static_cast<int64_t>(vulkan_memory_event.memory_size());
  if (vulkan_memory_event.has_allocation_scope())
    vulkan_memory_event_row.scope =
        vulkan_memory_tracker_.FindAllocationScopeString(
            static_cast<VulkanMemoryEvent::AllocationScope>(
                vulkan_memory_event.allocation_scope()));

  UpdateVulkanMemoryAllocationCounters(vulkan_memory_event_row.upid.value(),
                                       vulkan_memory_event);

  auto* allocs = context_->storage->mutable_vulkan_memory_allocations_table();
  VulkanAllocId id = allocs->Insert(vulkan_memory_event_row).id;

  if (vulkan_memory_event.has_annotations()) {
    auto inserter = context_->args_tracker->AddArgsTo(id);

    for (auto it = vulkan_memory_event.annotations(); it; ++it) {
      protos::pbzero::VulkanMemoryEventAnnotation::Decoder annotation(*it);

      auto key_id =
          vulkan_memory_tracker_
              .GetInternedString<InternedData::kVulkanMemoryKeysFieldNumber>(
                  sequence_state, static_cast<uint64_t>(annotation.key_iid()));

      if (annotation.has_int_value()) {
        inserter.AddArg(key_id, Variadic::Integer(annotation.int_value()));
      } else if (annotation.has_double_value()) {
        inserter.AddArg(key_id, Variadic::Real(annotation.double_value()));
      } else if (annotation.has_string_iid()) {
        auto string_id =
            vulkan_memory_tracker_
                .GetInternedString<InternedData::kVulkanMemoryKeysFieldNumber>(
                    sequence_state,
                    static_cast<uint64_t>(annotation.string_iid()));

        inserter.AddArg(key_id, Variadic::String(string_id));
      }
    }
  }
}

void GpuEventParser::ParseGpuLog(int64_t ts, ConstBytes blob) {
  protos::pbzero::GpuLog::Decoder event(blob.data, blob.size);

  tables::GpuTrackTable::Row track(gpu_log_track_name_id_);
  track.scope = gpu_log_scope_id_;
  TrackId track_id = context_->track_tracker->InternGpuTrack(track);

  auto args_callback = [this, &event](ArgsTracker::BoundInserter* inserter) {
    if (event.has_tag()) {
      inserter->AddArg(
          tag_id_,
          Variadic::String(context_->storage->InternString(event.tag())));
    }
    if (event.has_log_message()) {
      inserter->AddArg(log_message_id_,
                       Variadic::String(context_->storage->InternString(
                           event.log_message())));
    }
  };

  auto severity = static_cast<size_t>(event.severity());
  StringId severity_id =
      severity < log_severity_ids_.size()
          ? log_severity_ids_[static_cast<size_t>(event.severity())]
          : log_severity_ids_[log_severity_ids_.size() - 1];

  tables::GpuSliceTable::Row row;
  row.ts = ts;
  row.track_id = track_id;
  row.name = severity_id;
  row.dur = 0;
  context_->slice_tracker->ScopedTyped(
      context_->storage->mutable_gpu_slice_table(), row, args_callback);
}

void GpuEventParser::ParseVulkanApiEvent(int64_t ts, ConstBytes blob) {
  protos::pbzero::VulkanApiEvent::Decoder vk_event(blob.data, blob.size);
  if (vk_event.has_vk_debug_utils_object_name()) {
    protos::pbzero::VulkanApiEvent_VkDebugUtilsObjectName::Decoder event(
        vk_event.vk_debug_utils_object_name());
    debug_marker_names_[event.object_type()][event.object()] =
        event.object_name().ToStdString();
  }
  if (vk_event.has_vk_queue_submit()) {
    protos::pbzero::VulkanApiEvent_VkQueueSubmit::Decoder event(
        vk_event.vk_queue_submit());
    // Once flow table is implemented, we can create a nice UI that link the
    // vkQueueSubmit to GpuRenderStageEvent.  For now, just add it as in a GPU
    // track so that they can appear close to the render stage slices.
    tables::GpuTrackTable::Row track(vk_event_track_id_);
    track.scope = vk_event_scope_id_;
    TrackId track_id = context_->track_tracker->InternGpuTrack(track);
    tables::GpuSliceTable::Row row;
    row.ts = ts;
    row.dur = static_cast<int64_t>(event.duration_ns());
    row.track_id = track_id;
    row.name = vk_queue_submit_id_;
    if (event.has_vk_command_buffers()) {
      row.command_buffer = static_cast<int64_t>(*event.vk_command_buffers());
    }
    row.submission_id = event.submission_id();
    auto args_callback = [this, &event](ArgsTracker::BoundInserter* inserter) {
      inserter->AddArg(context_->storage->InternString("pid"),
                       Variadic::Integer(event.pid()));
      inserter->AddArg(context_->storage->InternString("tid"),
                       Variadic::Integer(event.tid()));
    };
    context_->slice_tracker->ScopedTyped(
        context_->storage->mutable_gpu_slice_table(), row, args_callback);
  }
}

void GpuEventParser::ParseGpuMemTotalEvent(int64_t ts, ConstBytes blob) {
  protos::pbzero::GpuMemTotalEvent::Decoder gpu_mem_total(blob.data, blob.size);

  TrackId track = kInvalidTrackId;
  const uint32_t pid = gpu_mem_total.pid();
  if (pid == 0) {
    // Pid 0 is used to indicate the global total
    track = context_->track_tracker->InternGlobalCounterTrack(
        gpu_mem_total_name_id_, {}, gpu_mem_total_unit_id_,
        gpu_mem_total_global_desc_id_);
  } else {
    // Process emitting the packet can be different from the pid in the event.
    UniqueTid utid = context_->process_tracker->UpdateThread(pid, pid);
    UniquePid upid = context_->storage->thread_table().upid()[utid].value_or(0);
    track = context_->track_tracker->InternProcessCounterTrack(
        gpu_mem_total_name_id_, upid, gpu_mem_total_unit_id_,
        gpu_mem_total_proc_desc_id_);
  }
  context_->event_tracker->PushCounter(
      ts, static_cast<double>(gpu_mem_total.size()), track);
}

}  // namespace trace_processor
}  // namespace perfetto
