Android TextureView / графика / графика

Я TextureView сделать приложение для рисования / рисования с помощью TextureView на Android. Я хочу поддерживать поверхность рисования размером до 4096×4096 пикселей, что кажется разумным для моего минимального целевого устройства (которое я использую для тестирования), которое представляет собой Google Nexus 7 2013, у которого есть хороший четырехъядерный процессор и 2 ГБ памяти.

Одним из моих требований является то, что мое представление должно быть внутри представления, которое позволяет его масштабировать и вынимать и подкрасить, что является всем настраиваемым кодом, который я написал (думаю, UIScrollView из iOS).

Я попытался использовать обычный View (not TextureView ) с OnDraw, и производительность была абсолютно ужасной – менее 1 кадра в секунду. Это произошло, даже если я вызвал Invalidate(rect) только с измененным прямоугольником. Я попытался отключить аппаратное ускорение для представления, но ничего не отображалось, я полагаю, потому что 4096×4096 слишком велико для программного обеспечения.

Затем я попытался использовать TextureView и производительность немного лучше – около 5-10 кадров в секунду (все еще ужасно, но лучше). Пользователь рисует растровое изображение, которое затем втягивается в текстуру, используя фоновый поток. Я использую Xamarin, но, надеюсь, код имеет смысл для 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 { } } 

Если я изменю lockCanvas, чтобы передать null вместо rect, производительность велика при 60 fps, но содержимое TextureView и становится поврежденным, что неутешительно. Я бы подумал, что это будет просто использовать рамку буфера / рендеринга OpenGL под ней или, по крайней мере, иметь возможность сохранить содержимое.

Есть ли еще какие-то другие возможности, помимо всего прочего, в необработанном OpenGL в Android для высокопроизводительного рисования и рисования на поверхности, которая сохраняется между обратными вызовами?

Solutions Collecting From Web of "Android TextureView / графика / графика"

Прежде всего, если вы хотите понять, что происходит под капотом, вам нужно прочитать документ Graphics Architecture . Это долго, но если вы искренне хотите понять «почему», это место для начала.

О проекте TextureView

TextureView работает так: у него есть Поверхность, которая представляет собой очередь буферов с отношениями производитель-потребитель. Если вы используете рендеринг программного обеспечения (Canvas), вы блокируете Surface, которая дает вам буфер; Вы рисуете на нем; То вы разблокируете Поверхность, которая отправляет буфер потребителю. Потребитель в этом случае находится в том же процессе и называется SurfaceTexture или (внутренне, более метко) GLConsumer. Он преобразует буфер в текстуру OpenGL ES, которая затем отображается в представлении.

Если вы отключите аппаратное ускорение, GLES отключен, и TextureView ничего не может сделать. Вот почему вы ничего не получили, когда выключили аппаратное ускорение. Документация очень специфична: «TextureView может использоваться только в аппаратном ускоренном окне. При визуализации в программном обеспечении TextureView ничего не рисует».

Если вы укажете грязный прямоугольник, программный рендеринг будет memcpy предыдущего содержимого в кадр после завершения рендеринга. Я не верю, что он устанавливает прямой клип, поэтому, если вы вызываете drawColor (), вы заполняете весь экран, а затем перезаписываете эти пиксели. Если вы в настоящее время не устанавливаете прямой клип, вы можете увидеть некоторые преимущества в производительности от этого. (Я не проверял код, хотя.)

Грязный прямоугольник является входным параметром. Вы передаете исправление, которое хотите, когда вы вызываете lockCanvas() , и системе разрешено изменять его до того, как возвращается вызов. (На практике единственная причина, по которой это будет сделано, – это если бы предыдущий кадр или размер поверхности не были изменены, и в этом случае он расширил бы его, чтобы охватить весь экран. Я думаю, что это было бы лучше обработано с более прямым «Я отклоняю ваш прямой» сигнал.) Вам необходимо обновить каждый пиксель внутри прямоугольника, который вы вернетесь. Вам не разрешено изменять прямоугольник, который вы пытаетесь сделать в своем примере – все, что находится в грязном прямоугольнике после того, как lockCanvas() – это то, на что вам нужно нарисовать.

Я подозреваю, что неправильное обращение с грязными прямыми является источником вашего мерцания. К сожалению, это непростая ошибка, поскольку поведение свойства lockCanvas() dirtyRect arg dirtyRect только в самом классе Surface .

Поверхности и буферизация

Все поверхности являются двойными или тройными буферами. Нет никакого способа обойти это – вы не можете читать и писать одновременно, а не разрывать. Если вам нужен один буфер, который вы можете изменить и нажать, когда захотите, этот буфер нужно будет заблокировать, скопировать и разблокировать, что создает киоски в конвейере композиции. Для лучшей пропускной способности и латентности лучше всего использовать флип-буферы.

Если вы хотите поведение блокировки-разблокировки, вы можете написать это самостоятельно (или найти библиотеку, которая это делает), и она будет такой же эффективной, как и если бы система сделала это для вас (при условии, что вы хороши с Blit-петли). Нарисуйте на экране Canvas и blit растровое изображение, или в OpenGL ES FBO, и blit-буфер. Вы можете найти пример последнего в « записи GL приложения » Grafika, который имеет режим, который отображает один раз за пределами экрана, а затем blits дважды (один раз для отображения, один раз для записи видео).

Более высокая скорость и

Существует два основных способа рисования пикселей на Android: с Canvas или с OpenGL. Перенос холста на поверхность или растровое изображение всегда выполняется в программном обеспечении, а рендеринг OpenGL выполняется с помощью графического процессора. Единственное исключение состоит в том, что при рендеринге пользовательского представления вы можете использовать аппаратное ускорение, но это не применяется при визуализации к поверхности SurfaceView или TextureView.

Приложение рисования или рисования может либо помнить команды рисования, либо просто бросать пиксели в буфер и использовать это как свою память. Первый позволяет глубже «отменить», последний намного проще и имеет более высокую производительность, так как количество материала для рендеринга растет. Похоже, вы хотите сделать последнее, так что смешение с экрана не имеет смысла.

Большинство мобильных устройств имеют аппаратное ограничение 4096×4096 или меньше для текстур GLES, поэтому вы не сможете использовать одну текстуру для чего-либо большего. Вы можете запросить ограничение по размеру (GL_MAX_TEXTURE_SIZE), но вам может быть лучше с внутренним буфером, который вам нужен, и просто отрисуйте часть, которая подходит на экране. Я не знаю, каково ограничение Skia (Canvas), но я считаю, что вы можете создавать гораздо большие растровые изображения.

В зависимости от ваших потребностей SurfaceView может быть предпочтительнее TextureView, поскольку он избегает промежуточного этапа текстуры GLES. Все, что вы рисуете на поверхности, поступает непосредственно к системному компоновщику (SurfaceFlinger). Нижняя сторона этого подхода состоит в том, что, поскольку потребитель Surface не находится в процессе, нет возможности для системы View обрабатывать выходные данные, поэтому Surface является независимым уровнем. (Для программы рисования это может быть полезно – изображение будет нарисовано на одном уровне, ваш пользовательский интерфейс находится на отдельном слое сверху).

FWIW, я не смотрел код, но приложение Mark Sanders от Дэна Сандлера может стоить заглянуть (исходный код здесь ).

Обновление: коррупция была идентифицирована как ошибка и исправлена ​​в «L» .

UPDATE Я перечеркнула TextureView и теперь использую представление OpenGL, где я вызываю glTexSubImage2D для обновления измененных фрагментов текстуры рендеринга.

OLD ANSWER Я закончил разбивку TextureView в сетке 4×4. В зависимости от грязного прямоугольника каждого кадра я обновляю соответствующие представления TextureView. Любое представление, которое не обновляется, в котором я вызываю Invalidate.

Некоторые устройства, такие как телефон Moto G, имеют проблему, когда двойная буферизация повреждена для одного кадра. Вы можете исправить это, вызвав lockCanvas дважды, когда родительский вид имеет имя onLayout.

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