无法将Android WebView呈现为C ++和Java代码之间共享的外部纹理

时间:2017-06-01 09:45:56

标签: java android c++ webview android-ndk

我目前正在尝试将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();

我确认它按预期工作,在GraphicBuffer中写入的数据是我在像素着色器中对外部纹理进行采样时检索的数据。

现在,让我们看一下Java端如何在相同的纹理中渲染WebView:

Java方

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端编写的。

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

1 个答案:

答案 0 :(得分:2)

我几天前终于找到了答案。我假设问题是SurfaceSurfaceTexture对象在同一个OpenGL上下文中没有实现(Java端)而不是纹理创建(C ++端)。

我找到this project,其中SurfaceTextureSurface个对象是使用JNI直接从C ++创建的。然后,我不是传递纹理名称,而是传递JNI Surface并使用它来检索Canvas并将WebView渲染到其中。

C ++方

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 updateTexImage()对象上的SurfaceTexture来更新OpenGL纹理内容非常重要。因此,我们甚至不再需要GLSurfaceView(在我的情况下,仅用于定期更新纹理内容):

env->CallVoidMethod(jniSurfaceTexture, updateTexImageMethodId);

Java方面,您只需要存储C ++代码提供的Surface对象,并在覆盖draw()函数中使用它来绘制它。

Java方

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);
    }
}

就这么简单。如果将纹理与正确的目标(GraphicBuffer)绑定并使用正确的采样器({{1}),则不需要使用EGL图像(因此,不再使用GL_TEXTURE_EXTERNAL_OES!) }),它应该按预期工作。