Рассматривая реализацию OpenGL в приложении - PullRequest
0 голосов
/ 05 августа 2011

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

В настоящее время моя анимация достигается с помощью справочных таблиц, асинхронных задач и динамического создания растровых изображений (для текста - я отображаю его в растровых изображениях, когда использую пользовательские шрифты - поэтому я никогда не рисую текст непосредственно на холсте). У меня есть асинхронная задача, выполняемая в течение n * 1000 мс, и в потоке она ждет x мс (обычно 50 мс), а затем выталкивает сообщение о ходе выполнения - вспомогательные классы затем работают, где в индексированной по времени таблице поиска анимация и рассчитывает относительные значения на основе этого. Я рисую статические кусочки грифа прямо на холсте с помощью включенных методов рисования круга и линии.

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

Я следовал некоторым урокам и написал несколько OpenGL, но этого недостаточно, чтобы вообще что-то знать - я знаю, что это быстро, хотя и важно. Я не знаю, смогу ли я использовать те же методы рисования линий и окружностей непосредственно на холсте с помощью OpenGL, и я думаю, что мне все равно придется создавать некоторые растровые изображения для текста, и я думаю, что я должен применять их в качестве текстур в чтобы показать их.

Так кто-нибудь может дать мне несколько советов? Некоторый пример кода рисования линий, кругов и текста был бы удивительным. Любые указания на анимацию в OpenGL - я думаю, что моя текущая установка довольно надежна и может портировать ее, но любой совет был бы полезен, так как это мой первый взгляд на анимацию.

* РЕДАКТИРОВАТЬ *

Вот основной обзор моего кода - есть много частей, но я включаю их в надежде, что кто-то еще сможет использовать некоторые из них .:

  1. У меня есть справочная таблица

    public enum LookUpTable {
    
        COUNT_DOWN {
    
            @Override
            public float[][] getLookUpTable() {  
    
                /*
                 * 1 = total time left
                 * 2 = font alpha
                 * 3 = font size
                 */
    
                float[][] lookUpTable = {
    
                        {  0f,  0f,  400f }, 
                        {  400f,  255f,   200f },
                        {  700f,  255f,   150f },
                        { 1000f,  0f,      5f }
                };
                return lookUpTable;
            }
    
            @Override
            public LookUpType getLookUpType() {
                return LookUpType.REPEAT;
            }
        };
    
        // does the timer loop around, or is it a one off run
        private enum LookUpType {
            REPEAT, SINGLE
        }
    
        abstract public LookUpType getLookUpType();
        abstract public float[][] getLookUpTable();
    }
    
  2. Я расширил задачу AsyncTask до функции построителя:

    public class CountDownTimerBuilder {
    
        // callbacks - instantiated in the view
        protected CountDownEndEvent countDownEndEvent;
        protected CountDownProgressEvent countDownProgressEvent;
        protected CountDownInitEvent countDownInitEvent;
        protected int updatePeriod;
        protected float runTime;
    
        public CountDownTimerBuilder withCountDownEndEvent(CountDownEndEvent countDownEndEvent) {
            this.countDownEndEvent = countDownEndEvent;
            return this;
        }
    
        public CountDownTimerBuilder withCountDownProgressEvent(CountDownProgressEvent countDownProgressEvent) {
            this.countDownProgressEvent = countDownProgressEvent;
            return this;
        }
    
        public CountDownTimerBuilder withCountDownInitEvent(CountDownInitEvent countDownInitEvent) {
            this.countDownInitEvent = countDownInitEvent;
            return this;
        }
    
        public CountDownTimerBuilder withUpdatePeriod(int updatePeriod) {
            this.updatePeriod = updatePeriod;
            return this;
        }
    
        public CountDownTimerBuilder withRunTime(float runTime) {
            this.runTime = runTime;
            return this;
        }
    
    
        public CountDownTimer build() {
            return new CountDownTimer();
        }
    
        public static interface CountDownEndEvent {
            public abstract void dispatch(Long... endResult);
        }
    
        public static interface CountDownInitEvent {
            public abstract void dispatch();
        }
    
        public static interface CountDownProgressEvent {
            public abstract void dispatch(Long... progress);
        }
    
        public class CountDownTimer {
        AsyncTask<Void, Long, Long> genericTimerTask;
    
        /**
         * Starts the internal timer
         */
        public void start() {
            genericTimerTask = new GenericCountDownTimer().execute(new Void[] {});
        }
    
        public void cancel() {
            if (genericTimerTask != null) {
                genericTimerTask.cancel(true);
                genericTimerTask = null;
            }
        }
    
        private class GenericCountDownTimer extends AsyncTask<Void, Long, Long> {
    
            @Override
            protected Long doInBackground(Void... params) {
                long startTime = System.currentTimeMillis();
                long currentTime;
                long countDown;
    
                Log.i(ApplicationState.getLogTag(getClass()), "Timer running for " + runTime + " ms, updating every " + updatePeriod + " ms");
    
                do {
                    try {
                    Thread.sleep(updatePeriod);
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                    }
                    if (this.isCancelled()) {
                    Log.i(ApplicationState.getLogTag(getClass()), "Timer Cancelled");
                    break;
                    }
                    currentTime = System.currentTimeMillis();
                    countDown = currentTime - startTime;
                    publishProgress((long)runTime - countDown);
                } while (countDown <= runTime);
                return 0l;
            }
    
            @Override
            protected void onPreExecute() {
                if (countDownInitEvent != null) {
                    countDownInitEvent.dispatch();
                } 
            }
    
            @Override
            protected void onProgressUpdate(Long... progress) {
                Log.v(ApplicationState.getLogTag(getClass()), "Timer progress " + progress[0] + " ms");
    
                if (countDownProgressEvent != null) {
                    countDownProgressEvent.dispatch(progress);
                }
    
            }
    
            @Override
            protected void onPostExecute(Long endresult) {
                if (countDownEndEvent != null) {
                    countDownEndEvent.dispatch(endresult);
                }
            }
        }
        }
    }
    
  3. У меня есть класс, в котором вычисляются значения моей анимации:

    public class AnimationHelper {
    
        private LookUpTable lookUpTable;
        private float[][] lookUpTableData;
        private float currentTime = -1;
        private float multiplier;
        private int sourceIndex;
    
        public void setLookupTableData(LookUpTable lookUpTable) {
            if (this.lookUpTable != lookUpTable) {
                this.lookUpTableData = lookUpTable.getLookUpTable();
                this.currentTime = -1;
                this.multiplier = -1;
                this.sourceIndex = -1;
            }
        }
    
        private void setCurrentTime(float currentTime) {
            this.currentTime = currentTime;
        }
    
        public float calculate(float currentTime, int index) {
            if (this.currentTime == -1 || this.currentTime != currentTime) {
                setCurrentTime(currentTime);
                getCurrentLookupTableIndex();
                getMultiplier();
            }
            return getCurrentValue(index);
        }
    
        private void getCurrentLookupTableIndex() {
            sourceIndex = -1;
            for (int scanTimeRange = 0; scanTimeRange < (lookUpTableData.length - 1); scanTimeRange++) {
                if (currentTime < lookUpTableData[scanTimeRange + 1][0]) {
                    sourceIndex = scanTimeRange;
                    break;
                }
            }
        }
    
        private void getMultiplier() {
            if ((lookUpTableData[sourceIndex][0] - lookUpTableData[sourceIndex + 1][0]) == 0.0f) {
                multiplier = 0.0f;
            } else {
                multiplier = (currentTime - lookUpTableData[sourceIndex][0]) / (lookUpTableData[sourceIndex + 1][0] - lookUpTableData[sourceIndex][0]);
            }
        }
    
        public float getCurrentValue(int index) {
            float currentValue = lookUpTableData[sourceIndex][index] + ((lookUpTableData[sourceIndex + 1][index] - lookUpTableData[sourceIndex][index]) * multiplier);       
            return currentValue > 0 ? currentValue : 0;
        }
    }
    
  4. В своем игровом коде я связываю все это, определяя таблицу поиска для использования и создавая обратные вызовы для каждого из различных состояний, создавая таймер с классом построителя и запуская его:

    AnimationHelper animHelper = new AnimationHelper();
    animHelper.setLookupTableData(LookUpTable.COUNT_DOWN);
    
    CountDownInitEvent animationInitEvent = new CountDownInitEvent() {
    
        public void dispatch() {
            genericTimerState = TimerState.NOT_STARTED;
        }
    };
    CountDownProgressEvent animationProgressEvent = new CountDownProgressEvent() {
    
        public void dispatch(Long... progress) {
            genericTimerState = TimerState.IN_PROGRESS;
            // update the generic timer - we'll use this in all animations
            genericTimerCountDown = progress[0];
            invalidate();
        }
    };
    CountDownEndEvent animationEndEvent = new CountDownEndEvent() {
    
        public void dispatch(Long... endValue) {
            genericTimerState = TimerState.FINISHED;
            startGame();
        }
    };
    CountDownTimer timer = new CountDownTimerBuilder()
            .withRunTime(getCountDownPeriod(countDownTimePeriod)) // getCountDownPeriod() is used for handling screen rotation - esentially returns the run time for the timer in ms 
            .withUpdatePeriod(TIMER_UPDATE_PERIOD) // currently set at 50
            .withCountDownInitEvent(animationInitEvent)
            .withCountDownProgressEvent(animationProgressEvent)
            .withCountDownEndEvent(animationEndEvent)
            .build();
    
    timer.start();
    
  5. в моем onDraw я получаю конкретные значения из справочной таблицы и воздействую на них:

    private int IFTL = 0; // total time left
    private int IFY1 = 1; // initial instructions y offset
    private int IFY2 = 2; // start message y offset
    private int IFA1 = 3; // note to guess alpha
    
    float yPosition1 = animHelper.calculate(genericTimerCountDown, IFY1);
    float yPosition2 = animHelper.calculate(genericTimerCountDown, IFY2);
    float alpha1 = animHelper.calculate(genericTimerCountDown, IFA1);
    
    // getScreenDrawData() returns the coordinates and other positioning info for the bitmap
    final ScreenDrawData guessNoteTitleDrawValues = FretBoardDimensionHelper.getScreenDrawData(AssetId.GUESS_NOTE_TITLE);
    
    //change the y position of the bitmap being drawn to screen
    guessNoteTitleDrawValues.withAlteredCoordinate(Constants.Y_COORDINATE, 0-yPosition1);
    
    //
    DrawBitmapBuilder.createInstance()
        .withCanvas(getCanvas())
        .withBitmap(bitmapCacheGet(AssetId.GUESS_NOTE_TITLE))
        .withBitmapDrawValues(guessNoteTitleDrawValues)
        .draw();
    
    
    Paint paint = new Paint();
    paint.setAlpha((int)alpha1);
    
    final ScreenDrawData initialNoteDrawValues = FretBoardDimensionHelper.getScreenDrawData(AssetId.GUESS_NOTE_INITIAL_NOTE);
    
    // draw to screen with specified alpha
    DrawBitmapBuilder
        .createInstance()
        .withCanvas(getCanvas())
        .withBitmap(bitmapCacheGet(AssetId.GUESS_NOTE_INITIAL_NOTE))
        .withBitmapDrawValues(initialNoteDrawValues)
        .withPaint(paint)
        .draw();
    

В последнем бите кода вам придется читать между строк немного, поскольку там есть множество вспомогательных функций.
Это похоже на разумный подход? Я хотел бы пересмотреть код, если кто-то может быть обеспокоен - более чем счастлив выложить больше кода или объяснений, если требуется

1 Ответ

1 голос
/ 05 августа 2011

Я немного работал с OpenGL и хорошо ... Это не тривиально.

  • Для текста вам нужно будет создать изменяемое растровое изображение, написать в нем некоторый текст с canvas.drawText (...), а затем преобразовать это растровое изображение (которое должно иметь размер powerOfTwo) в gl текстуру и примените ее к прямоугольнику.

  • Для кругов ... это будет сложно. OpenGL рисует линии, треугольники, точки ... я боюсь, не круги. Один из способов создать круг в OpenGL - создать текстуру круга и применить ее к прямоугольнику.

Каждый раз, когда ваше приложение ставится на паузу и возобновляется, вам придется воссоздавать каждую из ваших текстур и заново настраивать поверхность gl ...

Если вам нужно смешать, вы, вероятно, обнаружите, что с большим количеством текстур у вашего телефона недостаточно памяти, чтобы справиться со всем этим ...

Теперь, когда я вас напугал: open gl действительно один из способов! Это позволит вашему приложению делегировать рисунок в графический процессор телефона, что позволит вам использовать процессор для расчета анимации и тому подобного.

На этом сайте вы найдете информацию об использовании OpenGL ES для Android: http://blog.jayway.com/2009/12/03/opengl-es-tutorial-for-android-part-i/

Существует 6 учебных пособий, а 6-е посвящено текстурам. Во втором уроке рассказывается о рисовании многоугольника и линий.

Наконец, вы должны прочитать эту книгу: http://my.safaribooksonline.com/book/office-and-productivity-applications/9781430226475/copyright/ii Это про андроид, openGL, ...

Удачи.

Редактировать:

Похоже, у меня недостаточно прав для написания комментария, поэтому я отредактирую это:

То, как я бы это сделал, может быть, намного проще:

  • У меня был бы вид поверхности с рендером, предназначенным для рисования как можно чаще. Он бы нарисовал синхронизированный список элементов (синхронизированный, из-за многопоточности), с позицией, альфа, ... здесь нет асинхронной задачи, только обычный поток рендеринга.

  • Поверхностный вид также запускает поток (обычный поток снова, без асинхронной задачи) со списком (или чем-то в этом роде). Этот поток каждые 16 мс или около того пробегает список анимаций, применяет их. (синхронно, конечно, если необходимо изменить некоторые элементы, используемые потоком рендеринга).

  • Когда анимация закончится (например, если она была в списке более 2000 мс, ее легко проверить с помощью такой функции, как System.currentTimeMillis ()), я бы удалила ее из списка.

Вуаля!

Тогда вы скажете мне: эй, но если я сделаю много анимации, вычисление может занять более 16 мс, и, похоже, будет медленнее! Мой ответ таков: для этого есть достаточно простое решение: в теме, касающейся анимации, перед ее применением вы делаете что-то подобное:

long lastTime = currentTime;
// Save current time for next frame;
currentTime = System.currentTimeMillis();
// Get elapsed time since last animation calculus
long ellapsedTime = currentTime - lastTime;

animate(ellapsedTime);

И в своей функции одушевления вы оживляете больше, если прошло больше времени! Надеюсь, это поможет.

...