gecko-dev/gfx/gl/GLBlitHelper.cpp
Milan Sreckovic 099cfc4242 Bug 1423570: Use BaseRect access methods instead of member variables in gfx/ r=bas.schouten
MozReview-Commit-ID: ZGySgc9oP3

--HG--
extra : rebase_source : 23aadc10e9885002290155684b2c495780d979ce
2017-12-19 15:48:39 -05:00

1249 lines
46 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 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 "gfxUtils.h"
#include "GLBlitHelper.h"
#include "GLContext.h"
#include "GLScreenBuffer.h"
#include "ScopedGLHelpers.h"
#include "mozilla/Preferences.h"
#include "ImageContainer.h"
#include "HeapCopyOfStackArray.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/UniquePtr.h"
#include "GPUVideoImage.h"
#ifdef MOZ_WIDGET_ANDROID
#include "GeneratedJNIWrappers.h"
#include "AndroidSurfaceTexture.h"
#include "GLImages.h"
#include "GLLibraryEGL.h"
#endif
#ifdef XP_MACOSX
#include "MacIOSurfaceImage.h"
#include "GLContextCGL.h"
#endif
using mozilla::layers::PlanarYCbCrImage;
using mozilla::layers::PlanarYCbCrData;
namespace mozilla {
namespace gl {
// --
const char* const kFragHeader_Tex2D = "\
#define SAMPLER sampler2D \n\
#if __VERSION__ >= 130 \n\
#define TEXTURE texture \n\
#else \n\
#define TEXTURE texture2D \n\
#endif \n\
";
const char* const kFragHeader_Tex2DRect = "\
#define SAMPLER sampler2DRect \n\
#if __VERSION__ >= 130 \n\
#define TEXTURE texture \n\
#else \n\
#define TEXTURE texture2DRect \n\
#endif \n\
";
const char* const kFragHeader_TexExt = "\
#extension GL_OES_EGL_image_external : require \n\
#if __VERSION__ >= 130 \n\
#define TEXTURE texture \n\
#else \n\
#define TEXTURE texture2D \n\
#endif \n\
#define SAMPLER samplerExternalOES \n\
";
const char* const kFragBody_RGBA = "\
VARYING vec2 vTexCoord0; \n\
uniform SAMPLER uTex0; \n\
\n\
void main(void) \n\
{ \n\
FRAG_COLOR = TEXTURE(uTex0, vTexCoord0); \n\
} \n\
";
const char* const kFragBody_CrYCb = "\
VARYING vec2 vTexCoord0; \n\
uniform SAMPLER uTex0; \n\
uniform MAT4X3 uColorMatrix; \n\
\n\
void main(void) \n\
{ \n\
vec4 yuv = vec4(TEXTURE(uTex0, vTexCoord0).gbr, \n\
1.0); \n\
FRAG_COLOR = vec4((uColorMatrix * yuv).rgb, 1.0); \n\
} \n\
";
const char* const kFragBody_NV12 = "\
VARYING vec2 vTexCoord0; \n\
VARYING vec2 vTexCoord1; \n\
uniform SAMPLER uTex0; \n\
uniform SAMPLER uTex1; \n\
uniform MAT4X3 uColorMatrix; \n\
\n\
void main(void) \n\
{ \n\
vec4 yuv = vec4(TEXTURE(uTex0, vTexCoord0).x, \n\
TEXTURE(uTex1, vTexCoord1).xy, \n\
1.0); \n\
FRAG_COLOR = vec4((uColorMatrix * yuv).rgb, 1.0); \n\
} \n\
";
const char* const kFragBody_PlanarYUV = "\
VARYING vec2 vTexCoord0; \n\
VARYING vec2 vTexCoord1; \n\
uniform SAMPLER uTex0; \n\
uniform SAMPLER uTex1; \n\
uniform SAMPLER uTex2; \n\
uniform MAT4X3 uColorMatrix; \n\
\n\
void main(void) \n\
{ \n\
vec4 yuv = vec4(TEXTURE(uTex0, vTexCoord0).x, \n\
TEXTURE(uTex1, vTexCoord1).x, \n\
TEXTURE(uTex2, vTexCoord1).x, \n\
1.0); \n\
FRAG_COLOR = vec4((uColorMatrix * yuv).rgb, 1.0); \n\
} \n\
";
// --
template<uint8_t N>
/*static*/ Mat<N>
Mat<N>::Zero()
{
Mat<N> ret;
for (auto& x : ret.m) {
x = 0.0f;
}
return ret;
}
template<uint8_t N>
/*static*/ Mat<N>
Mat<N>::I()
{
auto ret = Mat<N>::Zero();
for (uint8_t i = 0; i < N; i++) {
ret.at(i,i) = 1.0f;
}
return ret;
}
template<uint8_t N>
Mat<N>
Mat<N>::operator*(const Mat<N>& r) const
{
Mat<N> ret;
for (uint8_t x = 0; x < N; x++) {
for (uint8_t y = 0; y < N; y++) {
float sum = 0.0f;
for (uint8_t i = 0; i < N; i++) {
sum += at(i,y) * r.at(x,i);
}
ret.at(x,y) = sum;
}
}
return ret;
}
Mat3
SubRectMat3(const float x, const float y, const float w, const float h)
{
auto ret = Mat3::Zero();
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);
}
// --
ScopedSaveMultiTex::ScopedSaveMultiTex(GLContext* const gl, const uint8_t texCount,
const GLenum texTarget)
: mGL(*gl)
, mTexCount(texCount)
, mTexTarget(texTarget)
, mOldTexUnit(mGL.GetIntAs<GLenum>(LOCAL_GL_ACTIVE_TEXTURE))
{
GLenum texBinding;
switch (mTexTarget) {
case LOCAL_GL_TEXTURE_2D:
texBinding = LOCAL_GL_TEXTURE_BINDING_2D;
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;
}
for (uint8_t i = 0; i < mTexCount; i++) {
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()
{
for (uint8_t i = 0; i < mTexCount; i++) {
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
{
GLContext& mGL;
const GLuint mOldVBO;
public:
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);
mGL.fColorMask(true, true, true, true);
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());
}
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_uColorMatrix(mParent.mGL->fGetUniformLocation(mProg, "uColorMatrix"))
, mType_uColorMatrix(0)
{
MOZ_ASSERT(mLoc_uDestMatrix != -1);
MOZ_ASSERT(mLoc_uTexMatrix0 != -1);
if (mLoc_uColorMatrix != -1) {
MOZ_ASSERT(mLoc_uTexMatrix1 != -1);
const auto& gl = mParent.mGL;
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_ASSERT(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);
const auto& colorMatrix = gfxUtils::YuvToRgbMatrix4x4ColumnMajor(argsYUV->colorSpace);
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;
if (mParent.mQuadVAO) {
oldVAO = gl->GetIntAs<GLuint>(LOCAL_GL_VERTEX_ARRAY_BINDING);
gl->fBindVertexArray(mParent.mQuadVAO);
} else {
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);
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);
}
gl->fVertexAttribPointer(0, vaa0Size, vaa0Type, bool(vaa0Normalized), vaa0Stride,
vaa0Pointer);
}
}
// --
GLBlitHelper::GLBlitHelper(GLContext* const gl)
: mGL(gl)
, mQuadVAO(0)
, mQuadVBO(0)
, mDrawBlitProg_VertShader(mGL->fCreateShader(LOCAL_GL_VERTEX_SHADER))
, mYuvUploads{0}
, 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();
// Always use 100 on ES 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
// this is a lot easier and is not harmful to other usages.
if (mGL->IsGLES()) {
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 char* const parts[] = {
mDrawBlitProg_VersionLine.get(),
kVertSource
};
mGL->fShaderSource(mDrawBlitProg_VertShader, ArrayLength(parts), parts, nullptr);
mGL->fCompileShader(mDrawBlitProg_VertShader);
}
GLBlitHelper::~GLBlitHelper()
{
for (const auto& pair : mDrawBlitProgs) {
const auto& ptr = pair.second;
delete ptr;
}
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
{
const auto& res = mDrawBlitProgs.insert({key, nullptr});
auto& pair = *(res.first);
const auto& didInsert = res.second;
if (didInsert) {
pair.second = CreateDrawBlitProg(pair.first);
}
return pair.second;
}
const DrawBlitProg*
GLBlitHelper::CreateDrawBlitProg(const DrawBlitProg::Key& key) const
{
const char kFragHeader_Global[] = "\
#ifdef GL_ES \n\
#ifdef GL_FRAGMENT_PRECISION_HIGH \n\
precision highp float; \n\
#else \n\
precision mediump float; \n\
#endif \n\
#endif \n\
\n\
#if __VERSION__ >= 130 \n\
#define VARYING in \n\
#define FRAG_COLOR oFragColor \n\
out vec4 FRAG_COLOR; \n\
#else \n\
#define VARYING varying \n\
#define FRAG_COLOR gl_FragColor \n\
#endif \n\
\n\
#if __VERSION__ >= 120 \n\
#define MAT4X3 mat4x3 \n\
#else \n\
#define MAT4X3 mat4 \n\
#endif \n\
";
const ScopedShader fs(mGL, LOCAL_GL_FRAGMENT_SHADER);
const char* const parts[] = {
mDrawBlitProg_VersionLine.get(),
key.fragHeader,
kFragHeader_Global,
key.fragBody
};
mGL->fShaderSource(fs, ArrayLength(parts), parts, nullptr);
mGL->fCompileShader(fs);
const auto prog = mGL->fCreateProgram();
mGL->fAttachShader(prog, mDrawBlitProg_VertShader);
mGL->fAttachShader(prog, fs);
mGL->fBindAttribLocation(prog, 0, "aPosition");
mGL->fLinkProgram(prog);
GLenum status = 0;
mGL->fGetProgramiv(prog, LOCAL_GL_LINK_STATUS, (GLint*)&status);
if (status == LOCAL_GL_TRUE) {
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)
break;
mGL->fUniform1i(loc, i);
}
return new 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;
gfxCriticalError() << "DrawBlitProg link failed:\n"
<< "progLog: " << progLog.get() << "\n"
<< "vsLog: " << vsLog.get() << "\n"
<< "fsLog: " << fsLog.get() << "\n";
return nullptr;
}
// -----------------------------------------------------------------------------
bool
GLBlitHelper::BlitImageToFramebuffer(layers::Image* const srcImage,
const gfx::IntSize& destSize,
const OriginPos destOrigin)
{
switch (srcImage->GetFormat()) {
case ImageFormat::PLANAR_YCBCR:
return BlitImage(static_cast<PlanarYCbCrImage*>(srcImage), destSize, destOrigin);
#ifdef MOZ_WIDGET_ANDROID
case ImageFormat::SURFACE_TEXTURE:
return BlitImage(static_cast<layers::SurfaceTextureImage*>(srcImage), destSize,
destOrigin);
#endif
#ifdef XP_MACOSX
case ImageFormat::MAC_IOSURFACE:
return BlitImage(srcImage->AsMacIOSurfaceImage(), destSize, destOrigin);
#endif
#ifdef XP_WIN
case ImageFormat::GPU_VIDEO:
return BlitImage(static_cast<layers::GPUVideoImage*>(srcImage), destSize,
destOrigin);
case ImageFormat::D3D11_YCBCR_IMAGE:
return BlitImage((layers::D3D11YCbCrImage*)srcImage, destSize, destOrigin);
case ImageFormat::D3D9_RGB32_TEXTURE:
return false; // todo
#endif
default:
gfxCriticalError() << "Unhandled srcImage->GetFormat(): "
<< uint32_t(srcImage->GetFormat());
return false;
}
}
// -------------------------------------
#ifdef MOZ_WIDGET_ANDROID
bool
GLBlitHelper::BlitImage(layers::SurfaceTextureImage* srcImage, const gfx::IntSize& destSize,
const OriginPos destOrigin) const
{
AndroidSurfaceTextureHandle handle = srcImage->GetHandle();
const auto& surfaceTexture = java::GeckoSurfaceTexture::Lookup(handle);
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 transform4;
AndroidSurfaceTexture::GetTransformMatrix(java::sdk::SurfaceTexture::Ref::From(surfaceTexture),
&transform4);
Mat3 transform3;
transform3.at(0,0) = transform4._11;
transform3.at(0,1) = transform4._12;
transform3.at(0,2) = transform4._14;
transform3.at(1,0) = transform4._21;
transform3.at(1,1) = transform4._22;
transform3.at(1,2) = transform4._24;
transform3.at(2,0) = transform4._41;
transform3.at(2,1) = transform4._42;
transform3.at(2,2) = transform4._44;
// We don't do w-divison, so if these aren't what we expect, we're probably doing
// something wrong.
MOZ_ASSERT(transform3.at(0,2) == 0);
MOZ_ASSERT(transform3.at(1,2) == 0);
MOZ_ASSERT(transform3.at(2,2) == 1);
const auto& srcOrigin = srcImage->GetOriginPos();
// I honestly have no idea why this logic is flipped, but changing the
// source origin would mean we'd have to flip it in the compositor
// which makes just as little sense as this.
const bool yFlip = (srcOrigin == destOrigin);
const auto& prog = GetDrawBlitProg({kFragHeader_TexExt, kFragBody_RGBA});
MOZ_RELEASE_ASSERT(prog);
// There is no padding on these images, so we can use the GetTransformMatrix directly.
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::BlitImage(layers::PlanarYCbCrImage* const yuvImage,
const gfx::IntSize& destSize, const OriginPos destOrigin)
{
const auto& prog = GetDrawBlitProg({kFragHeader_Tex2D, kFragBody_PlanarYUV});
MOZ_RELEASE_ASSERT(prog);
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();
}
// --
const PlanarYCbCrData* const yuvData = yuvImage->GetData();
if (yuvData->mYSkip || yuvData->mCbSkip || yuvData->mCrSkip ||
yuvData->mYSize.width < 0 || yuvData->mYSize.height < 0 ||
yuvData->mCbCrSize.width < 0 || yuvData->mCbCrSize.height < 0 ||
yuvData->mYStride < 0 || yuvData->mCbCrStride < 0)
{
gfxCriticalError() << "Unusual PlanarYCbCrData: "
<< yuvData->mYSkip << ","
<< yuvData->mCbSkip << ","
<< yuvData->mCrSkip << ", "
<< yuvData->mYSize.width << ","
<< yuvData->mYSize.height << ", "
<< yuvData->mCbCrSize.width << ","
<< yuvData->mCbCrSize.height << ", "
<< yuvData->mYStride << ","
<< yuvData->mCbCrStride;
return false;
}
gfx::IntSize divisors;
if (!GuessDivisors(yuvData->mYSize, yuvData->mCbCrSize, &divisors)) {
gfxCriticalError() << "GuessDivisors failed:"
<< yuvData->mYSize.width << ","
<< yuvData->mYSize.height << ", "
<< yuvData->mCbCrSize.width << ","
<< yuvData->mCbCrSize.height;
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->mYSize.height);
const gfx::IntSize uvTexSize(yuvData->mCbCrStride, yuvData->mCbCrSize.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->GetPictureRect();
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),
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
{
MacIOSurface* const iosurf = srcImage->GetSurface();
if (mGL->GetContextType() != GLContextType::CGL) {
MOZ_ASSERT(false);
return false;
}
const auto glCGL = static_cast<GLContextCGL*>(mGL);
const auto cglContext = glCGL->GetCGLContext();
const auto& srcOrigin = OriginPos::BottomLeft;
DrawBlitProg::BaseArgs baseArgs;
baseArgs.yFlip = (destOrigin != srcOrigin);
baseArgs.destSize = destSize;
DrawBlitProg::YUVArgs yuvArgs;
yuvArgs.colorSpace = YUVColorSpace::BT601;
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 char* const fragHeader = kFragHeader_Tex2DRect;
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();
const auto formatChars = (const char*)&pixelFormat;
const char formatStr[] = {
formatChars[3],
formatChars[2],
formatChars[1],
formatChars[0],
0
};
if (mGL->ShouldSpew()) {
printf_stderr("iosurf format: %s (0x%08x)\n", formatStr, uint32_t(pixelFormat));
}
const char* fragBody;
GLenum internalFormats[3] = {0, 0, 0};
GLenum unpackFormats[3] = {0, 0, 0};
GLenum unpackTypes[3] = { LOCAL_GL_UNSIGNED_BYTE,
LOCAL_GL_UNSIGNED_BYTE,
LOCAL_GL_UNSIGNED_BYTE };
switch (planes) {
case 1:
fragBody = kFragBody_RGBA;
internalFormats[0] = LOCAL_GL_RGBA;
unpackFormats[0] = LOCAL_GL_RGBA;
break;
case 2:
fragBody = kFragBody_NV12;
if (mGL->Version() >= 300) {
internalFormats[0] = LOCAL_GL_R8;
unpackFormats[0] = LOCAL_GL_RED;
internalFormats[1] = LOCAL_GL_RG8;
unpackFormats[1] = LOCAL_GL_RG;
} else {
internalFormats[0] = LOCAL_GL_LUMINANCE;
unpackFormats[0] = LOCAL_GL_LUMINANCE;
internalFormats[1] = LOCAL_GL_LUMINANCE_ALPHA;
unpackFormats[1] = LOCAL_GL_LUMINANCE_ALPHA;
}
pYuvArgs = &yuvArgs;
break;
case 3:
fragBody = kFragBody_PlanarYUV;
if (mGL->Version() >= 300) {
internalFormats[0] = LOCAL_GL_R8;
unpackFormats[0] = LOCAL_GL_RED;
} else {
internalFormats[0] = LOCAL_GL_LUMINANCE;
unpackFormats[0] = LOCAL_GL_LUMINANCE;
}
internalFormats[1] = internalFormats[0];
internalFormats[2] = internalFormats[0];
unpackFormats[1] = unpackFormats[0];
unpackFormats[2] = unpackFormats[0];
pYuvArgs = &yuvArgs;
break;
default:
gfxCriticalError() << "Unexpected plane count: " << planes;
return false;
}
if (pixelFormat == '2vuy') {
fragBody = kFragBody_CrYCb;
// APPLE_rgb_422 adds RGB_RAW_422_APPLE for `internalFormat`, but only RGB seems
// to work?
internalFormats[0] = LOCAL_GL_RGB;
unpackFormats[0] = LOCAL_GL_RGB_422_APPLE;
unpackTypes[0] = LOCAL_GL_UNSIGNED_SHORT_8_8_APPLE;
pYuvArgs = &yuvArgs;
}
for (uint32_t p = 0; p < planes; p++) {
mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + p);
mGL->fBindTexture(texTarget, texs[p]);
mGL->TexParams_SetClampNoMips(texTarget);
const auto width = iosurf->GetDevicePixelWidth(p);
const auto height = iosurf->GetDevicePixelHeight(p);
auto err = iosurf->CGLTexImageIOSurface2D(cglContext, texTarget,
internalFormats[p], width, height,
unpackFormats[p], unpackTypes[p], p);
if (err) {
const nsPrintfCString errStr("CGLTexImageIOSurface2D(context, target, 0x%04x,"
" %u, %u, 0x%04x, 0x%04x, iosurfPtr, %u) -> %i",
internalFormats[p], uint32_t(width),
uint32_t(height), unpackFormats[p],
unpackTypes[p], p, err);
gfxCriticalError() << errStr.get() << " (iosurf format: " << formatStr << ")";
return false;
}
if (p == 0) {
baseArgs.texMatrix0 = SubRectMat3(0, 0, width, height);
yuvArgs.texMatrix1 = SubRectMat3(0, 0, width / 2.0, height / 2.0);
}
}
const auto& prog = GetDrawBlitProg({fragHeader, fragBody});
if (!prog)
return false;
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
{
const char* fragHeader;
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& prog = GetDrawBlitProg({ fragHeader, kFragBody_RGBA});
MOZ_ASSERT(prog);
const ScopedSaveMultiTex saveTex(mGL, 1, srcTarget);
mGL->fBindTexture(srcTarget, srcTex);
const bool yFlip = false;
const DrawBlitProg::BaseArgs baseArgs = { texMatrix0, yFlip, destSize, Nothing() };
prog->Draw(baseArgs);
}
// -----------------------------------------------------------------------------
void
GLBlitHelper::BlitFramebuffer(const gfx::IntSize& srcSize,
const gfx::IntSize& destSize) const
{
MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit));
const ScopedGLState scissor(mGL, LOCAL_GL_SCISSOR_TEST, false);
mGL->fBlitFramebuffer(0, 0, srcSize.width, srcSize.height,
0, 0, destSize.width, destSize.height,
LOCAL_GL_COLOR_BUFFER_BIT,
LOCAL_GL_NEAREST);
}
// --
void
GLBlitHelper::BlitFramebufferToFramebuffer(const GLuint srcFB, const GLuint destFB,
const gfx::IntSize& srcSize,
const gfx::IntSize& destSize) const
{
MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit));
MOZ_ASSERT(!srcFB || mGL->fIsFramebuffer(srcFB));
MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
const ScopedBindFramebuffer boundFB(mGL);
mGL->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, srcFB);
mGL->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destFB);
BlitFramebuffer(srcSize, destSize);
}
void
GLBlitHelper::BlitTextureToFramebuffer(GLuint srcTex, const gfx::IntSize& srcSize,
const gfx::IntSize& destSize,
GLenum srcTarget) const
{
MOZ_ASSERT(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(srcSize, destSize);
return;
}
DrawBlitTextureToFramebuffer(srcTex, srcSize, destSize, srcTarget);
}
void
GLBlitHelper::BlitFramebufferToTexture(GLuint destTex,
const gfx::IntSize& srcSize,
const gfx::IntSize& destSize,
GLenum destTarget) const
{
MOZ_ASSERT(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(srcSize, 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_ASSERT(mGL->fIsTexture(srcTex));
MOZ_ASSERT(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);
}
} // namespace gl
} // namespace mozilla