/* -*- 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 "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_WAYLAND # include "mozilla/layers/DMABUFSurfaceImage.h" # include "mozilla/widget/DMABufSurface.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& texUnits, const GLenum texTarget) : mGL(*gl), mTexUnits(texUnits), mTexTarget(texTarget), mOldTexUnit(mGL.GetIntAs(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(LOCAL_GL_SAMPLER_BINDING); mGL.fBindSampler(unit, 0); } mOldTex[i] = mGL.GetIntAs(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(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(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 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(*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(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 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(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 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(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 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 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 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 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_WAYLAND case layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf: { const auto& sd = asd.get_SurfaceDescriptorDMABuf(); RefPtr 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(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(srcImage), destSize, destOrigin); #ifdef XP_WIN case ImageFormat::D3D11_SHARE_HANDLE_TEXTURE: return BlitImage(static_cast(srcImage), destSize, destOrigin); case ImageFormat::D3D11_TEXTURE_IMF_SAMPLE: return BlitImage( static_cast(srcImage), destSize, destOrigin); case ImageFormat::D3D11_YCBCR_IMAGE: return BlitImage(static_cast(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_WAYLAND return BlitImage(static_cast(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(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(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 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_WAYLAND case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDMABuf: { const auto& subdesc = subdescUnion.get_SurfaceDescriptorDMABuf(); RefPtr 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 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_WAYLAND 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 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 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(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 GLBlitHelper::GetColorLutTex( const ColorLutKey& key) const { auto& weak = mColorLutTexMap[key]; auto strong = weak.lock(); if (!strong) { auto& gl = *mGL; strong = std::make_shared(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 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 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