Низкая частота кадров при рисовании полноэкранного рисования на холсте - PullRequest
0 голосов
/ 05 ноября 2018

Приложение, которое я разрабатываю, является клоном Flappy Bird. Я использую surfaceView объект, в котором у меня есть gameThread, и внутри его метода run я рисую различные компоненты игры на холсте.

Все идет гладко, пока я просто рисую Rects для представления объектов, но как только я добавил первые Drawables, я заметил небольшую потерю гладкости. Если я попытаюсь нарисовать фон как Drawable, игра потеряет очень значительную частоту кадров.

Что я пробовал:

  • Использование png и всех видов растровых изображений в качестве активов
  • Изменение размера объекта для идеального размещения на холсте, что позволяет избежать масштабирования

Ничто из этого не имело ощутимого эффекта.

В основном:

  • Если я использую только drawRect: 60fps
  • Если я рисую спину с drawRect, а другие компоненты с drawable.draw (canvas): 57fps
  • Если я рисую все (включая фон) с drawable.draw (canvas): 15fps

В некоторой степени соответствующий код:

public class CannonView extends SurfaceView
        implements SurfaceHolder.Callback {
    private CannonThread cannonThread; // controls the game loop
    private Drawable background;

    // constructor
    public CannonView(Context context, AttributeSet attrs) {
        super(context, attrs); // call superclass constructor
        getHolder().addCallback(this);
        background= ResourcesCompat.getDrawable(getResources(), R.drawable.background, null);
    }
    public void newGame() {
        background.setBounds(0,0, getScreenWidth(),getScreenHeight());
    }
    public void drawGameElements(Canvas canvas) {
        background.draw(canvas);
    }
    public void stopGame() {
        if (cannonThread != null)
            cannonThread.setRunning(false); // tell thread to terminate
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        if (!dialogIsDisplayed) {
            newGame(); // set up and start a new game
            cannonThread = new CannonThread(holder); // create thread
            cannonThread.setRunning(true); // start game running
            cannonThread.start(); // start the game loop thread
        }
    }
    private class CannonThread extends Thread {
        private SurfaceHolder surfaceHolder; // for manipulating canvas
        private boolean threadIsRunning = true; // running by default

        // initializes the surface holder
        public CannonThread(SurfaceHolder holder) {
            surfaceHolder = holder;
            setName("CannonThread");
        }

        // changes running state
        public void setRunning(boolean running) {
            threadIsRunning = running;
        }
        // controls the game loop
        @Override
        public void run() {
            Canvas canvas = null; // used for drawing
            while (threadIsRunning) {
                try {
                    // get Canvas for exclusive drawing from this thread
                    canvas = surfaceHolder.lockCanvas(null);
                    synchronized(surfaceHolder) {
                        drawGameElements(canvas);
                    }
                }
                finally {
                    if (canvas != null)
                        surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }

}

1 Ответ

0 голосов
/ 06 ноября 2018

Кажется очевидным, что основной причиной низкой частоты кадров является background.draw(). Переключение на Bitmap несколько улучшает это, возможно, из-за того, что оно кэширует выходные данные draw() и потому, что его можно использовать с Canvas функциями, которые гарантированно не нуждаются в масштабировании (например, drawBitmap (Bitmap, float) , поплавок, краски) )

Вы также обнаружили, что переключение на RGB_565 в качестве промежуточного формата значительно повышает производительность, предположительно потому, что он отбрасывает альфа. (В противном случае, я бы ожидал, что это будет несколько медленнее, т.к. формат должен быть конвертирован обратно в RGBA_8888, поскольку он преобразован в SurfaceView.)

Также очевидно, что Android не будет пропускать более 60 кадров в секунду. Это почти наверняка, потому что lockCanvas() участвует в схеме тройной буферизации , которая регулирует частоту рисования, чтобы предотвратить отправку кадров, которые никогда не могут отображаться (из-за фиксированной частоты обновления экрана вашего устройства 60 Гц) .

Это оставляет вопрос, почему вы не получаете полных 60 кадров в секунду, но что-то близко к этому. Если для drawGameElements() требуется одинаковое количество времени для запуска каждый раз, и оно меньше 16 мс, тогда lockCanvas() должно задушить вас, и ни один кадр не должен пропадать (непрерывно 60 кадров в секунду). Кажется вероятным, что в планировщике потоков есть что-то вроде сбоя, и время от времени CannonThread не выполняется достаточно быстро, чтобы предоставить кадр, прежде чем схеме тройной буферизации потребуется page-flip, В этом случае кадр должен быть отложен до следующего обновления экрана. Вы можете попытаться увеличить CannonThread's приоритет потока , удалить любую дополнительную обработку в drawGameElements(), которая не обязательно должна выполняться в CannonThread, или закрыть другие приложения, работающие на вашем устройстве.

Как уже упоминалось, OpenGL является стандартным способом получения максимальной производительности спрайтов для подобных игр, поскольку он способен перегрузить многие операции на оборудование. Возможно, вы приближаетесь к пределу производительности игры на drawBitmap().

...