我当前正在尝试将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侧写的。
你知道会发生什么吗?我做错了吗?
我几天前终于找到了答案。我认为问题是Surface
和SurfaceTexture
对象在相同的OpenGL上下文中未安装(Java sene(,而不是纹理创建(C 侧(。
我发现了使用JNI直接从C 创建SurfaceTexture
和Surface
对象的项目。然后,我通过纹理名称,而是通过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
!(,它应该按预期工作。