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

#include "ui/gl/test/gl_test_support.h"

#include <vector>

#include "base/check_op.h"
#include "base/notreached.h"
#include "build/build_config.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/half_float.h"
#include "ui/gl/init/gl_factory.h"
#include "ui/gl/test/gl_surface_test_support.h"

#if BUILDFLAG(IS_OZONE)
#include "base/run_loop.h"
#include "ui/base/ui_base_features.h"
#include "ui/ozone/public/ozone_platform.h"
#endif

namespace gl {

namespace {

template <typename T>
void rgb_to_yuv(uint8_t r, uint8_t g, uint8_t b, T* y, T* u, T* v) {
  // These values are used in the transformation from YUV to RGB color values.
  // They are taken from http://www.fourcc.org/fccyvrgb.php
  *y = (0.257 * r) + (0.504 * g) + (0.098 * b) + 16;
  *u = -(0.148 * r) - (0.291 * g) + (0.439 * b) + 128;
  *v = (0.439 * r) - (0.368 * g) - (0.071 * b) + 128;
}
}  // namespace

// static
GLDisplay* GLTestSupport::InitializeGL(
    absl::optional<GLImplementationParts> prefered_impl) {
#if BUILDFLAG(IS_OZONE)
  ui::OzonePlatform::InitParams params;
  params.single_process = true;
  ui::OzonePlatform::InitializeForGPU(params);
#endif

  std::vector<GLImplementationParts> allowed_impls =
      init::GetAllowedGLImplementations();
  DCHECK(!allowed_impls.empty());

  GLImplementationParts impl =
      prefered_impl ? *prefered_impl : allowed_impls[0];
  DCHECK(impl.IsAllowed(allowed_impls));

  GLDisplay* display =
      GLSurfaceTestSupport::InitializeOneOffImplementation(impl, true);
#if BUILDFLAG(IS_OZONE)
  // Make sure all the tasks posted to the current task runner by the
  // initialization functions are run before running the tests.
  base::RunLoop().RunUntilIdle();
#endif
  return display;
}

// static
void GLTestSupport::CleanupGL(GLDisplay* display) {
  GLSurfaceTestSupport::ShutdownGL(display);
}

// static
void GLTestSupport::SetBufferDataToColor(int width,
                                         int height,
                                         int stride,
                                         int plane,
                                         gfx::BufferFormat format,
                                         const uint8_t color[4],
                                         uint8_t* data) {
  switch (format) {
    case gfx::BufferFormat::R_8:
    case gfx::BufferFormat::RG_88:
      DCHECK_EQ(0, plane);
      for (int y = 0; y < height; ++y) {
        memset(&data[y * stride], color[0], width);
      }
      return;
    case gfx::BufferFormat::R_16:
      DCHECK_EQ(0, plane);
      for (int y = 0; y < height; ++y) {
        uint16_t* row = reinterpret_cast<uint16_t*>(data + y * stride);
        for (int x = 0; x < width; ++x) {
          row[x] = static_cast<uint16_t>(color[0] << 8);
        }
      }
      return;
    case gfx::BufferFormat::RG_1616:
      DCHECK_EQ(0, plane);
      for (int y = 0; y < height; ++y) {
        uint16_t* row = reinterpret_cast<uint16_t*>(data + y * stride);
        for (int x = 0; x < width; ++x) {
          row[2 * x + 0] = static_cast<uint16_t>(color[0] << 8);
          row[2 * x + 1] = static_cast<uint16_t>(color[1] << 8);
        }
      }
      return;
    case gfx::BufferFormat::RGBA_4444:
      DCHECK_EQ(0, plane);
      for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
          data[y * stride + x * 2 + 0] = (color[1] << 4) | (color[0] & 0xf);
          data[y * stride + x * 2 + 1] = (color[3] << 4) | (color[2] & 0xf);
        }
      }
      return;
    case gfx::BufferFormat::BGR_565:
      DCHECK_EQ(0, plane);
      for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
          *reinterpret_cast<uint16_t*>(&data[y * stride + x * 2]) =
              ((color[2] >> 3) << 11) | ((color[1] >> 2) << 5) |
              (color[0] >> 3);
        }
      }
      return;
    case gfx::BufferFormat::RGBX_8888:
      DCHECK_EQ(0, plane);
      for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
          data[y * stride + x * 4 + 0] = color[0];
          data[y * stride + x * 4 + 1] = color[1];
          data[y * stride + x * 4 + 2] = color[2];
          data[y * stride + x * 4 + 3] = 0xaa;  // unused
        }
      }
      return;
    case gfx::BufferFormat::RGBA_8888:
      DCHECK_EQ(0, plane);
      for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
          data[y * stride + x * 4 + 0] = color[0];
          data[y * stride + x * 4 + 1] = color[1];
          data[y * stride + x * 4 + 2] = color[2];
          data[y * stride + x * 4 + 3] = color[3];
        }
      }
      return;
    case gfx::BufferFormat::BGRX_8888:
      DCHECK_EQ(0, plane);
      for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
          data[y * stride + x * 4 + 0] = color[2];
          data[y * stride + x * 4 + 1] = color[1];
          data[y * stride + x * 4 + 2] = color[0];
          data[y * stride + x * 4 + 3] = 0xaa;  // unused
        }
      }
      return;
    case gfx::BufferFormat::BGRA_1010102: {
      DCHECK_EQ(0, plane);
      DCHECK_EQ(63, color[3] % 64) << "Alpha channel doesn't have enough "
                                      "precision for the supplied value";
      const uint8_t scaled_alpha = color[3] >> 6;
      for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
          *reinterpret_cast<uint32_t*>(&data[y * stride + x * 4]) =
              (scaled_alpha << 30) |                       // A
              ((color[0] << 2) | (color[0] >> 6)) << 20 |  // B
              ((color[1] << 2) | (color[1] >> 6)) << 10 |  // G
              ((color[2] << 2) | (color[2] >> 6));         // R
        }
      }
      return;
    }
    case gfx::BufferFormat::RGBA_1010102: {
      DCHECK_EQ(0, plane);
      DCHECK_EQ(63, color[3] % 64) << "Alpha channel doesn't have enough "
                                      "precision for the supplied value";
      const uint8_t scaled_alpha = color[3] >> 6;
      for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
          *reinterpret_cast<uint32_t*>(&data[y * stride + x * 4]) =
              (scaled_alpha << 30) |                       // A
              ((color[2] << 2) | (color[2] >> 6)) << 20 |  // B
              ((color[1] << 2) | (color[1] >> 6)) << 10 |  // G
              ((color[0] << 2) | (color[0] >> 6));         // R
        }
      }
      return;
    }
    case gfx::BufferFormat::BGRA_8888:
      DCHECK_EQ(0, plane);
      for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
          data[y * stride + x * 4 + 0] = color[2];
          data[y * stride + x * 4 + 1] = color[1];
          data[y * stride + x * 4 + 2] = color[0];
          data[y * stride + x * 4 + 3] = color[3];
        }
      }
      return;
    case gfx::BufferFormat::RGBA_F16: {
      DCHECK_EQ(0, plane);
      float float_color[4] = {
          color[0] / 255.f,
          color[1] / 255.f,
          color[2] / 255.f,
          color[3] / 255.f,
      };
      uint16_t half_float_color[4];
      gfx::FloatToHalfFloat(float_color, half_float_color, 4);
      for (int y = 0; y < height; ++y) {
        uint16_t* row = reinterpret_cast<uint16_t*>(data + y * stride);
        for (int x = 0; x < width; ++x) {
          row[x * 4 + 0] = half_float_color[0];
          row[x * 4 + 1] = half_float_color[1];
          row[x * 4 + 2] = half_float_color[2];
          row[x * 4 + 3] = half_float_color[3];
        }
      }
      return;
    }
    case gfx::BufferFormat::YVU_420: {
      DCHECK_LT(plane, 3);
      DCHECK_EQ(0, height % 2);
      DCHECK_EQ(0, width % 2);
      uint8_t yvu[3] = {};
      rgb_to_yuv(color[0], color[1], color[2], &yvu[0], &yvu[2], &yvu[1]);

      if (plane == 0) {
        for (int y = 0; y < height; ++y) {
          for (int x = 0; x < width; ++x) {
            data[stride * y + x] = yvu[0];
          }
        }
      } else {
        for (int y = 0; y < height / 2; ++y) {
          for (int x = 0; x < width / 2; ++x) {
            data[stride * y + x] = yvu[plane];
          }
        }
      }
      return;
    }
    case gfx::BufferFormat::YUV_420_BIPLANAR: {
      DCHECK_LT(plane, 2);
      DCHECK_EQ(0, height % 2);
      DCHECK_EQ(0, width % 2);
      uint8_t yuv[3] = {};
      rgb_to_yuv(color[0], color[1], color[2], &yuv[0], &yuv[1], &yuv[2]);

      if (plane == 0) {
        for (int y = 0; y < height; ++y) {
          for (int x = 0; x < width; ++x) {
            data[stride * y + x] = yuv[0];
          }
        }
      } else {
        for (int y = 0; y < height / 2; ++y) {
          for (int x = 0; x < width / 2; ++x) {
            data[stride * y + x * 2] = yuv[1];
            data[stride * y + x * 2 + 1] = yuv[2];
          }
        }
      }
      return;
    }
    case gfx::BufferFormat::YUVA_420_TRIPLANAR: {
      DCHECK_LT(plane, 3);
      DCHECK_EQ(0, height % 2);
      DCHECK_EQ(0, width % 2);
      uint8_t yuv[4] = {};
      rgb_to_yuv(color[0], color[1], color[2], &yuv[0], &yuv[1], &yuv[2]);
      yuv[3] = color[3];

      if (plane == 0) {
        for (int y = 0; y < height; ++y) {
          for (int x = 0; x < width; ++x) {
            data[stride * y + x] = yuv[0];
          }
        }
      } else if (plane == 1) {
        for (int y = 0; y < height / 2; ++y) {
          for (int x = 0; x < width / 2; ++x) {
            data[stride * y + x * 2] = yuv[1];
            data[stride * y + x * 2 + 1] = yuv[2];
          }
        }
      } else {
        for (int y = 0; y < height; ++y) {
          for (int x = 0; x < width; ++x) {
            data[stride * y + x] = yuv[3];
          }
        }
      }
      return;
    }
    case gfx::BufferFormat::P010: {
      DCHECK_LT(plane, 3);
      DCHECK_EQ(0, height % 2);
      DCHECK_EQ(0, width % 2);
      uint16_t yuv[3] = {};
      rgb_to_yuv(color[0], color[1], color[2], &yuv[0], &yuv[1], &yuv[2]);

      if (plane == 0) {
        for (int y = 0; y < height; ++y) {
          uint16_t* row = reinterpret_cast<uint16_t*>(data + y * stride);
          for (int x = 0; x < width; ++x) {
            row[x] = yuv[0] << 2;
          }
        }
      } else {
        for (int y = 0; y < height / 2; ++y) {
          uint16_t* row = reinterpret_cast<uint16_t*>(data + y * stride);
          for (int x = 0; x < width; x += 2) {
            row[x] = yuv[1] << 2;
            row[x + 1] = yuv[2] << 2;
          }
        }
      }
      return;
    }
  }
  NOTREACHED();
}
}  // namespace gl