OpenGL/Skia 渲染 GL_TEXTURE_EXTERNAL_OES 到 GL_TEXTURE_2D

mro*_*avy 11 c++ android opengl-es skia android-camera2

我的 OpenGL/Skia Android Camera2 应用程序遇到奇怪的问题。

我的相机将帧渲染为SurfaceTexture,这是GL_TEXTURE_EXTERNAL_OESOpenGL 中的纹理。

然后,我可以使用简单的直通着色器简单地将这个 OpenGL 纹理渲染到所有输出(1920x1080 Preview EGLSurface、4000x2000 Video Recorder )。EGLSurface

Camera --> GL_TEXTURE_EXTERNAL_OES

GL_TEXTURE_EXTERNAL_OES --> PassThroughShader --> Preview Output EGLSurface
GL_TEXTURE_EXTERNAL_OES --> PassThroughShader --> Video Recorder Output EGLSurface
Run Code Online (Sandbox Code Playgroud)

现在我想将 Skia 引入其中,它允许我在将其传递到输出之前渲染到相机框架上(例如,在框架上绘制一个红色框)。由于我无法GL_TEXTURE_EXTERNAL_OES再次直接渲染到相同的对象上,因此我创建了一个单独的离屏纹理 ( GL_TEXTURE_2D) 和一个单独的离屏帧缓冲区 (FBO1) 并附加了它们。

现在,当我渲染到 FBO1 时,屏幕外纹理GL_TEXTURE_2D会更新,然后我想将其传递GL_TEXTURE_2D到我的输出:

Camera --> GL_TEXTURE_EXTERNAL_OES
GL_TEXTURE_EXTERNAL_OES --> Skia to FBO1 + drawing a red box --> GL_TEXTURE_2D

GL_TEXTURE_2D --> PassThroughShader --> Preview Output EGLSurface
GL_TEXTURE_2D --> PassThroughShader --> Video Recorder Output EGLSurface
Run Code Online (Sandbox Code Playgroud)

但由于某种原因,这只绘制了第一帧,然后卡住并在屏幕上呈现奇怪的故障伪像。请参阅此处的视频演示: https://github.com/mrousavy/react-native-vision-camera/assets/15199031/254a5455-b9cf-4c1e-9cb9-85f9b60d0cd5

相关文件是:

  1. onFrame(..):相机将新的 Frame 放入_inputTexture( GL_TEXTURE_EXTERNAL_OES) 后调用。
    Camera --> GL_TEXTURE_EXTERNAL_OES
    
    GL_TEXTURE_EXTERNAL_OES --> PassThroughShader --> Preview Output EGLSurface
    GL_TEXTURE_EXTERNAL_OES --> PassThroughShader --> Video Recorder Output EGLSurface
    
    Run Code Online (Sandbox Code Playgroud)
  2. SkiaRenderer::renderFrame(..):调用将 Frame 渲染到屏幕外纹理 ( GL_TEXTURE_2D) 上并添加一些额外的 Skia 命令(绘制红色矩形)。
    Camera --> GL_TEXTURE_EXTERNAL_OES
    GL_TEXTURE_EXTERNAL_OES --> Skia to FBO1 + drawing a red box --> GL_TEXTURE_2D
    
    GL_TEXTURE_2D --> PassThroughShader --> Preview Output EGLSurface
    GL_TEXTURE_2D --> PassThroughShader --> Video Recorder Output EGLSurface
    
    Run Code Online (Sandbox Code Playgroud)
  3. OpenGLRenderer::renderTextureToSurface(..):使用新渲染到GL_TEXTURE_2D包含帧和红色矩形的离屏纹理 ( ) 进行调用。这会将框架渲染到输出 EGLSurface。
    // Camera texture
    OpenGLTexture& cameraTexture = _inputTexture.value();
    
    // Render to new texture using Skia
    auto newTexture = skia->renderFrame(_glContext, cameraTexture);
    
    // Reset the bindings
    glBindTexture(GL_TEXTURE_2D, newTexture.id);
    glBindFramebuffer(GL_FRAMEBUFFER, DEFAULT_FRAMEBUFFER);
    
    // Render to all outputs
    if (_previewOutput) {
      _previewOutput->renderTextureToSurface(newTexture, transformMatrix);
    }
    if (_recordingSessionOutput) {
      _recordingSessionOutput->renderTextureToSurface(newTexture, transformMatrix);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. PassThroughShader::draw(..):实际上对包含我的 Skia 绘图的 2D 纹理进行直通渲染。是它使用的着色器。(sampler2D而不是samplerExternalOES,对吧?)
    if (_skiaContext == nullptr) {
      _skiaContext = GrDirectContext::MakeGL();
    }
    
    if (_offscreenSurface == nullptr) {
      GrBackendTexture skiaTex = _skiaContext->createBackendTexture(cameraTexture.width,
                                                                    cameraTexture.height,
                                                                    SkColorType::kN32_SkColorType,
                                                                    GrMipMapped::kNo,
                                                                    GrRenderable::kYes);
      GrGLTextureInfo info;
      skiaTex.getGLTextureInfo(&info);
      _offscreenSurfaceTextureId = info.fID;
    
      SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
      _offscreenSurface = SkSurfaces::WrapBackendTexture(_skiaContext.get(),
                                                         skiaTex,
                                                         kBottomLeft_GrSurfaceOrigin,
                                                         0,
                                                         SkColorType::kN32_SkColorType,
                                                         nullptr,
                                                         &props,
                                                         // TODO: Delete texture!
                                                         nullptr);
    }
    
    GrGLTextureInfo textureInfo {
        // OpenGL will automatically convert YUV -> RGB because it's an EXTERNAL texture
        .fTarget = GL_TEXTURE_EXTERNAL_OES,
        .fID = cameraTexture.id,
        .fFormat = GR_GL_RGBA8,
        .fProtected = skgpu::Protected::kNo,
    };
    GrBackendTexture skiaTexture(cameraTexture.width,
                                 cameraTexture.height,
                                 GrMipMapped::kNo,
                                 textureInfo);
    sk_sp<SkImage> frame = SkImages::BorrowTextureFrom(_skiaContext.get(),
                                                       skiaTexture,
                                                       kBottomLeft_GrSurfaceOrigin,
                                                       kN32_SkColorType,
                                                       kOpaque_SkAlphaType,
                                                       nullptr,
                                                       nullptr);
    
    
    SkCanvas* canvas = _offscreenSurface->getCanvas();
    
    canvas->clear(SkColors::kCyan);
    
    canvas->drawImage(frame, 0, 0);
    
    SkRect rect = SkRect::MakeXYWH(150, 250, random() * 200, random() * 400);
    SkPaint paint;
    paint.setColor(SkColors::kGreen);
    canvas->drawRect(rect, paint);
    
    _offscreenSurface->flushAndSubmit();
    
    Run Code Online (Sandbox Code Playgroud)

着色器在这里:

if (_surface == EGL_NO_SURFACE) {
  _context->makeCurrent();
  _surface = eglCreateWindowSurface(_context->display, _context->config, _outputSurface, nullptr);
}

// 1. Activate the OpenGL context for this surface
_context->makeCurrent(_surface);

// 2. Set the viewport for rendering
glViewport(0, 0, _width, _height);
glDisable(GL_BLEND);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f); // <-- red for debug
glClear(GL_COLOR_BUFFER_BIT);

// 3. Bind the input texture
glBindTexture(GL_TEXTURE_2D, newTexture.id);

// 4. Draw it using the pass-through shader which also applies transforms
_passThroughShader.draw(newTexture, transformMatrix);

// 5. Swap buffers to pass it to the window surface
_context->flush();
Run Code Online (Sandbox Code Playgroud)

OpenGL 设置在这里,我基本上有一个 1x1 pbuffer 用于创建我的纹理,然后我在输出 EGLSurfaces 之间切换以进行渲染。当我渲染到离屏帧缓冲区 (TEXTURE_2D) 时,1x1 pbuffer 处于活动状态。

我不确定 Skia 是否正在执行一些我应该撤消的纹理/缓冲区绑定,是否存在内存问题,是否直通着色器错误,或者变换矩阵是否也需要应用于 Skia 着色器。

如果你想看一下这个;

  1. 克隆此 PR/分支:https://github.com/mrousavy/react-native-vision-camera/pull/1731
  2. 跑步yarn && cd example && yarn
  3. example/android在 Android Studio 中打开

任何帮助表示赞赏!