text: // Copyright 2013 The Flutter 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 "software_surface.h"
  
  #include <lib/async/default.h>
  #include <lib/ui/scenic/cpp/commands.h>
  #include <zircon/status.h>
  #include <zircon/types.h>
  #include <zx/cpp/fidl.h>
  
  #include <cmath>
  
  #include "flutter/fml/logging.h"
  #include "flutter/fml/trace_event.h"
  #include "fuchsia/sysmem/cpp/fidl.h"
  #include "include/core/SkImageInfo.h"
  #include "third_party/skia/include/core/SkColorSpace.h"
  #include "third_party/skia/include/core/SkImageInfo.h"
  
  #include "../runtime/dart/utils/inlines.h"
  
  namespace flutter_runner {
  
  namespace {
  
  constexpr SkColorType kSkiaColorType = kRGBA_8888_SkColorType;
  
  uint32_t BytesPerRow(const fuchsia::sysmem::SingleBufferSettings& settings,
                       uint32_t bytes_per_pixel,
                       uint32_t image_width) {
    const uint32_t bytes_per_row_divisor =
        settings.image_format_constraints.bytes_per_row_divisor;
    const uint32_t min_bytes_per_row =
        settings.image_format_constraints.min_bytes_per_row;
    const uint32_t unrounded_bytes_per_row =
        std::max(image_width * bytes_per_pixel, min_bytes_per_row);
    const uint32_t roundup_bytes =
        unrounded_bytes_per_row % bytes_per_row_divisor;
  
    return unrounded_bytes_per_row + roundup_bytes;
  }
  
  }  // namespace
  
  uint32_t SoftwareSurface::sNextBufferId = 1;  // 0 is invalid; start at 1.
  
  SoftwareSurface::SoftwareSurface(
      fuchsia::sysmem::AllocatorSyncPtr& sysmem_allocator,
      fuchsia::ui::composition::AllocatorPtr& flatland_allocator,
      scenic::Session* session,
      const SkISize& size)
      : session_(session), wait_for_surface_read_finished_(this) {
    FML_CHECK((session_ || flatland_allocator.is_bound()) &&
              !(session_ && flatland_allocator.is_bound()));
  
    if (!SetupSkiaSurface(sysmem_allocator, flatland_allocator, size)) {
      FML_LOG(ERROR) << "Could not create render surface.";
      return;
    }
  
    if (!CreateFences()) {
      FML_LOG(ERROR) << "Could not create signal fences.";
      return;
    }
  
    if (session) {
      if (image_id_ == 0) {
        image_id_ = session->AllocResourceId();
      }
      session->Enqueue(scenic::NewCreateImage2Cmd(
          image_id_, sk_surface_->width(), sk_surface_->height(), buffer_id_, 0));
    }
  
    wait_for_surface_read_finished_.set_object(release_event_.get());
    wait_for_surface_read_finished_.set_trigger(ZX_EVENT_SIGNALED);
    Reset();
  
    valid_ = true;
  }
  
  SoftwareSurface::~SoftwareSurface() {
    if (session_) {
      if (image_id_) {
        session_->Enqueue(scenic::NewReleaseResourceCmd(image_id_));
      }
      if (buffer_id_) {
        session_->DeregisterBufferCollection(buffer_id_);
      }
    } else {
      release_image_callback_();
    }
    wait_for_surface_read_finished_.Cancel();
    wait_for_surface_read_finished_.set_object(ZX_HANDLE_INVALID);
  }
  
  bool SoftwareSurface::IsValid() const {
    return valid_;
  }
  
  SkISize SoftwareSurface::GetSize() const {
    if (!valid_) {
      return SkISize::Make(0, 0);
    }
  
    return SkISize::Make(sk_surface_->width(), sk_surface_->height());
  }
  
  bool SoftwareSurface::CreateFences() {
    if (zx::event::create(0, &acquire_event_) != ZX_OK) {
      FML_LOG(ERROR) << "Failed to create acquire event.";
      return false;
    }
  
    if (zx::event::create(0, &release_event_) != ZX_OK) {
      FML_LOG(ERROR) << "Failed to create release event.";
      return false;
    }
  
    return true;
  }
  
  bool SoftwareSurface::SetupSkiaSurface(
      fuchsia::sysmem::AllocatorSyncPtr& sysmem_allocator,
      fuchsia::ui::composition::AllocatorPtr& flatland_allocator,
      const SkISize& size) {
    if (size.isEmpty()) {
      FML_LOG(ERROR) << "Failed to allocate surface, size is empty.";
      return false;
    }
  
    // Allocate a "local" sysmem token to represent flutter's handle to the
    // sysmem buffer.
    fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token;
    zx_status_t allocate_status =
        sysmem_allocator->AllocateSharedCollection(local_token.NewRequest());
    if (allocate_status != ZX_OK) {
      FML_LOG(ERROR) << "Failed to allocate collection: "
                     << zx_status_get_string(allocate_status);
      return false;
    }
  
    // Create a single Duplicate of the token and Sync it; the single duplicate
    // token represents scenic's handle to the sysmem buffer.
    std::vector<fuchsia::sysmem::BufferCollectionTokenHandle> duplicate_tokens;
    zx_status_t duplicate_status = local_token->DuplicateSync(
        std::vector<zx::rights>{zx::rights::SAME_RIGHTS}, &duplicate_tokens);
    if (duplicate_status != ZX_OK) {
      FML_LOG(ERROR) << "Failed to duplicate collection token: "
                     << zx_status_get_string(duplicate_status);
      return false;
    }
    if (duplicate_tokens.size() != 1u) {
      FML_LOG(ERROR) << "Failed to duplicate collection token: Incorrect number "
                        "of tokens returned.";
      return false;
    }
    auto scenic_token = std::move(duplicate_tokens[0]);
  
    // Register the sysmem token with flatland (or scenic's legacy gfx interface).
    //
    // This binds the sysmem token to a composition token, which is used later
    // to associate the rendering surface with a specific flatland Image.
    //
    // Under gfx, scenic uses an integral `buffer_id` instead of the composition
    // token.
    if (session_) {
      buffer_id_ = sNextBufferId++;
      session_->RegisterBufferCollection(buffer_id_, std::move(scenic_token));
    } else {
      fuchsia::ui::composition::BufferCollectionExportToken export_token;
      zx_status_t token_create_status =
          zx::eventpair::create(0, &export_token.value, &import_token_.value);
      if (token_create_status != ZX_OK) {
        FML_LOG(ERROR) << "Failed to create flatland export token: "
                       << zx_status_get_string(token_create_status);
        return false;
      }
  
      fuchsia::ui::composition::RegisterBufferCollectionArgs args;
      args.set_export_token(std::move(export_token));
      args.set_buffer_collection_token(std::move(scenic_token));
      args.set_usage(
          fuchsia::ui::composition::RegisterBufferCollectionUsage::DEFAULT);
      flatland_allocator->RegisterBufferCollection(
          std::move(args),
          [](fuchsia::ui::composition::Allocator_RegisterBufferCollection_Result
                 result) {
            if (result.is_err()) {
              FML_LOG(ERROR)
                  << "RegisterBufferCollection call to Scenic Allocator failed.";
            }
          });
    }
  
    // Acquire flutter's local handle to the sysmem buffer.
    fuchsia::sysmem::BufferCollectionSyncPtr buffer_collection;
    zx_status_t bind_status = sysmem_allocator->BindSharedCollection(
        std::move(local_token), buffer_collection.NewRequest());
    if (bind_status != ZX_OK) {
      FML_LOG(ERROR) << "Failed to bind collection token: "
                     << zx_status_get_string(bind_status);
      return false;
    }
  
    // Set flutter's constraints on the sysmem buffer.  Software rendering only
    // requires CPU access to the surface and a basic R8G8B8A8 pixel format.
    fuchsia::sysmem::BufferCollectionConstraints constraints;
    constraints.min_buffer_count = 1;
    constraints.usage.cpu =
        fuchsia::sysmem::cpuUsageWrite | fuchsia::sysmem::cpuUsageWriteOften;
    constraints.has_buffer_memory_constraints = true;
    constraints.buffer_memory_constraints.physically_contiguous_required = false;
    constraints.buffer_memory_constraints.secure_required = false;
    constraints.buffer_memory_constraints.ram_domain_supported = true;
    constraints.buffer_memory_constraints.cpu_domain_supported = true;
    constraints.buffer_memory_constraints.inaccessible_domain_supported = false;
    constraints.image_format_constraints_count = 1;
    fuchsia::sysmem::ImageFormatConstraints& image_constraints =
        constraints.image_format_constraints[0];
    image_constraints = fuchsia::sysmem::ImageFormatConstraints();
    image_constraints.min_coded_width = static_cast<uint32_t>(size.fWidth);
    image_constraints.min_coded_height = static_cast<uint32_t>(size.fHeight);
    image_constraints.min_bytes_per_row = static_cast<uint32_t>(size.fWidth) * 4;
    image_constraints.pixel_format.type =
        fuchsia::sysmem::PixelFormatType::R8G8B8A8;
    image_constraints.color_spaces_count = 1;
    image_constraints.color_space[0].type = fuchsia::sysmem::ColorSpaceType::SRGB;
    image_constraints.pixel_format.has_format_modifier = true;
    image_constraints.pixel_format.format_modifier.value =
        fuchsia::sysmem::FORMAT_MODIFIER_LINEAR;
    zx_status_t set_constraints_status =
        buffer_collection->SetConstraints(true, constraints);
    if (set_constraints_status != ZX_OK) {
      FML_LOG(ERROR) << "Failed to set constraints: "
                     << zx_status_get_string(set_constraints_status);
      return false;
    }
  
    // Wait for sysmem to allocate, now that constraints are set.
    fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info;
    zx_status_t allocation_status = ZX_OK;
    zx_status_t wait_for_allocated_status =
        buffer_collection->WaitForBuffersAllocated(&allocation_status,
                                                   &buffer_collection_info);
    if (allocation_status != ZX_OK) {
      FML_LOG(ERROR) << "Failed to allocate: "
                     << zx_status_get_string(allocation_status);
      return false;
    }
    if (wait_for_allocated_status != ZX_OK) {
      FML_LOG(ERROR) << "Failed to wait for allocate: "
                     << zx_status_get_string(wait_for_allocated_status);
      return false;
    }
  
    // Cache the allocated surface VMO and metadata.
    FML_CHECK(buffer_collection_info.settings.buffer_settings.size_bytes != 0);
    FML_CHECK(buffer_collection_info.buffers[0].vmo != ZX_HANDLE_INVALID);
    surface_vmo_ = std::move(buffer_collection_info.buffers[0].vmo);
    surface_size_bytes_ =
        buffer_collection_info.settings.buffer_settings.size_bytes;
    if (buffer_collection_info.settings.buffer_settings.coherency_domain ==
        fuchsia::sysmem::CoherencyDomain::RAM) {
      // RAM coherency domain requires a cache clean when writes are finished.
      needs_cache_clean_ = true;
    }
  
    // Map the allocated buffer to the CPU.
    uint8_t* vmo_base = nullptr;
    zx_status_t buffer_map_status = zx::vmar::root_self()->map(
        ZX_VM_PERM_WRITE | ZX_VM_PERM_READ, 0, surface_vmo_, 0,
        surface_size_bytes_, reinterpret_cast<uintptr_t*>(&vmo_base));
    if (buffer_map_status != ZX_OK) {
      FML_LOG(ERROR) << "Failed to map buffer memory: "
                     << zx_status_get_string(buffer_map_status);
      return false;
    }
  
    // Now that the buffer is CPU-readable, it's safe to discard flutter's
    // connection to sysmem.
    zx_status_t close_status = buffer_collection->Close();
    if (close_status != ZX_OK) {
      FML_LOG(ERROR) << "Failed to close buffer: "
                     << zx_status_get_string(close_status);
      return false;
    }
  
    // Wrap the buffer in a software-rendered Skia surface.
    const uint64_t vmo_offset =
        buffer_collection_info.buffers[0].vmo_usable_start;
    const size_t vmo_stride =
        BytesPerRow(buffer_collection_info.settings, 4u, size.width());
    SkSurfaceProps sk_surface_props(0, kUnknown_SkPixelGeometry);
    sk_surface_ = SkSurface::MakeRasterDirect(
        SkImageInfo::Make(size, kSkiaColorType, kPremul_SkAlphaType,
                          SkColorSpace::MakeSRGB()),
        vmo_base + vmo_offset, vmo_stride, &sk_surface_props);
    if (!sk_surface_ || sk_surface_->getCanvas() == nullptr) {
      FML_LOG(ERROR) << "SkSurface::MakeRasterDirect failed.";
      return false;
    }
  
    return true;
  }
  
  void SoftwareSurface::SetImageId(uint32_t image_id) {
    FML_CHECK(image_id_ == 0);
    FML_CHECK(!session_);
    image_id_ = image_id;
  }
  
  uint32_t SoftwareSurface::GetImageId() {
    return image_id_;
  }
  
  sk_sp<SkSurface> SoftwareSurface::GetSkiaSurface() const {
    return valid_ ? sk_surface_ : nullptr;
  }
  
  fuchsia::ui::composition::BufferCollectionImportToken
  SoftwareSurface::GetBufferCollectionImportToken() {
    FML_CHECK(!session_);
    fuchsia::ui::composition::BufferCollectionImportToken import_dup;
    import_token_.value.duplicate(ZX_RIGHT_SAME_RIGHTS, &import_dup.value);
    return import_dup;
  }
  
  zx::event SoftwareSurface::GetAcquireFence() {
    FML_CHECK(!session_);
    zx::event fence;
    acquire_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &fence);
    return fence;
  }
  
  zx::event SoftwareSurface::GetReleaseFence() {
    FML_CHECK(!session_);
    zx::event fence;
    release_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &fence);
    return fence;
  }
  void SoftwareSurface::SetReleaseImageCallback(
      ReleaseImageCallback release_image_callback) {
    FML_CHECK(!session_);
    release_image_callback_ = release_image_callback;
  }
  
  size_t SoftwareSurface::AdvanceAndGetAge() {
    return ++age_;
  }
  
  bool SoftwareSurface::FlushSessionAcquireAndReleaseEvents() {
    if (session_) {
      zx::event acquire, release;
      if (acquire_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &acquire) != ZX_OK ||
          release_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &release) != ZX_OK) {
        return false;
      }
      session_->EnqueueAcquireFence(std::move(acquire));
      session_->EnqueueReleaseFence(std::move(release));
    }
  
    age_ = 0;
    return true;
  }
  
  void SoftwareSurface::SignalWritesFinished(
      const std::function<void(void)>& on_surface_read_finished) {
    FML_CHECK(on_surface_read_finished);
  
    if (!valid_) {
      on_surface_read_finished();
      return;
    }
  
    dart_utils::Check(surface_read_finished_callback_ == nullptr,
                      "Attempted to signal a write on the surface when the "
                      "previous write has not yet been acknowledged by the "
                      "compositor.");
    surface_read_finished_callback_ = on_surface_read_finished;
  
    // Sysmem *may* require the cache to be cleared after writes to the surface
    // are complete.
    if (needs_cache_clean_) {
      surface_vmo_.op_range(ZX_VMO_OP_CACHE_CLEAN, 0, surface_size_bytes_,
                            /*buffer*/ nullptr,
                            /*buffer_size*/ 0);
    }
  
    // Inform scenic that flutter is finished writing to the surface.
    zx_status_t signal_status = acquire_event_.signal(0u, ZX_EVENT_SIGNALED);
    if (signal_status != ZX_OK) {
      FML_LOG(ERROR) << "Failed to signal acquire event; "
                     << zx_status_get_string(signal_status);
    }
  }
  
  void SoftwareSurface::Reset() {
    if (acquire_event_.signal(ZX_EVENT_SIGNALED, 0u) != ZX_OK ||
        release_event_.signal(ZX_EVENT_SIGNALED, 0u) != ZX_OK) {
      valid_ = false;
      FML_LOG(ERROR) << "Could not reset fences. The surface is no longer valid.";
    }
  
    wait_for_surface_read_finished_.Begin(async_get_default_dispatcher());
  
    // It is safe for the caller to collect the surface in the callback.
    auto callback = surface_read_finished_callback_;
    surface_read_finished_callback_ = nullptr;
    if (callback) {
      callback();
    }
  }
  
  void SoftwareSurface::OnSurfaceReadFinished(async_dispatcher_t* dispatcher,
                                              async::WaitBase* wait,
                                              zx_status_t status,
                                              const zx_packet_signal_t* signal) {
    if (status != ZX_OK) {
      return;
    }
    FML_DCHECK(signal->observed & ZX_EVENT_SIGNALED);
  
    Reset();
  }
  
  }  // namespace flutter_runner
  