Android TextureView / Zeichnung / Malerei-Performance
Ich bin versucht, eine Zeichnung/Malerei-app mit TextureView
auf Android. Ich will eine Zeichen-Oberfläche von bis zu 4096x4096 Pixeln, das scheint vernünftig für mein Mindest-Ziel-Gerät (die ich zum testen verwenden), die ist ein Google Nexus 7 2013 hat einen schönen quad-core-CPU und 2GB Speicher.
Einer meiner Anforderungen ist, dass meiner Meinung nach muss innerhalb einer Sicht, die es erlaubt die ein-und ausgezoomt werden und verrissen, die alle benutzerdefinierten code, den ich geschrieben habe (glaube UIScrollView von iOS).
Habe ich versucht, mit einer normalen Ansicht (nicht TextureView
) mit OnDraw und Leistung war absolut schrecklich - weniger als 1 frame die Sekunde. Dies passiert auch, wenn ich rief Invalidate(rect)
nur mit dem rect, der geändert wurde. Ich habe versucht, das ausschalten der hardware-Beschleunigung für die Ansicht, aber nichts gerendert, ich vermute, weil 4096x4096 ist zu groß für die software.
Habe ich dann versucht, mit TextureView
und die performance ist ein wenig besser - ungefähr 5-10 frames pro Sekunde (noch schrecklich, aber besser). Der Benutzer zieht in eine bitmap, die dann später gezogen, in der textur mit einem hintergrund-thread. Ich bin mit Xamarin, sondern hoffentlich auch den code sinnvoll zu Java-Menschen.
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
{
}
}
Wenn ich lockCanvas zu übergeben Sie null anstelle von einem rect, Leistung ist Super bei 60 fps, aber der Inhalt der TextureView
flimmern und beschädigt werden, was enttäuschend ist. Ich hätte gedacht, es würde einfach sein, mit einem OpenGL frame buffer /render-textur unter, oder zumindest eine option zu wahren Inhalt.
Gibt es andere Optionen kurz, tun alles in raw OpenGL in Android für high-performance-zeichnen und malen auf einer Oberfläche befindet, erhalten in zwischen den draw calls?
- 4096x4096 ist ziemlich riesig. Ich bin nicht überrascht, Sie haben performance-Probleme. Benötigen Sie unbedingt ein Bild so groß? Sie vielleicht überprüfen Sie heraus die Grafik-Architektur: source.android.com/devices/graphics/architecture.html.
- Ja, dass ist eine Anforderung. Ich habe gebaut eine ähnliche app auf iOS mithilfe von core graphics, UIScrollView und auf dem iPad Air, die Leistung ist einfach in Ordnung zu 4096x4096. Die 2013 Nexus 7 soll besser als das iPad Air oder zumindest vergleichbar würde ich denken.
- Was wirklich bringt mich ist, dass die Leistung Super, wenn ich übergeben Sie null, um die lockCanvas nennen, aber dann den textur-Inhalt scheint beschädigt zu bekommen... es ist sehr seltsam, dass lockCanvas(rect) wäre langsamer als ein lockCanvas(null)...
Du musst angemeldet sein, um einen Kommentar abzugeben.
First off, wenn Sie wollen, um zu verstehen, was Los ist unter der Haube, Sie müssen Lesen Sie die Android-Grafik-Architektur-Dokument. Es ist lang, aber wenn Sie ernsthaft wollen, um zu verstehen, "warum", es ist der Ort, um zu starten.
Über TextureView
TextureView funktioniert wie folgt: es hat eine Oberfläche, die eine Puffer-queue mit einer Erzeuger-Verbraucher-Beziehung. Wenn Sie mit einer software (Canvas) rendering, sperren Sie die Oberfläche, die Ihnen einen Puffer; Sie auf Sie zu ziehen; dann entsperren Sie die Oberfläche, die sendet die Puffer an den Verbraucher. Der Verbraucher in diesem Fall ist der gleiche Prozess, und wird als SurfaceTexture oder (intern, mehr treffend) GLConsumer. Es wandelt die Puffer in eine OpenGL-ES-textur, die dann gerendert, um die Ansicht.
Wenn Sie die Hardwarebeschleunigung deaktivieren, GLES ist deaktiviert, und TextureView kann nichts tun. Dies ist der Grund, warum Sie nichts bekommen, wenn Sie sich die hardware-Beschleunigung ausschalten. Die Dokumentation ist sehr spezifisch: "TextureView kann nur verwendet werden, in eine hardware-beschleunigte Fenster. Beim Rendern in der software, TextureView ziehen wird nichts."
Wenn Sie angeben, eine schmutzige rect, den software-renderer wird memcpy die bisherigen Inhalte in den Rahmen nach das Rendern abgeschlossen ist. Ich glaube nicht, setzt es eine clip-rect, so dass, wenn Sie nennen drawColor(), Sie füllt den gesamten Bildschirm, und dann haben diejenigen Pixel überschrieben. Wenn Sie derzeit nicht die Einstellung einer clip-rect, sehen Sie möglicherweise einige performance-Vorteil bringt. (Ich habe nicht überprüfen Sie den code, obwohl.)
Schmutzigen rect ist ein in-out-parameter. Übergeben Sie die rect-Sie wollen, wenn Sie rufen
lockCanvas()
, und das system ist erlaubt, um es zu ändern, bevor Sie den Anruf zurück. (In der Praxis, der einzige Grund, warum es dies tun würde, wenn es keine vorherigen frame oder die Fläche wurden verkleinert, in dem Fall würde es ausweiten auf den gesamten Bildschirm. Ich denke, das hätte besser gehandhabt werden mit einem direkten "ich lehne Ihre rect" - signal.) Sie sind erforderlich, um ein update alle pixel innerhalb des rect erhalten Sie zurück. Sie sind nicht erlaubt zu ändern das rect, was Sie zu sein scheinen, versuchen zu tun, in Ihrem Beispiel-was ist in der "dirty" rect " nachlockCanvas()
gelingt, ist das, was Sie sind erforderlich, um zu zeichnen.Ich vermute, das schmutzige rect mis-handling ist die Quelle Ihrer flackern. Leider, dies ist eine einfache Fehler zu machen, wie das Verhalten der
lockCanvas()
dirtyRect
arg ist nur dokumentiert die Oberfläche Klasse selbst.Flächen und Puffern
Alle Oberflächen sind Doppel - oder dreifach-gepuffert. Es gibt keinen Weg, um dieses -- Sie können nicht Lesen und schreiben gleichzeitig und nicht reißen. Wenn Sie möchten, einen einzigen Puffer, die Sie ändern können, und schieben, wenn gewünscht, die Puffer müssen gesperrt werden, kopiert und aufgehoben, wodurch Stände in der Zusammensetzung der pipeline. Für den besten Durchsatz und die Latenz, spiegeln Puffer ist besser.
Wenn Sie möchten, dass die lock-kopieren-unlock Verhalten, Sie können schreiben, dass Sie sich selbst (oder eine Bibliothek, die das tut es), und es wird so effizient, wie es wäre, wenn das system hat es für Sie (vorausgesetzt, Sie sind gut mit blit-loops). Zeichnen Sie auf ein off-screen-Leinwand und blit-bitmap-oder eine OpenGL-ES-FBO und blit-Puffer. Sie finden ein Beispiel für die letztere in der Grafika ' s " Rekord GL app " - Aktivitäten, das hat einen Modus, der macht einmal im off-screen, und dann blits zweimal (einmal für die Anzeige, sobald für die Aufzeichnung von video).
Mehr speed und solche
Gibt es zwei grundlegende Methoden zum zeichnen von Pixeln auf Android: mit Leinwand, oder mit OpenGL. Canvas-rendering auf eine Oberfläche oder Bitmap erfolgt immer in der software, während OpenGL-rendering erfolgt mit der GPU. Die einzige Ausnahme ist, dass bei der Wiedergabe auf einem benutzerdefinierte Ansicht, können Sie entscheiden, die Nutzung von hardware-Beschleunigung, aber dies gilt nicht, wenn das Rendern der Oberfläche einer SurfaceView oder TextureView.
Einer Zeichnung oder Malerei app können entweder erinnere mich an die Zeichnung Befehle, oder einfach nur werfen die Pixel-buffer und den Gebrauch, die als Speicher. Der ehemalige ermöglicht tiefere "rückgängig machen", letzteres ist viel einfacher, und hat immer eine bessere Leistung als die Menge der Sachen zu Rendern wächst. Es klingt wie Sie wollen, um letzteres zu tun, so blitting von off-screen Sinn macht.
Meisten mobilen Geräte haben eine hardware-Einschränkung von 4096x4096 oder kleiner für die GLES-Texturen, so dass Sie nicht in der Lage sein, die Verwendung einer einzelnen textur für die etwas größeren. Sie können die Abfrage der Größe Grenzwert (GL_MAX_TEXTURE_SIZE), aber Sie können es besser mit einer internen Puffer, der ist so groß, wie Sie wollen, und Rendern Sie einfach den Teil, der auf das Display passt. Ich weiß nicht, was die Skia (Canvas) Einschränkung ist die offhand, aber ich glaube, Sie können erstellen viel größere Bitmaps.
Je nach Ihren Bedürfnissen, eine SurfaceView möglicherweise vorzuziehen, eine TextureView, wie es vermeidet, das Zwischenprodukt GLES textur Schritt. Alles, was Sie zeichnen auf der Oberfläche geht direkt an den system-compositor (SurfaceFlinger). Die Kehrseite dieses Ansatzes ist, dass, weil die Oberfläche der Verbraucher ist nicht in-Prozess, es gibt keine Möglichkeit für das View system, die Ausgabe zu Steuern, so dass die Oberfläche ist eine unabhängige Schicht. (Für eine Zeichnung Programm, das dieses von Vorteil sein könnte -- das Bild gezeichnet wird, ist auf einer Ebene, die Benutzeroberfläche ist auf eine separate Schicht auf der Oberseite.)
FWIW, ich habe nicht geschaut auf den code, aber Dan Sandler ist Marker app könnte einen Blick Wert (source code hier).
Update: die Korruption war identifiziert als ein bug und Feste in der "L".
lockCanvas()
, und nicht die Zeichnung die gesamte bitmap in eine TextureView jeden Rahmen. Das beste, was Sie tun können, ist zum Rendern auf einer GLES FBO, die blitted durch die GPU, wie, die verhindert, dass der langsame Weg insgesamt. Für Canvas-rendering-Sie haben die Wahl zwischen kopieren 64MB in eine riesige TextureView (langsame Zeichnung, schnelles scrollen) oder nur blitting den sichtbaren Teil jedes Rahmens in einer kleinen (schnellen Zeichnung, langsames Rollen). Sie könnte ein hybrider Ansatz, der Schalter auf der fly.UPDATE ich ditched TextureView und nun eine OpenGL-Ansicht, wo ich anrufen glTexSubImage2D zu aktualisieren, geänderte Stücke eine render-textur.
ALTE ANTWORT
Ich landete Fliesen TextureView in einem 4x4-raster. Je nach dirty rect jeden Rahmen, ich aktualisieren den entsprechenden TextureView Ansichten. Jede Ansicht, die nicht aktualisiert wird, dass der frame I Invalidate aufrufen auf.
Einige Geräte, wie das Moto G Handy ein Problem haben, wo die Doppelpufferung beschädigt ist für einen frame. Sie können das Problem beheben, indem Sie lockCanvas, zweimal, wenn die übergeordnete Ansicht hat es onLayout genannt.