Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  GLBlitHelper.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "GLBlitHelper.h"

#include "gfxEnv.h"
#include "gfxUtils.h"
#include "GLContext.h"
#include "GLScreenBuffer.h"
#include "GPUVideoImage.h"
#include "HeapCopyOfStackArray.h"
#include "ImageContainer.h"
#include "ScopedGLHelpers.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/gfx/BuildConstants.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/layers/ImageDataSerializer.h"
#include "mozilla/layers/LayersSurfaces.h"

#ifdef MOZ_WIDGET_ANDROID
#  include "AndroidSurfaceTexture.h"
#  include "GLImages.h"
#  include "GLLibraryEGL.h"
#endif

#ifdef XP_MACOSX
#  include "GLContextCGL.h"
#  include "MacIOSurfaceImage.h"
#endif

#ifdef XP_WIN
#  include "mozilla/layers/D3D11ShareHandleImage.h"
#  include "mozilla/layers/D3D11ZeroCopyTextureImage.h"
#  include "mozilla/layers/D3D11YCbCrImage.h"
#endif

#ifdef MOZ_WIDGET_GTK
#  include "mozilla/layers/DMABUFSurfaceImage.h"
#  include "mozilla/widget/DMABufSurface.h"
#  include "mozilla/widget/DMABufLibWrapper.h"
#endif

using mozilla::layers::PlanarYCbCrData;
using mozilla::layers::PlanarYCbCrImage;

namespace mozilla {
namespace gl {

// --

static const char kFragPreprocHeader[] = R"(
  #ifdef GL_ES
    #ifdef GL_FRAGMENT_PRECISION_HIGH
      #define MAXP highp
    #endif
  #else
    #define MAXP highp
  #endif
  #ifndef MAXP
    #define MAXP mediump
  #endif

  #if __VERSION__ >= 130
    #define VARYING in
  #else
    #define VARYING varying
  #endif
  #if __VERSION__ >= 120
    #define MAT4X3 mat4x3
  #else
    #define MAT4X3 mat4
  #endif
)";

// -

const charconst kFragHeader_Tex2D = R"(
    #define SAMPLER sampler2D
    #if __VERSION__ >= 130
        #define TEXTURE texture
    #else
        #define TEXTURE texture2D
    #endif
)";
const charconst kFragHeader_Tex2DRect = R"(
    #define SAMPLER sampler2DRect
    #if __VERSION__ >= 130
        #define TEXTURE texture
    #else
        #define TEXTURE texture2DRect
    #endif
)";
const charconst kFragHeader_TexExt = R"(
    #extension GL_OES_EGL_image_external : enable
    #extension GL_OES_EGL_image_external_essl3 : enable
    #if __VERSION__ >= 130
        #define TEXTURE texture
    #else
        #define TEXTURE texture2D
    #endif
    #define SAMPLER samplerExternalOES
)";

// -

static const char kFragDeclHeader[] = R"(
  precision PRECISION float;
  #if __VERSION__ >= 130
    #define FRAG_COLOR oFragColor
    out vec4 FRAG_COLOR;
  #else
    #define FRAG_COLOR gl_FragColor
  #endif
)";

// -

const charconst kFragSample_OnePlane = R"(
  VARYING mediump vec2 vTexCoord0;
  uniform PRECISION SAMPLER uTex0;

  vec4 metaSample() {
    vec4 src = TEXTURE(uTex0, vTexCoord0);
    return src;
  }
)";
// Ideally this would just change the color-matrix it uses, but this is
// acceptable debt for now.
// `extern` so that we don't get ifdef-dependent const-var-unused Werrors.
extern const charconst kFragSample_OnePlane_YUV_via_GBR = R"(
  VARYING mediump vec2 vTexCoord0;
  uniform PRECISION SAMPLER uTex0;

  vec4 metaSample() {
    vec4 yuva = TEXTURE(uTex0, vTexCoord0).gbra;
    return yuva;
  }
)";
const charconst kFragSample_TwoPlane = R"(
  VARYING mediump vec2 vTexCoord0;
  VARYING mediump vec2 vTexCoord1;
  uniform PRECISION SAMPLER uTex0;
  uniform PRECISION SAMPLER uTex1;

  vec4 metaSample() {
    vec4 src = TEXTURE(uTex0, vTexCoord0); // Keep r and a.
    src.gb = TEXTURE(uTex1, vTexCoord1).rg;
    return src;
  }
)";
const charconst kFragSample_ThreePlane = R"(
  VARYING mediump vec2 vTexCoord0;
  VARYING mediump vec2 vTexCoord1;
  uniform PRECISION SAMPLER uTex0;
  uniform PRECISION SAMPLER uTex1;
  uniform PRECISION SAMPLER uTex2;

  vec4 metaSample() {
    vec4 src = TEXTURE(uTex0, vTexCoord0); // Keep r and a.
    src.g = TEXTURE(uTex1, vTexCoord1).r;
    src.b = TEXTURE(uTex2, vTexCoord1).r;
    return src;
  }
)";

// -

const charconst kFragConvert_None = R"(
  vec3 metaConvert(vec3 src) {
    return src;
  }
)";
const charconst kFragConvert_BGR = R"(
  vec3 metaConvert(vec3 src) {
    return src.bgr;
  }
)";
const charconst kFragConvert_ColorMatrix = R"(
  uniform mediump MAT4X3 uColorMatrix;

  vec3 metaConvert(vec3 src) {
    return (uColorMatrix * vec4(src, 1)).rgb;
  }
)";
const charconst kFragConvert_ColorLut3d = R"(
  uniform PRECISION sampler3D uColorLut;

  vec3 metaConvert(vec3 src) {
    // Half-texel filtering hazard!
    // E.g. For texture size of 2,
    // E.g. x=0.25 is still sampling 100% of texel x=0, 0% of texel x=1.
    // For the LUT, we need r=0.25 to filter 75/25 from texel 0 and 1.
    // That is, we need to adjust our sampling point such that it starts in the
    // center of texel 0, and ends in the center of texel N-1.
    // We need, for N=2:
    // v=0.0|N=2 => v'=0.5/2
    // v=1.0|N=2 => v'=1.5/2
    // For N=3:
    // v=0.0|N=3 => v'=0.5/3
    // v=1.0|N=3 => v'=2.5/3
    // => v' = ( 0.5 + v * (3 - 1) )/3
    vec3 size = vec3(textureSize(uColorLut, 0));
    src = (0.5 + src * (size - 1.0)) / size;
    return texture(uColorLut, src).rgb;
  }
)";
// Delete if unused after 2024-10-01:
const charconst kFragConvert_ColorLut2d = R"(
  uniform PRECISION sampler2D uColorLut;
  uniform mediump vec3 uColorLut3dSize;

  vec3 metaConvert(vec3 src) {
    // Half-texel filtering hazard!
    // E.g. For texture size of 2,
    // E.g. x=0.25 is still sampling 100% of texel x=0, 0% of texel x=1.
    // For the LUT, we need r=0.25 to filter 75/25 from texel 0 and 1.
    // That is, we need to adjust our sampling point such that it starts in the
    // center of texel 0, and ends in the center of texel N-1.
    // We need, for N=2:
    // v=0.0|N=2 => v'=0.5/2
    // v=1.0|N=2 => v'=1.5/2
    // For N=3:
    // v=0.0|N=3 => v'=0.5/3
    // v=1.0|N=3 => v'=2.5/3
    // => v' = ( 0.5 + v * (3 - 1) )/3
    src = clamp(src, vec3(0,0,0), vec3(1,1,1));
    vec3 lut3dSize = uColorLut3dSize;
    vec2 lut2dSize = vec2(lut3dSize.x, lut3dSize.y * lut3dSize.z);
    vec3 texelSrc3d = 0.5 + src * (lut3dSize - 1.0);

    vec3 texelSrc3d_zFloor = texelSrc3d;
    texelSrc3d_zFloor.z = floor(texelSrc3d_zFloor.z);
    vec3 texelSrc3d_zNext = texelSrc3d_zFloor + vec3(0,0,1);
    texelSrc3d_zNext.z = min(texelSrc3d_zNext.z, lut3dSize.z - 1.0);

    vec2 texelSrc2d_zFloor = texelSrc3d_zFloor.xy + vec2(0, texelSrc3d_zFloor.z * lut3dSize.y);
    vec2 texelSrc2d_zNext  = texelSrc3d_zNext.xy  + vec2(0, texelSrc3d_zNext.z  * lut3dSize.y);

    vec4 dst_zFloor = texture(uColorLut, texelSrc2d_zFloor / lut2dSize);
    vec4 dst_zNext = texture(uColorLut, texelSrc2d_zNext / lut2dSize);

    return mix(dst_zFloor, dst_zNext, texelSrc3d.z - texelSrc3d_zFloor.z);
  }
)";

// -

const charconst kFragMixin_AlphaMultColors = R"(
  #define MIXIN_ALPHA_MULT_COLORS
)";
const charconst kFragMixin_AlphaClampColors = R"(
  #define MIXIN_ALPHA_CLAMP_COLORS
)";
const charconst kFragMixin_AlphaOne = R"(
  #define MIXIN_ALPHA_ONE
)";

// -

static const char kFragBody[] = R"(
  void main(void) {
    vec4 src = metaSample();
    vec4 dst = vec4(metaConvert(src.rgb), src.a);

  #ifdef MIXIN_ALPHA_MULT_COLORS
    dst.rgb *= dst.a;
  #endif
  #ifdef MIXIN_ALPHA_CLAMP_COLORS
    dst.rgb = min(dst.rgb, vec3(dst.a)); // Ensure valid premult-alpha colors.
  #endif
  #ifdef MIXIN_ALPHA_ONE
    dst.a = 1.0;
  #endif

    FRAG_COLOR = dst;
  }
)";

// --

Mat3 SubRectMat3(const float x, const float y, const float w, const float h) {
  auto ret = Mat3{};
  ret.at(0, 0) = w;
  ret.at(1, 1) = h;
  ret.at(2, 0) = x;
  ret.at(2, 1) = y;
  ret.at(2, 2) = 1.0f;
  return ret;
}

Mat3 SubRectMat3(const gfx::IntRect& subrect, const gfx::IntSize& size) {
  return SubRectMat3(float(subrect.X()) / size.width,
                     float(subrect.Y()) / size.height,
                     float(subrect.Width()) / size.width,
                     float(subrect.Height()) / size.height);
}

Mat3 SubRectMat3(const gfx::IntRect& bigSubrect, const gfx::IntSize& smallSize,
                 const gfx::IntSize& divisors) {
  const float x = float(bigSubrect.X()) / divisors.width;
  const float y = float(bigSubrect.Y()) / divisors.height;
  const float w = float(bigSubrect.Width()) / divisors.width;
  const float h = float(bigSubrect.Height()) / divisors.height;
  return SubRectMat3(x / smallSize.width, y / smallSize.height,
                     w / smallSize.width, h / smallSize.height);
}

Mat3 MatrixToMat3(const gfx::Matrix& aMatrix) {
  auto ret = Mat3();
  ret.at(0, 0) = aMatrix._11;
  ret.at(1, 0) = aMatrix._21;
  ret.at(2, 0) = aMatrix._31;
  ret.at(0, 1) = aMatrix._12;
  ret.at(1, 1) = aMatrix._22;
  ret.at(2, 1) = aMatrix._32;
  ret.at(0, 2) = 0.0f;
  ret.at(1, 2) = 0.0f;
  ret.at(2, 2) = 1.0f;
  return ret;
}

// --

ScopedSaveMultiTex::ScopedSaveMultiTex(GLContext* const gl,
                                       const size_t texUnits,
                                       const GLenum texTarget)
    : mGL(*gl),
      mTexUnits(texUnits),
      mTexTarget(texTarget),
      mOldTexUnit(mGL.GetIntAs<GLenum>(LOCAL_GL_ACTIVE_TEXTURE)) {
  MOZ_RELEASE_ASSERT(texUnits >= 1);

  GLenum texBinding;
  switch (mTexTarget) {
    case LOCAL_GL_TEXTURE_2D:
      texBinding = LOCAL_GL_TEXTURE_BINDING_2D;
      break;
    case LOCAL_GL_TEXTURE_3D:
      texBinding = LOCAL_GL_TEXTURE_BINDING_3D;
      break;
    case LOCAL_GL_TEXTURE_RECTANGLE:
      texBinding = LOCAL_GL_TEXTURE_BINDING_RECTANGLE;
      break;
    case LOCAL_GL_TEXTURE_EXTERNAL:
      texBinding = LOCAL_GL_TEXTURE_BINDING_EXTERNAL;
      break;
    default:
      gfxCriticalError() << "Unhandled texTarget: " << texTarget;
      MOZ_CRASH();
  }

  for (const auto i : IntegerRange(mTexUnits)) {
    mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + i);
    if (mGL.IsSupported(GLFeature::sampler_objects)) {
      mOldTexSampler[i] = mGL.GetIntAs<GLuint>(LOCAL_GL_SAMPLER_BINDING);
      mGL.fBindSampler(i, 0);
    }
    mOldTex[i] = mGL.GetIntAs<GLuint>(texBinding);
  }
}

ScopedSaveMultiTex::~ScopedSaveMultiTex() {
  // Unbind in reverse order, in case we have repeats.
  // Order matters because we unbound samplers during ctor, so now we have to
  // make sure we rebind them in the right order.
  for (const auto i : Reversed(IntegerRange(mTexUnits))) {
    mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + i);
    if (mGL.IsSupported(GLFeature::sampler_objects)) {
      mGL.fBindSampler(i, mOldTexSampler[i]);
    }
    mGL.fBindTexture(mTexTarget, mOldTex[i]);
  }
  mGL.fActiveTexture(mOldTexUnit);
}

// --

class ScopedBindArrayBuffer final {
 public:
  GLContext& mGL;
  const GLuint mOldVBO;

  ScopedBindArrayBuffer(GLContext* const gl, const GLuint vbo)
      : mGL(*gl), mOldVBO(mGL.GetIntAs<GLuint>(LOCAL_GL_ARRAY_BUFFER_BINDING)) {
    mGL.fBindBuffer(LOCAL_GL_ARRAY_BUFFER, vbo);
  }

  ~ScopedBindArrayBuffer() { mGL.fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mOldVBO); }
};

// --

class ScopedShader final {
  GLContext& mGL;
  const GLuint mName;

 public:
  ScopedShader(GLContext* const gl, const GLenum shaderType)
      : mGL(*gl), mName(mGL.fCreateShader(shaderType)) {}

  ~ScopedShader() { mGL.fDeleteShader(mName); }

  operator GLuint() const { return mName; }
};

// --

class SaveRestoreCurrentProgram final {
  GLContext& mGL;
  const GLuint mOld;

 public:
  explicit SaveRestoreCurrentProgram(GLContext* const gl)
      : mGL(*gl), mOld(mGL.GetIntAs<GLuint>(LOCAL_GL_CURRENT_PROGRAM)) {}

  ~SaveRestoreCurrentProgram() { mGL.fUseProgram(mOld); }
};

// --

class ScopedDrawBlitState final {
  GLContext& mGL;

  const bool blend;
  const bool cullFace;
  const bool depthTest;
  const bool dither;
  const bool polyOffsFill;
  const bool sampleAToC;
  const bool sampleCover;
  const bool scissor;
  const bool stencil;
  Maybe<bool> rasterizerDiscard;

  realGLboolean colorMask[4];
  GLint viewport[4];

 public:
  ScopedDrawBlitState(GLContext* const gl, const gfx::IntSize& destSize)
      : mGL(*gl),
        blend(mGL.PushEnabled(LOCAL_GL_BLEND, false)),
        cullFace(mGL.PushEnabled(LOCAL_GL_CULL_FACE, false)),
        depthTest(mGL.PushEnabled(LOCAL_GL_DEPTH_TEST, false)),
        dither(mGL.PushEnabled(LOCAL_GL_DITHER, true)),
        polyOffsFill(mGL.PushEnabled(LOCAL_GL_POLYGON_OFFSET_FILL, false)),
        sampleAToC(mGL.PushEnabled(LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE, false)),
        sampleCover(mGL.PushEnabled(LOCAL_GL_SAMPLE_COVERAGE, false)),
        scissor(mGL.PushEnabled(LOCAL_GL_SCISSOR_TEST, false)),
        stencil(mGL.PushEnabled(LOCAL_GL_STENCIL_TEST, false)) {
    if (mGL.IsSupported(GLFeature::transform_feedback2)) {
      // Technically transform_feedback2 requires transform_feedback, which
      // actually adds RASTERIZER_DISCARD.
      rasterizerDiscard =
          Some(mGL.PushEnabled(LOCAL_GL_RASTERIZER_DISCARD, false));
    }

    mGL.fGetBooleanv(LOCAL_GL_COLOR_WRITEMASK, colorMask);
    if (mGL.IsSupported(GLFeature::draw_buffers_indexed)) {
      mGL.fColorMaski(0, truetruetruetrue);
    } else {
      mGL.fColorMask(truetruetruetrue);
    }

    mGL.fGetIntegerv(LOCAL_GL_VIEWPORT, viewport);
    MOZ_ASSERT(destSize.width && destSize.height);
    mGL.fViewport(0, 0, destSize.width, destSize.height);
  }

  ~ScopedDrawBlitState() {
    mGL.SetEnabled(LOCAL_GL_BLEND, blend);
    mGL.SetEnabled(LOCAL_GL_CULL_FACE, cullFace);
    mGL.SetEnabled(LOCAL_GL_DEPTH_TEST, depthTest);
    mGL.SetEnabled(LOCAL_GL_DITHER, dither);
    mGL.SetEnabled(LOCAL_GL_POLYGON_OFFSET_FILL, polyOffsFill);
    mGL.SetEnabled(LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE, sampleAToC);
    mGL.SetEnabled(LOCAL_GL_SAMPLE_COVERAGE, sampleCover);
    mGL.SetEnabled(LOCAL_GL_SCISSOR_TEST, scissor);
    mGL.SetEnabled(LOCAL_GL_STENCIL_TEST, stencil);
    if (rasterizerDiscard) {
      mGL.SetEnabled(LOCAL_GL_RASTERIZER_DISCARD, rasterizerDiscard.value());
    }

    if (mGL.IsSupported(GLFeature::draw_buffers_indexed)) {
      mGL.fColorMaski(0, colorMask[0], colorMask[1], colorMask[2],
                      colorMask[3]);
    } else {
      mGL.fColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
    }
    mGL.fViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
  }
};

// --

DrawBlitProg::DrawBlitProg(const GLBlitHelper* const parent, const GLuint prog)
    : mParent(*parent),
      mProg(prog),
      mLoc_uDestMatrix(mParent.mGL->fGetUniformLocation(mProg, "uDestMatrix")),
      mLoc_uTexMatrix0(mParent.mGL->fGetUniformLocation(mProg, "uTexMatrix0")),
      mLoc_uTexMatrix1(mParent.mGL->fGetUniformLocation(mProg, "uTexMatrix1")),
      mLoc_uColorLut(mParent.mGL->fGetUniformLocation(mProg, "uColorLut")),
      mLoc_uColorMatrix(
          mParent.mGL->fGetUniformLocation(mProg, "uColorMatrix")) {
  const auto& gl = mParent.mGL;
  MOZ_GL_ASSERT(gl, mLoc_uDestMatrix != -1);  // Required
  MOZ_GL_ASSERT(gl, mLoc_uTexMatrix0 != -1);  // Required
  if (mLoc_uColorMatrix != -1) {
    MOZ_GL_ASSERT(gl, mLoc_uTexMatrix1 != -1);

    int32_t numActiveUniforms = 0;
    gl->fGetProgramiv(mProg, LOCAL_GL_ACTIVE_UNIFORMS, &numActiveUniforms);

    const size_t kMaxNameSize = 32;
    char name[kMaxNameSize] = {0};
    GLint size = 0;
    GLenum type = 0;
    for (int32_t i = 0; i < numActiveUniforms; i++) {
      gl->fGetActiveUniform(mProg, i, kMaxNameSize, nullptr, &size, &type,
                            name);
      if (strcmp("uColorMatrix", name) == 0) {
        mType_uColorMatrix = type;
        break;
      }
    }
    MOZ_GL_ASSERT(gl, mType_uColorMatrix);
  }
}

DrawBlitProg::~DrawBlitProg() {
  const auto& gl = mParent.mGL;
  if (!gl->MakeCurrent()) return;

  gl->fDeleteProgram(mProg);
}

void DrawBlitProg::Draw(const BaseArgs& args,
                        const YUVArgs* const argsYUV) const {
  const auto& gl = mParent.mGL;

  const SaveRestoreCurrentProgram oldProg(gl);
  gl->fUseProgram(mProg);

  // --

  Mat3 destMatrix;
  if (args.destRect) {
    const auto& destRect = args.destRect.value();
    destMatrix = SubRectMat3(destRect.X() / args.destSize.width,
                             destRect.Y() / args.destSize.height,
                             destRect.Width() / args.destSize.width,
                             destRect.Height() / args.destSize.height);
  } else {
    destMatrix = Mat3::I();
  }

  if (args.yFlip) {
    // Apply the y-flip matrix before the destMatrix.
    // That is, flip y=[0-1] to y=[1-0] before we restrict to the destRect.
    destMatrix.at(2, 1) += destMatrix.at(1, 1);
    destMatrix.at(1, 1) *= -1.0f;
  }

  gl->fUniformMatrix3fv(mLoc_uDestMatrix, 1, false, destMatrix.m);
  gl->fUniformMatrix3fv(mLoc_uTexMatrix0, 1, false, args.texMatrix0.m);

  MOZ_ASSERT(bool(argsYUV) == (mLoc_uColorMatrix != -1));
  if (argsYUV) {
    gl->fUniformMatrix3fv(mLoc_uTexMatrix1, 1, false, argsYUV->texMatrix1.m);

    if (mLoc_uColorMatrix != -1) {
      const auto& colorMatrix =
          gfxUtils::YuvToRgbMatrix4x4ColumnMajor(*argsYUV->colorSpaceForMatrix);
      float mat4x3[4 * 3];
      switch (mType_uColorMatrix) {
        case LOCAL_GL_FLOAT_MAT4:
          gl->fUniformMatrix4fv(mLoc_uColorMatrix, 1, false, colorMatrix);
          break;
        case LOCAL_GL_FLOAT_MAT4x3:
          for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 3; y++) {
              mat4x3[3 * x + y] = colorMatrix[4 * x + y];
            }
          }
          gl->fUniformMatrix4x3fv(mLoc_uColorMatrix, 1, false, mat4x3);
          break;
        default:
          gfxCriticalError()
              << "Bad mType_uColorMatrix: " << gfx::hexa(mType_uColorMatrix);
      }
    }
  }

  // --

  const ScopedDrawBlitState drawState(gl, args.destSize);

  GLuint oldVAO;
  GLint vaa0Enabled;
  GLint vaa0Size;
  GLenum vaa0Type;
  GLint vaa0Normalized;
  GLsizei vaa0Stride;
  GLvoid* vaa0Pointer;
  GLuint vaa0Buffer;
  if (mParent.mQuadVAO) {
    oldVAO = gl->GetIntAs<GLuint>(LOCAL_GL_VERTEX_ARRAY_BINDING);
    gl->fBindVertexArray(mParent.mQuadVAO);
  } else {
    // clang-format off
    gl->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, (GLint*)&vaa0Buffer);
    gl->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED, &vaa0Enabled);
    gl->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE, &vaa0Size);
    gl->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE, (GLint*)&vaa0Type);
    gl->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &vaa0Normalized);
    gl->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_STRIDE, (GLint*)&vaa0Stride);
    gl->fGetVertexAttribPointerv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER, &vaa0Pointer);
    // clang-format on

    gl->fEnableVertexAttribArray(0);
    const ScopedBindArrayBuffer bindVBO(gl, mParent.mQuadVBO);
    gl->fVertexAttribPointer(0, 2, LOCAL_GL_FLOAT, false, 0, 0);
  }

  gl->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);

  if (mParent.mQuadVAO) {
    gl->fBindVertexArray(oldVAO);
  } else {
    if (vaa0Enabled) {
      gl->fEnableVertexAttribArray(0);
    } else {
      gl->fDisableVertexAttribArray(0);
    }
    // The current VERTEX_ARRAY_BINDING is not necessarily the same as the
    // buffer set for vaa0Buffer.
    const ScopedBindArrayBuffer bindVBO(gl, vaa0Buffer);
    gl->fVertexAttribPointer(0, vaa0Size, vaa0Type, bool(vaa0Normalized),
                             vaa0Stride, vaa0Pointer);
  }
}

// --

GLBlitHelper::GLBlitHelper(GLContext* const gl)
    : mGL(gl),
      mDrawBlitProg_VertShader(mGL->fCreateShader(LOCAL_GL_VERTEX_SHADER))
//, mYuvUploads_YSize(0, 0)
//, mYuvUploads_UVSize(0, 0)
{
  mGL->fGenBuffers(1, &mQuadVBO);
  {
    const ScopedBindArrayBuffer bindVBO(mGL, mQuadVBO);

    const float quadData[] = {0, 0, 1, 0, 0, 1, 1, 1};
    const HeapCopyOfStackArray<float> heapQuadData(quadData);
    mGL->fBufferData(LOCAL_GL_ARRAY_BUFFER, heapQuadData.ByteLength(),
                     heapQuadData.Data(), LOCAL_GL_STATIC_DRAW);

    if (mGL->IsSupported(GLFeature::vertex_array_object)) {
      const auto prev = mGL->GetIntAs<GLuint>(LOCAL_GL_VERTEX_ARRAY_BINDING);

      mGL->fGenVertexArrays(1, &mQuadVAO);
      mGL->fBindVertexArray(mQuadVAO);
      mGL->fEnableVertexAttribArray(0);
      mGL->fVertexAttribPointer(0, 2, LOCAL_GL_FLOAT, false, 0, 0);

      mGL->fBindVertexArray(prev);
    }
  }

  // --

  const auto glslVersion = mGL->ShadingLanguageVersion();

  if (mGL->IsGLES()) {
    // If you run into problems on old android devices, it might be because some
    // devices have OES_EGL_image_external but not OES_EGL_image_external_essl3.
    // We could just use 100 in that particular case, but then we lose out on
    // e.g. sampler3D. Let's just try 300 for now, and if we get regressions
    // we'll add an essl100 fallback.
    if (glslVersion >= 300) {
      mDrawBlitProg_VersionLine = nsCString("#version 300 es\n");
    } else {
      mDrawBlitProg_VersionLine = nsCString("#version 100\n");
    }
  } else if (glslVersion >= 130) {
    mDrawBlitProg_VersionLine = nsPrintfCString("#version %u\n", glslVersion);
  }

  const char kVertSource[] =
      "\
        #if __VERSION__ >= 130                                               \n\
            #define ATTRIBUTE in                                             \n\
            #define VARYING out                                              \n\
        #else                                                                \n\
            #define ATTRIBUTE attribute                                      \n\
            #define VARYING varying                                          \n\
        #endif                                                               \n\
                                                                             \n\
        ATTRIBUTE vec2 aVert; // [0.0-1.0]                                   \n\
                                                                             \n\
        uniform mat3 uDestMatrix;                                            \n\
        uniform mat3 uTexMatrix0;                                            \n\
        uniform mat3 uTexMatrix1;                                            \n\
                                                                             \n\
        VARYING vec2 vTexCoord0;                                             \n\
        VARYING vec2 vTexCoord1;                                             \n\
                                                                             \n\
        void main(void)                                                      \n\
        {                                                                    \n\
            vec2 destPos = (uDestMatrix * vec3(aVert, 1.0)).xy;              \n\
            gl_Position = vec4(destPos * 2.0 - 1.0, 0.0, 1.0);               \n\
                                                                             \n\
            vTexCoord0 = (uTexMatrix0 * vec3(aVert, 1.0)).xy;                \n\
            vTexCoord1 = (uTexMatrix1 * vec3(aVert, 1.0)).xy;                \n\
        }                                                                    \n\
    ";
  const charconst parts[] = {mDrawBlitProg_VersionLine.get(), kVertSource};
  mGL->fShaderSource(mDrawBlitProg_VertShader, std::size(parts), parts,
                     nullptr);
  mGL->fCompileShader(mDrawBlitProg_VertShader);
}

GLBlitHelper::~GLBlitHelper() {
  mDrawBlitProgs.clear();

  if (!mGL->MakeCurrent()) return;

  mGL->fDeleteShader(mDrawBlitProg_VertShader);
  mGL->fDeleteBuffers(1, &mQuadVBO);

  if (mQuadVAO) {
    mGL->fDeleteVertexArrays(1, &mQuadVAO);
  }
}

// --

const DrawBlitProg& GLBlitHelper::GetDrawBlitProg(
    const DrawBlitProg::Key& key) const {
  auto& ret = mDrawBlitProgs[key];
  if (!ret) {
    ret = CreateDrawBlitProg(key);
  }
  return *ret;
}

std::unique_ptr<const DrawBlitProg> GLBlitHelper::CreateDrawBlitProg(
    const DrawBlitProg::Key& key) const {
  const auto precisionPref = StaticPrefs::gfx_blithelper_precision();
  const char* precision;
  switch (precisionPref) {
    case 0:
      precision = "lowp";
      break;
    case 1:
      precision = "mediump";
      break;
    default:
      if (precisionPref != 2) {
        NS_WARNING("gfx.blithelper.precision clamped to 2.");
      }
      precision = "MAXP";
      break;
  }

  nsPrintfCString precisionLine("\n#define PRECISION %s\n", precision);

  // -

  const ScopedShader fs(mGL, LOCAL_GL_FRAGMENT_SHADER);

  std::vector<const char*> parts;
  {
    parts.push_back(mDrawBlitProg_VersionLine.get());
    parts.push_back(kFragPreprocHeader);
    if (key.fragHeader) {
      parts.push_back(key.fragHeader);
    }
    parts.push_back(precisionLine.BeginReading());
    parts.push_back(kFragDeclHeader);
    for (const auto& part : key.fragParts) {
      if (part) {
        parts.push_back(part);
      }
    }
    parts.push_back(kFragBody);
  }

  const auto PrintFragSource = [&]() {
    printf_stderr("Frag source:\n");
    int i = 0;
    for (const auto& part : parts) {
      printf_stderr("// parts[%i]:\n%s\n", i, part);
      i += 1;
    }
  };
  if (gfxEnv::MOZ_DUMP_GLBLITHELPER()) {
    PrintFragSource();
  }

  mGL->fShaderSource(fs, AssertedCast<GLint>(parts.size()), parts.data(),
                     nullptr);
  mGL->fCompileShader(fs);

  const auto prog = mGL->fCreateProgram();
  mGL->fAttachShader(prog, mDrawBlitProg_VertShader);
  mGL->fAttachShader(prog, fs);

  mGL->fBindAttribLocation(prog, 0, "aVert");
  mGL->fLinkProgram(prog);

  GLenum status = 0;
  mGL->fGetProgramiv(prog, LOCAL_GL_LINK_STATUS, (GLint*)&status);
  if (status == LOCAL_GL_TRUE || mGL->CheckContextLost()) {
    const SaveRestoreCurrentProgram oldProg(mGL);
    mGL->fUseProgram(prog);
    const char* samplerNames[] = {"uTex0""uTex1""uTex2"};
    for (int i = 0; i < 3; i++) {
      const auto loc = mGL->fGetUniformLocation(prog, samplerNames[i]);
      if (loc == -1) continue;
      mGL->fUniform1i(loc, i);
    }

    return std::make_unique<DrawBlitProg>(this, prog);
  }

  GLuint progLogLen = 0;
  mGL->fGetProgramiv(prog, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&progLogLen);
  const UniquePtr<char[]> progLog(new char[progLogLen + 1]);
  mGL->fGetProgramInfoLog(prog, progLogLen, nullptr, progLog.get());
  progLog[progLogLen] = 0;

  const auto& vs = mDrawBlitProg_VertShader;
  GLuint vsLogLen = 0;
  mGL->fGetShaderiv(vs, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&vsLogLen);
  const UniquePtr<char[]> vsLog(new char[vsLogLen + 1]);
  mGL->fGetShaderInfoLog(vs, vsLogLen, nullptr, vsLog.get());
  vsLog[vsLogLen] = 0;

  GLuint fsLogLen = 0;
  mGL->fGetShaderiv(fs, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&fsLogLen);
  const UniquePtr<char[]> fsLog(new char[fsLogLen + 1]);
  mGL->fGetShaderInfoLog(fs, fsLogLen, nullptr, fsLog.get());
  fsLog[fsLogLen] = 0;

  const auto logs =
      std::string("DrawBlitProg link failed:\n") + "progLog: " + progLog.get() +
      "\n" + "vsLog: " + vsLog.get() + "\n" + "fsLog: " + fsLog.get() + "\n";
  gfxCriticalError() << logs;

  PrintFragSource();

  MOZ_CRASH("DrawBlitProg link failed");
}

// -----------------------------------------------------------------------------

#ifdef XP_MACOSX
static RefPtr<MacIOSurface> LookupSurface(
    const layers::SurfaceDescriptorMacIOSurface& sd) {
  return MacIOSurface::LookupSurface(sd.surfaceId(), !sd.isOpaque(),
                                     sd.yUVColorSpace());
}
#endif

bool GLBlitHelper::BlitSdToFramebuffer(const layers::SurfaceDescriptor& asd,
                                       const gfx::IntSize& destSize,
                                       const OriginPos destOrigin) {
  const auto sdType = asd.type();
  switch (sdType) {
    case layers::SurfaceDescriptor::TSurfaceDescriptorBuffer: {
      const auto& sd = asd.get_SurfaceDescriptorBuffer();
      const auto yuvData = PlanarYCbCrData::From(sd);
      if (!yuvData) {
        gfxCriticalNote << "[GLBlitHelper::BlitSdToFramebuffer] "
                           "PlanarYCbCrData::From failed";
        return false;
      }
      return BlitPlanarYCbCr(*yuvData, destSize, destOrigin);
    }
#ifdef XP_WIN
    case layers::SurfaceDescriptor::TSurfaceDescriptorD3D10: {
      const auto& sd = asd.get_SurfaceDescriptorD3D10();
      return BlitDescriptor(sd, destSize, destOrigin);
    }
    case layers::SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr: {
      const auto& sd = asd.get_SurfaceDescriptorDXGIYCbCr();
      return BlitDescriptor(sd, destSize, destOrigin);
    }
#endif
#ifdef XP_MACOSX
    case layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: {
      const auto& sd = asd.get_SurfaceDescriptorMacIOSurface();
      const auto surf = LookupSurface(sd);
      if (!surf) {
        NS_WARNING("LookupSurface(MacIOSurface) failed");
        // Sometimes that frame for our handle gone already. That's life, for
        // now.
        return false;
      }
      return BlitImage(surf, destSize, destOrigin);
    }
#endif
#ifdef MOZ_WIDGET_ANDROID
    case layers::SurfaceDescriptor::TSurfaceTextureDescriptor: {
      const auto& sd = asd.get_SurfaceTextureDescriptor();
      auto surfaceTexture = java::GeckoSurfaceTexture::Lookup(sd.handle());
      return Blit(surfaceTexture, destSize, destOrigin);
    }
#endif
#ifdef MOZ_WIDGET_GTK
    case layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf: {
      const auto& sd = asd.get_SurfaceDescriptorDMABuf();
      RefPtr<DMABufSurface> surface = DMABufSurface::CreateDMABufSurface(sd);
      return Blit(surface, destSize, destOrigin);
    }
#endif
    default:
      return false;
  }
}

bool GLBlitHelper::BlitImageToFramebuffer(layers::Image* const srcImage,
                                          const gfx::IntSize& destSize,
                                          const OriginPos destOrigin) {
  switch (srcImage->GetFormat()) {
    case ImageFormat::PLANAR_YCBCR: {
      const auto srcImage2 = static_cast<PlanarYCbCrImage*>(srcImage);
      const auto data = srcImage2->GetData();
      return BlitPlanarYCbCr(*data, destSize, destOrigin);
    }

    case ImageFormat::SURFACE_TEXTURE: {
#ifdef MOZ_WIDGET_ANDROID
      auto* image = srcImage->AsSurfaceTextureImage();
      MOZ_ASSERT(image);
      auto surfaceTexture =
          java::GeckoSurfaceTexture::Lookup(image->GetHandle());
      return Blit(surfaceTexture, destSize, destOrigin);
#else
      MOZ_ASSERT(false);
      return false;
#endif
    }
    case ImageFormat::MAC_IOSURFACE:
#ifdef XP_MACOSX
      return BlitImage(srcImage->AsMacIOSurfaceImage(), destSize, destOrigin);
#else
      MOZ_ASSERT(false);
      return false;
#endif

    case ImageFormat::GPU_VIDEO:
      return BlitImage(static_cast<layers::GPUVideoImage*>(srcImage), destSize,
                       destOrigin);
#ifdef XP_WIN
    case ImageFormat::D3D11_SHARE_HANDLE_TEXTURE:
      return BlitImage(static_cast<layers::D3D11ShareHandleImage*>(srcImage),
                       destSize, destOrigin);
    case ImageFormat::D3D11_TEXTURE_ZERO_COPY:
      return BlitImage(
          static_cast<layers::D3D11ZeroCopyTextureImage*>(srcImage), destSize,
          destOrigin);
    case ImageFormat::D3D9_RGB32_TEXTURE:
      return false;  // todo
    case ImageFormat::DCOMP_SURFACE:
      return false;
#else
    case ImageFormat::D3D11_SHARE_HANDLE_TEXTURE:
    case ImageFormat::D3D11_TEXTURE_ZERO_COPY:
    case ImageFormat::D3D9_RGB32_TEXTURE:
    case ImageFormat::DCOMP_SURFACE:
      MOZ_ASSERT(false);
      return false;
#endif
    case ImageFormat::DMABUF:
#ifdef MOZ_WIDGET_GTK
      return BlitImage(static_cast<layers::DMABUFSurfaceImage*>(srcImage),
                       destSize, destOrigin);
#else
      return false;
#endif
    case ImageFormat::MOZ2D_SURFACE:
    case ImageFormat::NV_IMAGE:
    case ImageFormat::OVERLAY_IMAGE:
    case ImageFormat::SHARED_RGB:
    case ImageFormat::TEXTURE_WRAPPER:
      return false;  // todo
  }
  return false;
}

// -------------------------------------

#ifdef MOZ_WIDGET_ANDROID
bool GLBlitHelper::Blit(const java::GeckoSurfaceTexture::Ref& surfaceTexture,
                        const gfx::IntSize& destSize,
                        const OriginPos destOrigin) const {
  if (!surfaceTexture) {
    return false;
  }

  const ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);

  if (!surfaceTexture->IsAttachedToGLContext((int64_t)mGL)) {
    GLuint tex;
    mGL->MakeCurrent();
    mGL->fGenTextures(1, &tex);

    if (NS_FAILED(surfaceTexture->AttachToGLContext((int64_t)mGL, tex))) {
      mGL->fDeleteTextures(1, &tex);
      return false;
    }
  }

  const ScopedBindTexture savedTex(mGL, surfaceTexture->GetTexName(),
                                   LOCAL_GL_TEXTURE_EXTERNAL);
  surfaceTexture->UpdateTexImage();

  gfx::Matrix4x4 transform;
  const auto surf = java::sdk::SurfaceTexture::LocalRef::From(surfaceTexture);
  gl::AndroidSurfaceTexture::GetTransformMatrix(surf, &transform);
  // SurfaceTexture transforms should always be 2D
  MOZ_DIAGNOSTIC_ASSERT(transform.Is2D());
  const auto transform3 = MatrixToMat3(transform.As2D());

  const auto srcOrigin = OriginPos::TopLeft;
  const bool yFlip = (srcOrigin != destOrigin);
  const auto& prog = GetDrawBlitProg(
      {kFragHeader_TexExt, {kFragSample_OnePlane, kFragConvert_None}});
  const DrawBlitProg::BaseArgs baseArgs = {transform3, yFlip, destSize,
                                           Nothing()};
  prog.Draw(baseArgs, nullptr);

  if (surfaceTexture->IsSingleBuffer()) {
    surfaceTexture->ReleaseTexImage();
  }

  return true;
}
#endif

// -------------------------------------

bool GuessDivisors(const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
                   gfx::IntSize* const out_divisors) {
  const gfx::IntSize divisors((ySize.width == uvSize.width) ? 1 : 2,
                              (ySize.height == uvSize.height) ? 1 : 2);
  if (uvSize.width * divisors.width != ySize.width ||
      uvSize.height * divisors.height != ySize.height) {
    return false;
  }
  *out_divisors = divisors;
  return true;
}

bool GLBlitHelper::BlitPlanarYCbCr(const PlanarYCbCrData& yuvData,
                                   const gfx::IntSize& destSize,
                                   const OriginPos destOrigin) {
  const auto& prog = GetDrawBlitProg(
      {kFragHeader_Tex2D, {kFragSample_ThreePlane, kFragConvert_ColorMatrix}});

  if (!mYuvUploads[0]) {
    mGL->fGenTextures(3, mYuvUploads);
    const ScopedBindTexture bindTex(mGL, mYuvUploads[0]);
    mGL->TexParams_SetClampNoMips();
    mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[1]);
    mGL->TexParams_SetClampNoMips();
    mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[2]);
    mGL->TexParams_SetClampNoMips();
  }

  // --

  auto ySize = yuvData.YDataSize();
  auto cbcrSize = yuvData.CbCrDataSize();
  if (yuvData.mYSkip || yuvData.mCbSkip || yuvData.mCrSkip || ySize.width < 0 ||
      ySize.height < 0 || cbcrSize.width < 0 || cbcrSize.height < 0 ||
      yuvData.mYStride < 0 || yuvData.mCbCrStride < 0) {
    gfxCriticalError() << "Unusual PlanarYCbCrData: " << yuvData.mYSkip << ","
                       << yuvData.mCbSkip << "," << yuvData.mCrSkip << ", "
                       << ySize.width << "," << ySize.height << ", "
                       << cbcrSize.width << "," << cbcrSize.height << ", "
                       << yuvData.mYStride << "," << yuvData.mCbCrStride;
    return false;
  }

  gfx::IntSize divisors;
  switch (yuvData.mChromaSubsampling) {
    case gfx::ChromaSubsampling::FULL:
      divisors = gfx::IntSize(1, 1);
      break;
    case gfx::ChromaSubsampling::HALF_WIDTH:
      divisors = gfx::IntSize(2, 1);
      break;
    case gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT:
      divisors = gfx::IntSize(2, 2);
      break;
    default:
      gfxCriticalError() << "Unknown chroma subsampling:"
                         << int(yuvData.mChromaSubsampling);
      return false;
  }

  // --

  // RED textures aren't valid in GLES2, and ALPHA textures are not valid in
  // desktop GL Core Profiles. So use R8 textures on GL3.0+ and GLES3.0+, but
  // LUMINANCE/LUMINANCE/UNSIGNED_BYTE otherwise.
  GLenum internalFormat;
  GLenum unpackFormat;
  if (mGL->IsAtLeast(gl::ContextProfile::OpenGLCore, 300) ||
      mGL->IsAtLeast(gl::ContextProfile::OpenGLES, 300)) {
    internalFormat = LOCAL_GL_R8;
    unpackFormat = LOCAL_GL_RED;
  } else {
    internalFormat = LOCAL_GL_LUMINANCE;
    unpackFormat = LOCAL_GL_LUMINANCE;
  }

  // --

  const ScopedSaveMultiTex saveTex(mGL, 3, LOCAL_GL_TEXTURE_2D);
  const ResetUnpackState reset(mGL);
  const gfx::IntSize yTexSize(yuvData.mYStride, yuvData.YDataSize().height);
  const gfx::IntSize uvTexSize(yuvData.mCbCrStride,
                               yuvData.CbCrDataSize().height);

  if (yTexSize != mYuvUploads_YSize || uvTexSize != mYuvUploads_UVSize) {
    mYuvUploads_YSize = yTexSize;
    mYuvUploads_UVSize = uvTexSize;

    mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
    mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[0]);
    mGL->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat, yTexSize.width,
                     yTexSize.height, 0, unpackFormat, LOCAL_GL_UNSIGNED_BYTE,
                     nullptr);
    for (int i = 1; i < 3; i++) {
      mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
      mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[i]);
      mGL->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat, uvTexSize.width,
                       uvTexSize.height, 0, unpackFormat,
                       LOCAL_GL_UNSIGNED_BYTE, nullptr);
    }
  }

  // --

  mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
  mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[0]);
  mGL->fTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0, 0, 0, yTexSize.width,
                      yTexSize.height, unpackFormat, LOCAL_GL_UNSIGNED_BYTE,
                      yuvData.mYChannel);
  mGL->fActiveTexture(LOCAL_GL_TEXTURE1);
  mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[1]);
  mGL->fTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0, 0, 0, uvTexSize.width,
                      uvTexSize.height, unpackFormat, LOCAL_GL_UNSIGNED_BYTE,
                      yuvData.mCbChannel);
  mGL->fActiveTexture(LOCAL_GL_TEXTURE2);
  mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[2]);
  mGL->fTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0, 0, 0, uvTexSize.width,
                      uvTexSize.height, unpackFormat, LOCAL_GL_UNSIGNED_BYTE,
                      yuvData.mCrChannel);

  // --

  const auto& clipRect = yuvData.mPictureRect;
  const auto srcOrigin = OriginPos::BottomLeft;
  const bool yFlip = (destOrigin != srcOrigin);

  const DrawBlitProg::BaseArgs baseArgs = {SubRectMat3(clipRect, yTexSize),
                                           yFlip, destSize, Nothing()};
  const DrawBlitProg::YUVArgs yuvArgs = {
      SubRectMat3(clipRect, uvTexSize, divisors), Some(yuvData.mYUVColorSpace)};
  prog.Draw(baseArgs, &yuvArgs);
  return true;
}

// -------------------------------------

#ifdef XP_MACOSX
bool GLBlitHelper::BlitImage(layers::MacIOSurfaceImage* const srcImage,
                             const gfx::IntSize& destSize,
                             const OriginPos destOrigin) const {
  return BlitImage(srcImage->GetSurface(), destSize, destOrigin);
}

static std::string IntAsAscii(const int x) {
  std::string str;
  str.reserve(6);
  auto u = static_cast<unsigned int>(x);
  while (u) {
    str.insert(str.begin(), u & 0xff);
    u >>= 8;
  }
  str.insert(str.begin(), '\'');
  str.push_back('\'');
  return str;
}

bool GLBlitHelper::BlitImage(MacIOSurface* const iosurf,
                             const gfx::IntSize& destSize,
                             const OriginPos destOrigin) const {
  if (!iosurf) {
    gfxCriticalError() << "Null MacIOSurface for GLBlitHelper::BlitImage";
    return false;
  }
  if (mGL->GetContextType() != GLContextType::CGL) {
    MOZ_ASSERT(false);
    return false;
  }

  const auto& srcOrigin = OriginPos::BottomLeft;

  DrawBlitProg::BaseArgs baseArgs;
  baseArgs.yFlip = (destOrigin != srcOrigin);
  baseArgs.destSize = destSize;

  // TODO: The colorspace is known by the IOSurface, why override it?
  // See GetYUVColorSpace/GetFullRange()
  DrawBlitProg::YUVArgs yuvArgs;
  yuvArgs.colorSpaceForMatrix = Some(iosurf->GetYUVColorSpace());

  const DrawBlitProg::YUVArgs* pYuvArgs = nullptr;

  auto planes = iosurf->GetPlaneCount();
  if (!planes) {
    planes = 1;  // Bad API. No cookie.
  }

  const GLenum texTarget = LOCAL_GL_TEXTURE_RECTANGLE;

  const ScopedSaveMultiTex saveTex(mGL, planes, texTarget);
  const ScopedTexture tex0(mGL);
  const ScopedTexture tex1(mGL);
  const ScopedTexture tex2(mGL);
  const GLuint texs[3] = {tex0, tex1, tex2};

  const auto pixelFormat = iosurf->GetPixelFormat();
  if (mGL->ShouldSpew()) {
    const auto formatStr = IntAsAscii(pixelFormat);
    printf_stderr("iosurf format: %s (0x%08x)\n", formatStr.c_str(),
                  pixelFormat);
  }

  const char* fragSample;
  switch (planes) {
    case 1:
      switch (pixelFormat) {
        case kCVPixelFormatType_24RGB:
        case kCVPixelFormatType_24BGR:
        case kCVPixelFormatType_32ARGB:
        case kCVPixelFormatType_32BGRA:
        case kCVPixelFormatType_32ABGR:
        case kCVPixelFormatType_32RGBA:
        case kCVPixelFormatType_64ARGB:
        case kCVPixelFormatType_48RGB:
          fragSample = kFragSample_OnePlane;
          break;
        case kCVPixelFormatType_422YpCbCr8:
        case kCVPixelFormatType_422YpCbCr8_yuvs:
          fragSample = kFragSample_OnePlane_YUV_via_GBR;
          pYuvArgs = &yuvArgs;
          break;
        default: {
          std::string str;
          if (pixelFormat <= 0xff) {
            str = std::to_string(pixelFormat);
          } else {
            str = IntAsAscii(pixelFormat);
          }
          gfxCriticalError() << "Unhandled kCVPixelFormatType_*: " << str;
          // Probably YUV though
          fragSample = kFragSample_OnePlane_YUV_via_GBR;
          pYuvArgs = &yuvArgs;
          break;
        }
      }
      break;
    case 2:
      fragSample = kFragSample_TwoPlane;
      pYuvArgs = &yuvArgs;
      break;
    case 3:
      fragSample = kFragSample_ThreePlane;
      pYuvArgs = &yuvArgs;
      break;
    default:
      gfxCriticalError() << "Unexpected plane count: " << planes;
      return false;
  }

  for (uint32_t p = 0; p < planes; p++) {
    mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + p);
    mGL->fBindTexture(texTarget, texs[p]);
    mGL->TexParams_SetClampNoMips(texTarget);

    if (!iosurf->BindTexImage(mGL, p)) {
      return false;
    }

    if (p == 0) {
      const auto width = iosurf->GetDevicePixelWidth(p);
      const auto height = iosurf->GetDevicePixelHeight(p);
      baseArgs.texMatrix0 = SubRectMat3(0, 0, width, height);
      yuvArgs.texMatrix1 = SubRectMat3(0, 0, width / 2.0, height / 2.0);
    }
  }

  const auto& prog = GetDrawBlitProg({
      kFragHeader_Tex2DRect,
      {fragSample, kFragConvert_ColorMatrix},
  });
  prog.Draw(baseArgs, pYuvArgs);
  return true;
}
#endif

// -----------------------------------------------------------------------------

void GLBlitHelper::DrawBlitTextureToFramebuffer(const GLuint srcTex,
                                                const gfx::IntSize& srcSize,
                                                const gfx::IntSize& destSize,
                                                const GLenum srcTarget,
                                                const bool srcIsBGRA) const {
  const char* fragHeader = nullptr;
  Mat3 texMatrix0;
  switch (srcTarget) {
    case LOCAL_GL_TEXTURE_2D:
      fragHeader = kFragHeader_Tex2D;
      texMatrix0 = Mat3::I();
      break;
    case LOCAL_GL_TEXTURE_RECTANGLE_ARB:
      fragHeader = kFragHeader_Tex2DRect;
      texMatrix0 = SubRectMat3(0, 0, srcSize.width, srcSize.height);
      break;
    default:
      gfxCriticalError() << "Unexpected srcTarget: " << srcTarget;
      return;
  }
  const auto fragConvert = srcIsBGRA ? kFragConvert_BGR : kFragConvert_None;
  const auto& prog = GetDrawBlitProg({
      fragHeader,
      {kFragSample_OnePlane, fragConvert},
  });

  const ScopedSaveMultiTex saveTex(mGL, 1, srcTarget);
  mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
  mGL->fBindTexture(srcTarget, srcTex);

  const bool yFlip = false;
  const DrawBlitProg::BaseArgs baseArgs = {texMatrix0, yFlip, destSize,
                                           Nothing()};
  prog.Draw(baseArgs);
}

// -----------------------------------------------------------------------------

void GLBlitHelper::BlitFramebuffer(const gfx::IntRect& srcRect,
                                   const gfx::IntRect& destRect,
                                   GLuint filter) const {
  MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit));

  const ScopedGLState scissor(mGL, LOCAL_GL_SCISSOR_TEST, false);
  mGL->fBlitFramebuffer(srcRect.x, srcRect.y, srcRect.XMost(), srcRect.YMost(),
                        destRect.x, destRect.y, destRect.XMost(),
                        destRect.YMost(), LOCAL_GL_COLOR_BUFFER_BIT, filter);
}

// --

void GLBlitHelper::BlitFramebufferToFramebuffer(const GLuint srcFB,
                                                const GLuint destFB,
                                                const gfx::IntRect& srcRect,
                                                const gfx::IntRect& destRect,
                                                GLuint filter) const {
  MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit));
  MOZ_GL_ASSERT(mGL, !srcFB || mGL->fIsFramebuffer(srcFB));
  MOZ_GL_ASSERT(mGL, !destFB || mGL->fIsFramebuffer(destFB));

  const ScopedBindFramebuffer boundFB(mGL);
  mGL->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, srcFB);
  mGL->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destFB);

  BlitFramebuffer(srcRect, destRect, filter);
}

void GLBlitHelper::BlitTextureToFramebuffer(GLuint srcTex,
                                            const gfx::IntSize& srcSize,
                                            const gfx::IntSize& destSize,
                                            GLenum srcTarget) const {
  MOZ_GL_ASSERT(mGL, mGL->fIsTexture(srcTex));

  if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
    const ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
    const ScopedBindFramebuffer bindFB(mGL);
    mGL->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, srcWrapper.FB());
    BlitFramebuffer(gfx::IntRect({}, srcSize), gfx::IntRect({}, destSize));
    return;
  }

  DrawBlitTextureToFramebuffer(srcTex, srcSize, destSize, srcTarget);
}

void GLBlitHelper::BlitFramebufferToTexture(GLuint destTex,
                                            const gfx::IntSize& srcSize,
                                            const gfx::IntSize& destSize,
                                            GLenum destTarget) const {
  MOZ_GL_ASSERT(mGL, mGL->fIsTexture(destTex));

  if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
    const ScopedFramebufferForTexture destWrapper(mGL, destTex, destTarget);
    const ScopedBindFramebuffer bindFB(mGL);
    mGL->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destWrapper.FB());
    BlitFramebuffer(gfx::IntRect({}, srcSize), gfx::IntRect({}, destSize));
    return;
  }

  ScopedBindTexture autoTex(mGL, destTex, destTarget);
  ScopedGLState scissor(mGL, LOCAL_GL_SCISSOR_TEST, false);
  mGL->fCopyTexSubImage2D(destTarget, 0, 0, 0, 0, 0, srcSize.width,
                          srcSize.height);
}

void GLBlitHelper::BlitTextureToTexture(GLuint srcTex, GLuint destTex,
                                        const gfx::IntSize& srcSize,
                                        const gfx::IntSize& destSize,
                                        GLenum srcTarget,
                                        GLenum destTarget) const {
  MOZ_GL_ASSERT(mGL, mGL->fIsTexture(srcTex));
  MOZ_GL_ASSERT(mGL, mGL->fIsTexture(destTex));

  // Start down the CopyTexSubImage path, not the DrawBlit path.
  const ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
  const ScopedBindFramebuffer bindFB(mGL, srcWrapper.FB());
  BlitFramebufferToTexture(destTex, srcSize, destSize, destTarget);
}

// -------------------------------------

bool GLBlitHelper::BlitImage(layers::GPUVideoImage* const srcImage,
                             const gfx::IntSize& destSize,
                             const OriginPos destOrigin) const {
  const auto& data = srcImage->GetData();
  if (!data) return false;

  const auto& desc = data->SD();

  MOZ_ASSERT(
      desc.type() ==
      layers::SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder);
  const auto& subdescUnion =
      desc.get_SurfaceDescriptorRemoteDecoder().subdesc();
  switch (subdescUnion.type()) {
#ifdef MOZ_WIDGET_GTK
    case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDMABuf: {
      const auto& subdesc = subdescUnion.get_SurfaceDescriptorDMABuf();
      RefPtr<DMABufSurface> surface =
          DMABufSurface::CreateDMABufSurface(subdesc);
      return Blit(surface, destSize, destOrigin);
    }
#endif
#ifdef XP_WIN
    case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10: {
      const auto& subdesc = subdescUnion.get_SurfaceDescriptorD3D10();
      return BlitDescriptor(subdesc, destSize, destOrigin);
    }
    case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDXGIYCbCr: {
      const auto& subdesc = subdescUnion.get_SurfaceDescriptorDXGIYCbCr();
      return BlitDescriptor(subdesc, destSize, destOrigin);
    }
#endif
#ifdef XP_MACOSX
    case layers::RemoteDecoderVideoSubDescriptor::
        TSurfaceDescriptorMacIOSurface: {
      const auto& subdesc = subdescUnion.get_SurfaceDescriptorMacIOSurface();
      RefPtr<MacIOSurface> surface = MacIOSurface::LookupSurface(
          subdesc.surfaceId(), !subdesc.isOpaque(), subdesc.yUVColorSpace());
      MOZ_ASSERT(surface);
      if (!surface) {
        return false;
      }
      return BlitImage(surface, destSize, destOrigin);
    }
#endif
    case layers::RemoteDecoderVideoSubDescriptor::Tnull_t:
      // This GPUVideoImage isn't directly readable outside the GPU process.
      // Abort.
      return false;
    default:
      gfxCriticalError() << "Unhandled subdesc type: "
                         << uint32_t(subdescUnion.type());
      return false;
  }
}

// -------------------------------------
#ifdef MOZ_WIDGET_GTK
bool GLBlitHelper::Blit(DMABufSurface* surface, const gfx::IntSize& destSize,
                        OriginPos destOrigin) const {
  const auto& srcOrigin = OriginPos::BottomLeft;

  DrawBlitProg::BaseArgs baseArgs;
  baseArgs.yFlip = (destOrigin != srcOrigin);
  baseArgs.destSize = destSize;

  // TODO: The colorspace is known by the DMABUFSurface, why override it?
  // See GetYUVColorSpace/GetFullRange()
  DrawBlitProg::YUVArgs yuvArgs;
  yuvArgs.colorSpaceForMatrix = Some(surface->GetYUVColorSpace());

  const DrawBlitProg::YUVArgs* pYuvArgs = nullptr;
  const auto planes = surface->GetTextureCount();

  // -
  // Ensure textures for all planes have been created.

  const bool createTextures = [&]() {
    for (int i = 0; i < planes; i++) {
      if (!surface->GetTexture(i)) {
        return true;
      }
    }
    return false;
  }();

  bool didCreateTexture = false;
  auto releaseTextures = mozilla::MakeScopeExit([&] {
    if (didCreateTexture) {
      surface->ReleaseTextures();
    }
  });

  if (createTextures) {
    for (int i = 0; i < planes; i++) {
      if (surface->GetTexture(i)) {
        continue;
      }
      if (!surface->CreateTexture(mGL, i)) {
        LOGDMABUF(("GLBlitHelper::Blit(): Failed to create DMABuf textures."));
        return false;
      }
      didCreateTexture = true;
    }
  }

  // -

  const GLenum texTarget = LOCAL_GL_TEXTURE_2D;

  const ScopedSaveMultiTex saveTex(mGL, planes, texTarget);
  const auto pixelFormat = surface->GetSurfaceType();

  const char* fragSample;
  auto fragConvert = kFragConvert_None;
  switch (pixelFormat) {
    case DMABufSurface::SURFACE_RGBA:
      fragSample = kFragSample_OnePlane;
      break;
    case DMABufSurface::SURFACE_YUV:
      if (surface->GetTextureCount() == 2) {
        fragSample = kFragSample_TwoPlane;
      } else if (surface->GetTextureCount() == 3) {
        fragSample = kFragSample_ThreePlane;
      } else {
        gfxCriticalError() << "Unexpected planes count: "
                           << surface->GetTextureCount();
        return false;
      }
      pYuvArgs = &yuvArgs;
      fragConvert = kFragConvert_ColorMatrix;
      break;
    default:
      return false;
  }

  for (const auto p : IntegerRange(planes)) {
    mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + p);
    mGL->fBindTexture(texTarget, surface->GetTexture(p));
    mGL->TexParams_SetClampNoMips(texTarget);
  }

  // We support only NV12/YUV420 formats only with 1/2 texture scale.
  // We don't set cliprect as DMABus textures are created without padding.
  baseArgs.texMatrix0 = SubRectMat3(0, 0, 1, 1);
  yuvArgs.texMatrix1 = SubRectMat3(0, 0, 1, 1);

  const auto& prog =
      GetDrawBlitProg({kFragHeader_Tex2D, {fragSample, fragConvert}});
  prog.Draw(baseArgs, pYuvArgs);

  return true;
}

bool GLBlitHelper::BlitImage(layers::DMABUFSurfaceImage* srcImage,
                             const gfx::IntSize& destSize,
                             OriginPos destOrigin) const {
  DMABufSurface* surface = srcImage->GetSurface();
  if (!surface) {
    gfxCriticalError() << "Null DMABUFSurface for GLBlitHelper::BlitImage";
    return false;
  }
  return Blit(surface, destSize, destOrigin);
}
#endif

// -

template <size_t N>
static void PushUnorm(uint32_t* const out, const float inVal) {
  const uint32_t mask = (1 << N) - 1;
  auto fval = inVal;
  fval = std::clamp(fval, 0.0f, 1.0f);
  fval *= mask;
  fval = roundf(fval);
  auto ival = static_cast<uint32_t>(fval);
  ival &= mask;

  *out <<= N;
  *out |= ival;
}

static uint32_t toRgb10A2(const color::vec4& val) {
  // R in LSB
  uint32_t ret = 0;
  PushUnorm<2>(&ret, val.w());
  PushUnorm<10>(&ret, val.z());
  PushUnorm<10>(&ret, val.y());
  PushUnorm<10>(&ret, val.x());
  return ret;
}

// -

color::ColorspaceDesc ToColorspaceDesc(const gfx::YUVRangedColorSpace cs) {
  switch (cs) {
    case gfx::YUVRangedColorSpace::BT601_Narrow:
      return {
          .chrom = color::Chromaticities::Rec601_525_Ntsc(),
          .tf = color::PiecewiseGammaDesc::Rec709(),
          .yuv =
              color::YuvDesc{
                  .yCoeffs = color::YuvLumaCoeffs::Rec709(),
                  .ycbcr = color::YcbcrDesc::Narrow8(),
              },
      };
    case gfx::YUVRangedColorSpace::BT601_Full:
      return {
          .chrom = color::Chromaticities::Rec601_525_Ntsc(),
          .tf = color::PiecewiseGammaDesc::Rec709(),
          .yuv =
              color::YuvDesc{
                  .yCoeffs = color::YuvLumaCoeffs::Rec709(),
                  .ycbcr = color::YcbcrDesc::Full8(),
              },
      };
    case gfx::YUVRangedColorSpace::BT709_Narrow:
      return {
          .chrom = color::Chromaticities::Rec709(),
          .tf = color::PiecewiseGammaDesc::Rec709(),
          .yuv =
              color::YuvDesc{
                  .yCoeffs = color::YuvLumaCoeffs::Rec709(),
                  .ycbcr = color::YcbcrDesc::Narrow8(),
              },
      };
    case gfx::YUVRangedColorSpace::BT709_Full:
      return {
          .chrom = color::Chromaticities::Rec709(),
          .tf = color::PiecewiseGammaDesc::Rec709(),
          .yuv =
              color::YuvDesc{
                  .yCoeffs = color::YuvLumaCoeffs::Rec709(),
                  .ycbcr = color::YcbcrDesc::Full8(),
              },
      };
    case gfx::YUVRangedColorSpace::BT2020_Narrow:
      return {
          .chrom = color::Chromaticities::Rec2020(),
          .tf = color::PiecewiseGammaDesc::Rec2020_12bit(),
          .yuv =
              color::YuvDesc{
                  .yCoeffs = color::YuvLumaCoeffs::Rec709(),
                  .ycbcr = color::YcbcrDesc::Narrow8(),
              },
      };
    case gfx::YUVRangedColorSpace::BT2020_Full:
      return {
          .chrom = color::Chromaticities::Rec2020(),
          .tf = color::PiecewiseGammaDesc::Rec2020_12bit(),
          .yuv =
              color::YuvDesc{
                  .yCoeffs = color::YuvLumaCoeffs::Rec2020(),
                  .ycbcr = color::YcbcrDesc::Full8(),
              },
      };
    case gfx::YUVRangedColorSpace::GbrIdentity:
      return {
          .chrom = color::Chromaticities::Rec709(),
          .tf = color::PiecewiseGammaDesc::Rec709(),
          .yuv =
              color::YuvDesc{
                  .yCoeffs = color::YuvLumaCoeffs::Gbr(),
                  .ycbcr = color::YcbcrDesc::Full8(),
              },
      };
  }
  MOZ_CRASH("Bad YUVRangedColorSpace.");
}

}  // namespace gl
namespace gfx {

color::ColorProfileDesc QueryOutputColorProfile();

}  // namespace gfx
namespace gl {

// -

/* static */
std::optional<color::ColorProfileDesc> GLBlitHelper::ToColorProfileDesc(
    const gfx::ColorSpace2 cspace) {
  color::ColorspaceDesc cspaceDesc;
  switch (cspace) {
    case gfx::ColorSpace2::Display:
      if (kIsWindows) {
#ifdef XP_WIN
        return gfx::QueryOutputColorProfile();
#endif
      }
      return {};

    case gfx::ColorSpace2::SRGB:
      cspaceDesc = {.chrom = color::Chromaticities::Srgb(),
                    .tf = color::PiecewiseGammaDesc::Srgb()};
      break;
    case gfx::ColorSpace2::DISPLAY_P3:
      cspaceDesc = {.chrom = color::Chromaticities::DisplayP3(),
                    .tf = color::PiecewiseGammaDesc::DisplayP3()};
      break;
    case gfx::ColorSpace2::BT601_525:  // aka smpte170m NTSC
      cspaceDesc = {.chrom = color::Chromaticities::Rec601_525_Ntsc(),
                    .tf = color::PiecewiseGammaDesc::Rec709()};
      break;
    case gfx::ColorSpace2::BT709:  // Same gamut as SRGB, but different gamma.
      cspaceDesc = {.chrom = color::Chromaticities::Rec709(),
                    .tf = color::PiecewiseGammaDesc::Rec709()};
      break;
    case gfx::ColorSpace2::BT2020:
      cspaceDesc = {.chrom = color::Chromaticities::Rec2020(),
                    .tf = color::PiecewiseGammaDesc::Rec2020_12bit()};
      break;
  }
  const auto profileDesc = color::ColorProfileDesc::From(cspaceDesc);
  return profileDesc;
}

// -

// For std::visit
template <class... Ts>
struct overloaded : Ts... {
  using Ts::operator()...;
};
// explicit deduction guide (not needed as of C++20)
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

// -

template <typename C, typename K>
inline auto MaybeFind(C& container, const K& key)
    -> decltype(&(container.find(key)->second)) {
  const auto itr = container.find(key);
  if (itr == container.end()) return nullptr;
  return &(itr->second);
}

// -

std::shared_ptr<gl::Texture> GLBlitHelper::GetColorLutTex(
    const ColorLutKey& request) const {
  if (const auto found = MaybeFind(mColorLutTexMap, request)) {
    return *found;  // Might be *Some(nullptr) -> nullptr!
  }

  return mColorLutTexMap[request] = [&]() -> std::shared_ptr<gl::Texture> {
    auto& gl = *mGL;
    const auto tex = std::make_shared<gl::Texture>(gl);

    // -

    const std::optional<color::ColorProfileDesc> srcProfile =
        std::visit(overloaded{
                       [&](const gfx::ColorSpace2& cs)
                           -> std::optional<color::ColorProfileDesc> {
                         MOZ_ASSERT(cs != request.dst);
                         const auto cpd = ToColorProfileDesc(cs);
                         return cpd;
                       },
                       [&](const gfx::YUVRangedColorSpace& cs)
                           -> std::optional<color::ColorProfileDesc> {
                         const auto csd = ToColorspaceDesc(cs);
                         const auto cpd = color::ColorProfileDesc::From(csd);
                         return cpd;
                       },
                   },
                   request.src);
    MOZ_ASSERT(srcProfile);

    const auto dstProfile = ToColorProfileDesc(request.dst);
    if (kIsWindows) {
      MOZ_ASSERT(dstProfile);
    }
    if (!srcProfile || !dstProfile) return nullptr;
    const auto conversion = color::ColorProfileConversionDesc::From({
        .src = *srcProfile,
        .dst = *dstProfile,
    });

    // -

    const auto minLutSize = color::ivec3{2};
    const auto maxLutSize = color::ivec3{256};
    auto lutSize = minLutSize;
    const bool isYcbcr =
        (conversion.srcRgbFromSrcYuv != color::mat4::Identity());
    if (isYcbcr) {
      lutSize.x(int(StaticPrefs::gfx_blithelper_lut_size_ycbcr_y()));
      lutSize.y(int(StaticPrefs::gfx_blithelper_lut_size_ycbcr_cb()));
      lutSize.z(int(StaticPrefs::gfx_blithelper_lut_size_ycbcr_cr()));
    } else {
      lutSize.x(int(StaticPrefs::gfx_blithelper_lut_size_rgb_r()));
      lutSize.y(int(StaticPrefs::gfx_blithelper_lut_size_rgb_g()));
      lutSize.z(int(StaticPrefs::gfx_blithelper_lut_size_rgb_b()));
    }
    lutSize = clamp(lutSize, minLutSize, maxLutSize);

    const auto lut = [&]() {
      auto lut = color::Lut3::Create(lutSize);
      lut.SetMap(
          [&](const color::vec3& src) { return conversion.DstFromSrc(src); });
      return lut;
    }();
    const auto& size = lut.size;

    // -

    constexpr GLenum target = LOCAL_GL_TEXTURE_3D;
    const auto bind = gl::ScopedBindTexture(&gl, tex->name, target);
    gl.fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
    gl.fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
    gl.fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_R, LOCAL_GL_CLAMP_TO_EDGE);
    gl.fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
    gl.fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);

    bool useFloat16 = true;
    if (useFloat16) {
      // Use rgba16f, which we can thankfully upload as rgba32f
      static_assert(sizeof(color::vec4) == sizeof(float) * 4);
      std::vector<color::vec4> uploadData;
      uploadData.reserve(lut.data.size());
      for (const auto& src : lut.data) {
        const auto dst = color::vec4{src, 1};
        uploadData.push_back(dst);
      }

      gl.fTexStorage3D(target, 1, LOCAL_GL_RGBA16F, size.x(), size.y(),
                       size.z());
      gl.fTexSubImage3D(target, 0, 0, 0, 0, size.x(), size.y(), size.z(),
                        LOCAL_GL_RGBA, LOCAL_GL_FLOAT, uploadData.data());
    } else {
      // Use Rgb10A2
      std::vector<uint32_t> uploadData;
      uploadData.reserve(lut.data.size());
      for (const auto& src : lut.data) {
        const auto dst = toRgb10A2({src, 1});
        uploadData.push_back(dst);
      }

      gl.fTexStorage3D(target, 1, LOCAL_GL_RGB10_A2, size.x(), size.y(),
                       size.z());
      gl.fTexSubImage3D(target, 0, 0, 0, 0, size.x(), size.y(), size.z(),
                        LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV,
                        uploadData.data());
    }
    return tex;
  }();
}

}  // namespace gl
}  // namespace mozilla

Messung V0.5
C=91 H=97 G=93

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge