无法将Android WebView渲染到C 和Java代码之间共享的外部纹理中



我当前正在尝试将Android WebView内容渲染成纹理,该纹理可以使用JNI和NDK在C 应用程序中使用。我不知道为什么它不正常。

我在网络上阅读了很多文档,这是我现在拥有的:

C 侧

// Create the texture
uint texture;
glGenTextures(1, &texture);
// Bind the texture with the proper external texture target
glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
// Create the EGLImage object that maps the GraphicBuffer
int usage = GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_READ_OFTEN | GraphicBuffer::USAGE_SW_WRITE_OFTEN;
auto gralloc = new GraphicBuffer(width, height, PIXEL_FORMAT_RGBA_8888, usage);
EGLClientBuffer clientBuffer = (EGLClientBuffer) gralloc->getNativeBuffer();
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
auto eglImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglImage);

在像素着色器中,我使用了特定于外部纹理的新型采样器:

// See https://developer.android.com/reference/android/graphics/SurfaceTexture.html
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES uDiffuseMap;
varying vec2 vVertexUV;
void main(void)
{
    gl_FragColor = texture2D(uDiffuseMap, vVertexUV);
}

要检查我可以正确采样纹理,我已经进行了一些测试,以填充 GraphicBuffer 使用类似的特定数据:

unsigned char *pixels = nullptr;
gralloc->lock(GraphicBuffer::USAGE_SW_READ_OFTEN | GraphicBuffer::USAGE_SW_WRITE_OFTEN, (void **) &pixels);
std::memcpy(pixels, data, width * height * 4);
gralloc->unlock();

,我确认这是按预期的工作,当我在Pixel着色器中采样外部纹理时,图形挡板中写的数据是检索到的数据。

现在,让我们看看它是如何完成Java的一面以相同纹理渲染WebView的:

java side

customRenderer.java (实现glsurfaceview.renderer(:

@Override
public void onSurfaceChanged(GL10 gl, int width, int height)
{
    // glTextureId is given by the C++ through JNI and correspond 
    // to the ID returns by glGenTextures() call
    surfaceTexture = new SurfaceTexture(glTextureId);
    surfaceTexture.setDefaultBufferSize(width, height);
    surface = new Surface(surfaceTexture);
}
@Override
public void onDrawFrame(GL10 gl)
{
    synchronized(this)
    {
        surfaceTexture.updateTexImage();
    }
}
public Canvas onDrawViewBegin()
{
    surfaceCanvas = surface.lockCanvas(null);
}
public void onDrawViewEnd()
{
    surface.unlockCanvasAndPost(surfaceCanvas);
}

customwebview.java (扩展WebView(

@Override
public void draw(Canvas canvas) {
    if (customRenderer == null)
    {
        super.draw(canvas);
        return;
    }
    // Returns canvas attached to OpenGL texture to draw on
    Canvas glAttachedCanvas = customRenderer.onDrawViewBegin();
    if (glAttachedCanvas != null)
    {
        // Draw the view to provided canvas
        super.draw(glAttachedCanvas);
    }
    else
    {
        super.draw(canvas);
        return;
    }
    // Notify the canvas is updated
    customRenderer.onDrawViewEnd();
}

我愿意删除所有错误检查以具有有史以来最简单的代码。

我使用此代码得到的结果让我认为纹理不是Java侧写的。

你知道会发生什么吗?我做错了吗?

我几天前终于找到了答案。我认为问题是SurfaceSurfaceTexture对象在相同的OpenGL上下文中未安装(Java sene(,而不是纹理创建(C 侧(。

我发现了使用JNI直接从C 创建SurfaceTextureSurface对象的项目。然后,我通过纹理名称,而是通过JNI表面并使用它来检索画布并将网络视图渲染到其中。

C side

auto textureName = 1; // Should be retrieved using glGenTextures() function
auto textureWidth = 512;
auto textureHeight = 512;
// Retrieve the JNI environment, using SDL, it looks like that
auto env = (JNIEnv*)SDL_AndroidGetJNIEnv();
// Create a SurfaceTexture using JNI
const jclass surfaceTextureClass = env->FindClass("android/graphics/SurfaceTexture");
// Find the constructor that takes an int (texture name)
const jmethodID surfaceTextureConstructor = env->GetMethodID(surfaceTextureClass, "<init>", "(I)V" );
jobject surfaceTextureObject = env->NewObject(surfaceTextureClass, surfaceTextureConstructor, textureName);
jobject jniSurfaceTexture = env->NewGlobalRef(surfaceTextureObject);
// To update the SurfaceTexture content
jmethodId updateTexImageMethodId = env->GetMethodID(surfaceTextureClass, "updateTexImage", "()V");
// To update the SurfaceTexture size
jmethodId setDefaultBufferSizeMethodId = env->GetMethodID(surfaceTextureClass, "setDefaultBufferSize", "(II)V" );
// Create a Surface from the SurfaceTexture using JNI
const jclass surfaceClass = env->FindClass("android/view/Surface");
const jmethodID surfaceConstructor = env->GetMethodID(surfaceClass, "<init>", "(Landroid/graphics/SurfaceTexture;)V");
jobject surfaceObject = env->NewObject(surfaceClass, surfaceConstructor, jniSurfaceTexture);
jobject jniSurface = env->NewGlobalRef(surfaceObject);
// Now that we have a globalRef, we can free the localRef
env->DeleteLocalRef(surfaceTextureObject);
env->DeleteLocalRef(surfaceTextureClass);
env->DeleteLocalRef(surfaceObject);
env->DeleteLocalRef(surfaceClass);
// Don't forget to update the size of the SurfaceTexture
env->CallVoidMethod(jniSurfaceTexture, setDefaultBufferSizeMethodId, textureWidth, textureHeight);
// Get the method to pass the Surface object to the WebView
jmethodId setWebViewRendererSurfaceMethod = env->GetMethodID(webViewClass, "setWebViewRendererSurface", "(Landroid/view/Surface;)V");
// Pass the JNI Surface object to the Webview
env->CallVoidMethod(webView, setWebViewRendererSurfaceMethod, jniSurface);

在同一线程中的JNI SurfaceTexture对象上调用updateTexImage()以更新OpenGL纹理内容很重要。因此,我们甚至不再需要一个GLSurfaceView(就我而言,仅用于定期更新纹理内容(:

env->CallVoidMethod(jniSurfaceTexture, updateTexImageMethodId);

java侧,您只需要存储从C 代码提供的Surface对象,然后在Overriden draw()函数中使用它。

java side

public class CustomWebView extends WebView
{
    private Surface _webViewSurface;
    public void setWebViewSurface(Surface webViewSurface)
    {
        _webViewSurface = webViewSurface;
    }
    @Override
    public void draw(Canvas canvas) {
        if (_webViewSurface == null)
        {
            super.draw(canvas);
            return;
        }
        // Returns canvas attached to OpenGL texture to draw on
        Canvas glAttachedCanvas = _webViewSurface.lockCanvas(null);
        if (glAttachedCanvas != null)
        {
            // Draw the view to provided canvas
            super.draw(glAttachedCanvas);
        }
        else
        {
            super.draw(canvas);
            return;
        }
        _webViewSurface.unlockCanvasAndPost(glAttachedCanvas);
    }
}

这很简单。如果您使用正确的目标(GL_TEXTURE_EXTERNAL_OES(绑定纹理并使用正确的采样器(samplerExternalOES(,则无需使用EGL映像(因此,不再使用GraphicBuffer!(,它应该按预期工作。

最新更新