Android TextureView / Dibujo / Rendimiento de la pintura

Estoy intentando hacer una aplicación de dibujo / pintura usando TextureView en Android. Quiero apoyar una superficie de dibujo de hasta 4096×4096 píxeles, lo que parece razonable para mi dispositivo de destino mínimo (que utilizo para las pruebas), que es un Google Nexus 7 2013 que tiene una CPU de núcleo cuádruple bonito y 2GB de memoria.

Uno de mis requisitos es que mi vista debe estar dentro de una vista que permite que sea ampliado y desplegado y panorámico, que es todo el código personalizado que he escrito (piense UIScrollView de iOS).

He intentado usar una vista regular (no TextureView ) con OnDraw y el rendimiento era absolutamente horrible – menos de 1 fotograma por segundo. Esto sucedió aunque llamé Invalidate(rect) con sólo el rect que cambió. He intentado desactivar la aceleración de hardware para la vista, pero nada prestados, supongo que 4096×4096 es demasiado grande para el software.

Entonces intenté usar TextureView y el funcionamiento es un poco mejor – cerca de 5-10 cuadros por segundo (todavía terrible pero mejor). El usuario dibuja en un mapa de bits, que luego se dibuja en la textura con un hilo de fondo. Estoy usando Xamarin pero espero que el código tenga sentido para la gente de Java.

 private void RunUpdateThread() { try { TimeSpan sleep = TimeSpan.FromSeconds(1.0f / 60.0f); while (true) { lock (dirtyRect) { if (dirtyRect.Width() > 0 && dirtyRect.Height() > 0) { Canvas c = LockCanvas(dirtyRect); if (c != null) { c.DrawBitmap(bitmap, dirtyRect, dirtyRect, bufferPaint); dirtyRect.Set(0, 0, 0, 0); UnlockCanvasAndPost(c); } } } Thread.Sleep(sleep); } } catch { } } 

Si cambio lockCanvas para pasar null en lugar de rect, el rendimiento es excelente a 60 fps, pero el contenido del parpadeo TextureView se corrompe, lo cual es decepcionante. Hubiera pensado que simplemente estaría usando un buffer de cuadros de OpenGL / textura de renderizado debajo o por lo menos tendría una opción para preservar el contenido.

¿Hay otras opciones de hacer todo en OpenGL sin procesar en Android para dibujo y pintura de alto rendimiento en una superficie que se conserva entre las llamadas de sorteo?

2 Solutions collect form web for “Android TextureView / Dibujo / Rendimiento de la pintura”

En primer lugar, si quieres entender lo que está pasando bajo el capó, es necesario leer el documento de arquitectura de gráficos de Android . Es largo, pero si usted sinceramente quiere entender el "por qué", es el lugar para comenzar.

Acerca de TextureView

TextureView funciona así: tiene una superficie, que es una cola de búferes con una relación productor-consumidor. Si está utilizando renderizado de software (lienzo), bloquea la superficie, lo que le da un búfer; Usted dibuja en él; A continuación, desbloquear la superficie, que envía el búfer al consumidor. El consumidor en este caso está en el mismo proceso, y se llama SurfaceTexture o (internamente, más acertadamente) GLConsumer. Convierte el búfer en una textura de OpenGL ES, que se renderiza a la vista.

Si desactiva la aceleración de hardware, GLES se desactiva y TextureView no puede hacer nada. Por eso no obtuvo nada cuando desactivó la aceleración de hardware. La documentación es muy específica: "TextureView sólo se puede utilizar en una ventana acelerada por hardware. Cuando se procesa en software, TextureView no dibujará nada".

Si especifica un rectángulo sucio, el procesador de software registrará el contenido anterior en el fotograma después de completar la representación. No creo que se establezca un clip rect, por lo que si llama drawColor (), se llena toda la pantalla, y luego tienen los píxeles sobrescritos. Si actualmente no está configurando un clip rect, es posible que vea algún beneficio en el rendimiento al hacerlo. (No he comprobado el código sin embargo.)

El rect desechado es un parámetro in-out. Usted pasa el rect que usted quiere adentro cuando usted llama a lockCanvas() , y el sistema se permite modificarlo antes de que la llamada vuelva. (En la práctica, la única razón por la que haría esto sería si no había un marco anterior o la superficie se han cambiado de tamaño, en cuyo caso se ampliará para cubrir toda la pantalla. Creo que esto habría sido mejor manejado con una más directa "Yo rechazo su señal rect".) Se le requiere que actualice cada píxel dentro del rect que regrese. No se le permite alterar el rect, que parece que está intentando hacer en su muestra – lo que está en el rect de sucio después de lockCanvas() tiene éxito es lo que se requiere para dibujar.

Sospecho que el manejo incorrecto de rectos sucios es la fuente de su parpadeo. Lamentablemente, este es un error fácil de hacer, ya que el comportamiento de la lockCanvas() dirtyRect arg sólo está documentado en la propia clase Surface .

Superficies y amortiguación

Todas las superficies son de doble o triple buffer. No hay manera alrededor de esto – no se puede leer y escribir simultáneamente y no obtener lagrimeo. Si desea un único búfer que puede modificar y empujar cuando lo desee, el búfer tendrá que ser bloqueado, copiado y desbloqueado, lo que crea puestos en el pipeline de composición. Para obtener el mejor rendimiento y latencia, voltear los búferes es mejor.

Si quieres el comportamiento de bloqueo-copia-desbloqueo, puedes escribirlo tú mismo (o encontrar una biblioteca que lo haga), y será tan eficiente como lo sería si el sistema lo hiciera por ti (asumiendo que eres bueno con Blit loops). Dibuja a un lienzo fuera de pantalla y blit el mapa de bits, o a un OpenBO ES FBO y blit el búfer. Puede encontrar un ejemplo de este último en la actividad de Grafika " registro GL app ", que tiene un modo que hace una vez fuera de pantalla, y luego blits dos veces (una vez para la pantalla, una vez para la grabación de vídeo).

Más velocidad y

Hay dos maneras básicas de dibujar píxeles en Android: con Canvas, o con OpenGL. La renderización de lienzo a una Superficie o Bitmap siempre se hace en software, mientras que la renderización de OpenGL se hace con la GPU. La única excepción es que, al renderizar a una vista personalizada , puede optar por utilizar aceleración de hardware, pero esto no se aplica al renderizar a la superficie de una SurfaceView o TextureView.

Una aplicación de dibujo o pintura puede recordar los comandos de dibujo, o simplemente lanzar píxeles en un búfer y utilizarlo como su memoria. El primero permite un "deshacer" más profundo, este último es mucho más simple, y tiene un rendimiento cada vez mejor a medida que crece la cantidad de material a renderizar. Suena como si quisieras hacer esto último, por lo que blitting de fuera de la pantalla tiene sentido.

La mayoría de los dispositivos móviles tienen una limitación de hardware de 4096 x 4096 o menor para texturas GLES, por lo que no podrá utilizar una sola textura para algo más grande. Puede consultar el valor de límite de tamaño (GL_MAX_TEXTURE_SIZE), pero es posible que esté mejor con un búfer interno que sea tan grande como desee, y sólo renderizar la parte que se ajusta en la pantalla. No sé cuál es la limitación Skia (lienzo) es improvisado, pero creo que puede crear Bitmaps mucho más grande.

Dependiendo de sus necesidades, un SurfaceView puede ser preferible a un TextureView, ya que evita el paso de textura intermedio GLES. Cualquier cosa que dibuje en la superficie va directamente al compositor del sistema (SurfaceFlinger). El lado negativo de este enfoque es que, debido a que el consumidor de Surface no está en proceso, no hay oportunidad para que el sistema View maneje la salida, por lo que Surface es una capa independiente. (Para un programa de dibujo esto podría ser beneficioso: la imagen que se está dibujando está en una capa, la interfaz de usuario está en una capa separada en la parte superior).

FWIW, no he mirado el código, pero la aplicación de marcadores de Dan Sandler podría valer la pena un vistazo (código fuente aquí ).

Actualización: la corrupción fue identificada como un error y arreglada en 'L' .

ACTUALIZAR He abandonado TextureView y ahora uso una vista de OpenGL donde llamo a glTexSubImage2D para actualizar las piezas modificadas de una textura de renderizado.

ANTIGUA RESPUESTA Terminé tejiendo TextureView en una cuadrícula de 4×4. Dependiendo del color de cada cuadro, actualizo las vistas de TextureView correspondientes. Cualquier vista que no se actualice el marco que llamo Invalidar en.

Algunos dispositivos, como el teléfono Moto G, tienen un problema en el que la memoria intermedia doble está dañada para un fotograma. Puedes solucionarlo llamando a lockCanvas dos veces cuando la vista padre tiene su onLayout llamado.

 private void InvalidateRect(int l, int t, int r, int b) { dirtyRect.Set(l, t, r, b); foreach (DrawSubView v in drawViews) { if (Rect.Intersects(dirtyRect, v.Clip)) { v.RedrawAsync(); } else { v.Invalidate(); } } Invalidate(); } protected override void OnLayout(bool changed, int l, int t, int r, int b) { for (int i = 0; i < ChildCount; i++) { View v = GetChildAt(i); v.Layout(v.Left, v.Top, v.Right, v.Bottom); DrawSubView sv = v as DrawSubView; if (sv != null) { sv.RedrawAsync(); // why are we re-drawing you ask? because of double buffering bugs in Android :) PostDelayed(() => sv.RedrawAsync(), 50); } } } 
  • Los métodos SurfaceTexture no siempre se utilizan en Android 7.0
  • TextureView vs GLSurfaceView o Cómo utilizar GLSurfaceView con EGL14
  • Android comparte SurfaceTexture entre dos procesos
  • Textura transparente semi-transparente no funciona
  • Android: reproduce el mismo video en varias vistas
  • ¿Es esto posible tener la opinión de la textura para la cámara en forma circular para el androide?
  • SurfaceView vs TextureView para la cámara?
  • Android SDK - camera2 - Dibujar rectángulo sobre TextureView
  • Se ha abandonado BufferQueue: Al reproducir vídeo con TextureView
  • Obtener Bitmap de TextureView eficientemente
  • ¿Cómo es que una vista previa de la cámara en una vista de textura es mucho más borrosa que en una vista de superficie?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.