diff options
Diffstat (limited to 'gfx/gl/GLBlitHelper.cpp')
-rw-r--r-- | gfx/gl/GLBlitHelper.cpp | 1684 |
1 files changed, 1684 insertions, 0 deletions
diff --git a/gfx/gl/GLBlitHelper.cpp b/gfx/gl/GLBlitHelper.cpp new file mode 100644 index 0000000000..dd9fa00235 --- /dev/null +++ b/gfx/gl/GLBlitHelper.cpp @@ -0,0 +1,1684 @@ +/* -*- 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/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/D3D11TextureIMFSampleImage.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 char* const kFragHeader_Tex2D = R"( + #define SAMPLER sampler2D + #if __VERSION__ >= 130 + #define TEXTURE texture + #else + #define TEXTURE texture2D + #endif +)"; +const char* const kFragHeader_Tex2DRect = R"( + #define SAMPLER sampler2DRect + #if __VERSION__ >= 130 + #define TEXTURE texture + #else + #define TEXTURE texture2DRect + #endif +)"; +const char* const 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 char* const 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 char* const 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 char* const 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 char* const 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 char* const kFragConvert_None = R"( + vec3 metaConvert(vec3 src) { + return src; + } +)"; +const char* const kFragConvert_BGR = R"( + vec3 metaConvert(vec3 src) { + return src.bgr; + } +)"; +const char* const kFragConvert_ColorMatrix = R"( + uniform mediump MAT4X3 uColorMatrix; + + vec3 metaConvert(vec3 src) { + return (uColorMatrix * vec4(src, 1)).rgb; + } +)"; +const char* const kFragConvert_ColorLut = R"( + uniform PRECISION sampler3D uColorLut; + + vec3 metaConvert(vec3 src) { + // Half-texel filtering hazard! + // E.g. For texture size of 2, + // E.g. 0.5/2=0.25 is still sampling 100% of texel 0, 0% of texel 1. + // For the LUT, we need 0.5/2=0.25 to filter 25/75 texel 0 and 1. + // That is, we need to adjust our sampling point such that it's 0.25 of the + // way from texel 0's center to texel 1's center. + // 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; + } +)"; + +// - + +const char* const kFragMixin_AlphaMultColors = R"( + #define MIXIN_ALPHA_MULT_COLORS +)"; +const char* const kFragMixin_AlphaClampColors = R"( + #define MIXIN_ALPHA_CLAMP_COLORS +)"; +const char* const 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); +} + +// -- + +ScopedSaveMultiTex::ScopedSaveMultiTex(GLContext* const gl, + const std::vector<uint8_t>& texUnits, + const GLenum texTarget) + : mGL(*gl), + mTexUnits(texUnits), + mTexTarget(texTarget), + mOldTexUnit(mGL.GetIntAs<GLenum>(LOCAL_GL_ACTIVE_TEXTURE)) { + MOZ_RELEASE_ASSERT(texUnits.size() >= 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.size())) { + const auto& unit = mTexUnits[i]; + mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + unit); + if (mGL.IsSupported(GLFeature::sampler_objects)) { + mOldTexSampler[i] = mGL.GetIntAs<GLuint>(LOCAL_GL_SAMPLER_BINDING); + mGL.fBindSampler(unit, 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.size()))) { + const auto& unit = mTexUnits[i]; + mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + unit); + if (mGL.IsSupported(GLFeature::sampler_objects)) { + mGL.fBindSampler(unit, 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, true, true, true, true); + } else { + 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()); + } + + 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); + + if (args.texUnitForColorLut) { + gl->fUniform1i(mLoc_uColorLut, + AssertedCast<GLint>(*args.texUnitForColorLut)); + } + + 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 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 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 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; + + 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_IMF_SAMPLE: + return BlitImage( + static_cast<layers::D3D11TextureIMFSampleImage*>(srcImage), destSize, + destOrigin); + case ImageFormat::D3D11_YCBCR_IMAGE: + return BlitImage(static_cast<layers::D3D11YCbCrImage*>(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_IMF_SAMPLE: + case ImageFormat::D3D11_YCBCR_IMAGE: + 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(); + const auto transform3 = Mat3::I(); + // const auto srcOrigin = OriginPos::TopLeft; + const auto srcOrigin = OriginPos::BottomLeft; + 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, {0, 1, 2}, 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 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; + + // 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; + + std::vector<uint8_t> texUnits; + for (uint8_t i = 0; i < planes; i++) { + texUnits.push_back(i); + } + const ScopedSaveMultiTex saveTex(mGL, texUnits, 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); + + auto err = iosurf->CGLTexImageIOSurface2D(mGL, cglContext, p); + if (err) { + 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, {0}, 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; + + std::vector<uint8_t> texUnits; + for (uint8_t i = 0; i < planes; i++) { + texUnits.push_back(i); + } + const ScopedSaveMultiTex saveTex(mGL, texUnits, 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_NV12: + fragSample = kFragSample_TwoPlane; + pYuvArgs = &yuvArgs; + fragConvert = kFragConvert_ColorMatrix; + break; + case DMABufSurface::SURFACE_YUV420: + fragSample = kFragSample_ThreePlane; + pYuvArgs = &yuvArgs; + fragConvert = kFragConvert_ColorMatrix; + break; + default: + gfxCriticalError() << "Unexpected pixel format: " << pixelFormat; + 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::max(0.0f, std::min(fval, 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; +} + +std::shared_ptr<gl::Texture> GLBlitHelper::GetColorLutTex( + const ColorLutKey& key) const { + auto& weak = mColorLutTexMap[key]; + auto strong = weak.lock(); + if (!strong) { + auto& gl = *mGL; + strong = std::make_shared<gl::Texture>(gl); + weak = strong; + + const auto ct = color::ColorspaceTransform::Create(key.src, key.dst); + + // - + + const auto minLutSize = color::ivec3{2}; + const auto maxLutSize = color::ivec3{256}; + auto lutSize = minLutSize; + if (ct.srcSpace.yuv) { + 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 = max(minLutSize, min(lutSize, maxLutSize)); // Clamp + + const auto lut = ct.ToLut3(lutSize); + const auto& size = lut.size; + + // - + + constexpr GLenum target = LOCAL_GL_TEXTURE_3D; + const auto bind = gl::ScopedBindTexture(&gl, strong->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 strong; +} + +} // namespace gl +} // namespace mozilla |