Programação com OpenGL/Instalação/Android NDK

Nosso tutorial de GLUT pode ser rodado no Android usando um simples empacotador(wrapper) próprio.

Bule de Chá(Teapot) rodando no Galaxy S

Nota: Um empacotador(wrapper) vem sendo integrado ao FreeGLUT oficial! (Fonte)

Nosso empacotador(wrapper)Editar

Para entender como o nosso empacotador(wrapper) GLUT funciona, veja Android GLUT Wrapper

DependênciasEditar

Este documento foi feito usando o Android NDK r7c. Você também precisará de um ambiente de desenvolvimento em Java:

sudo aptitude install openjdk-6-jdk ant

Seu programa também vai precisar do GLM e FreeType - veja nas seções dedicadas abaixo.

Emulador (falta de) suporte para o OpenGL ES 2.0Editar

O emulador do Android começou a suportar a OpenGL ES 2.0 apenas em Abril de 2012, requerendo assim um emulador bem configurado e um sistema de imagem(system image), e não parece funcionar em todas as plataformas.

Também tenha cuidado com as "API Demos" os aplicativos estão em "OpenGL ES 2.0" que silenciosamente falha com OpenGL ES 1.0 se não tiver suporte a versão 2.0, por isto elas não são um bom teste para ver se a OpenGL ES 2.0 é suportada.

Então apenas faça testes com OpenGL 2.0 em aparelho Android com suporte a ela.

Documentação Oficial: https://developer.android.com/tools/devices/emulator.html#accel-graphics

Conectando Aparelho pela USBEditar

Quando você conectar seu aparelho pela USB, você poderá usar os comandos da adb ( disponivel na Android SDK) para navegar pelo sistema de arquivos e instalar aplicativos, debugar eles e etc...

O jeito mais fácil rodar o adb como root(superusuário).

Permitir acesso pelo USB para usuários non-rootEditar

Uma alternativa é liberando o acesso a usuários sem privilégios para conectar ao dispositivo, através da criação de um arquivo udev Primeiro, determine o idVendor do seu aparelho, digitando 'dmesg' depois de conecta-lo; deverá aparecer mais ou menos isto:

usb 2-1: New USB device found, idVendor=18d1, idProduct=4e22

Em seguida crie a regra udev abaixo, que estará em /etc/udev/rules.d/51-android.rules:

# Galaxy S
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666", GROUP="plugdev"

(Isto pode ser melhorado com o idProduct.) Em seguida reinicie o serviço udev:

/etc/init.d/udev restart

Se você plugar seu dispositivo, o carácter USB do dispositivo deverá aparecer no grupo "plugdev":

# ll /dev/bus/usb/002/
total 0
crw-rw-r-T 1 root plugdev 189, 140 janv. 19 21:50 013

Usando nosso empacotador(wrapper)Editar

Neste wikibook, os exemplos serão baseados na biblioteca GLUT

o GLUT ainda não foi portada para o Android, então escreveremos um simples empacotador(wrapper) para Android( veja em repositório de código).

Nota: este empacotador(wrapper) ainda é bem recente e poderá haver algumas mudanças futuramente.

Veja como usa-lo:

Procure o diretório 'tut01_intro_android/'.

  • Conecte seu dispositivo ( smartphone, tablet...) pela USB
  • Adicione as directivas do PATH pelas ferramentas do Android:
export PATH="$PATH:/usr/src/android-sdk-linux/tools:/usr/src/android-sdk-linux/platform-tools:/usr/src/android-ndk-r7"


Here's how to use it:

  • Dentro da pasta jni/, faça do tut.cpp um symlink(link simbólico) para poder compilar código com GLUT (ex: ../../tut02_clean/triangle.cpp)
  • Faça um assets como link simbólico para o tutorial que você está compilando (ex: ../tut02_clean)
  • Agora vocẽ pode digitar:
make clean; make && make install
  • Você vai ter uma aplicação "OpenGL Wikibook" em seu dispostivo, pronto para rodar!

Tela CheiaEditar

Para fazer um aplicativo de tela cheia, adicione os seguintes atributos em sua AndroidManifest.xml

<application
  ...
  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
>

TecladoEditar

O teclado padrão do Android não possue as teclas F1/F2/F3.

Em vez disto, você pode usar um "Teclado Hackeado" com alternativa para dispositivo com mais teclas:

ÍconeEditar

Certifique-se que seu aplicativo possua o android:icon definido em AndroidManifest.xml:

    <application ...
		 android:icon="@drawable/icon"

Crie dois icones:

  • res/drawable/icon.png (48x48)
  • res/drawable-hdpi/icon.png (72x72)

Agora seu aplicativo possue um ícone personalizado no lançador.r.

DebugandoEditar

se você quer ver as saídas padrão (stdout e stderr) do seu programa, você precisa redirecionar para o log do sistema:

adb shell stop
adb shell setprop log.redirect-stdio true
adb shell start  # this may restart your Android session

Para ver os arquivos de log, você pode usar:

  • O comando: adb logcat
  • O utilitário 'ddms', que é uma GUI gráfica para navegar nos logs.
  • O Eclipse possui um LogCat similar ao 'ddms'

Vendo as chamadas da JNIEditar

Você ativar o acompanhador das chamadas do JNI para o C/C++, assim:

adb shell stop
adb shell setprop dalvik.vm.checkjni true
adb shell start

Você vai obter todos traços adicionais no log do sistema, e o JNI ficara mais restrito ao que ele pode aceitar.

GDBEditar

O GDB pode ser usado também

Nota: no NDKr7, existe algumas questões a serem resolvidas para ele funcionar:

  • Veja abaixo se você usa o Cyanogen 7.1 para corrigir o 'run-as'
  • Usando o formato "stabs" para depurar os símbolos no Android.mk, senão o GDB pode mostrar as linhas de códigos errada [1]:
LOCAL_CXXFLAGS  := -gstabs+

GDB precisa da depuração do compilador, coloque um NDK_DEBUG=1 enquanto compila seu código C++:

ndk-build NDK_DEBUG=1

Enquanto inicia o gdb, veja se seu AndroidManifest.xml menciona um depurador, se não o gdb se comportará mal (com falhas nas informações do thread, crash e etc...):

    <application ...
            android:hasCode="true" android:debuggable="true"

O gdb-server precisa de alguns segundos para iniciar no seu dispositivo, assim que seu programa iniciar ele possa de ser pausado pelo depurador. você precisar colocar em sua função android_main:

  sleep(5);

Para iniciar a sessão do depurador, digite:

ndk-gdb --start

Correção no Cyanogen 2.3Editar

o ndk-gdb depende do comando run-as, que faz uma série de verificações na pasta /data/data No Cyanogen 2.3, é um link simbólico, e falha com uma mensagem estranha, e o ndk-gdb falha retornado com [2]:

ERROR: Could not extract package's data directory. Are you sure that
       your installed application is debuggable?

Então trabalhe em cima para recriar o /data/data com link simbólico :

mv /data/data /datadata.break-run-as
mkdir -m 771 /data/data/
chown system: v
mv /datadata/* /data/data/

Carregamento da biblioteca nativa indisponívelEditar

Se aparecer este erro: E/AndroidRuntime( 3021): java.lang.RuntimeException: Unable to start activity

ComponentInfo{org.wikibooks.OpenGL/android.app.NativeActivity}: java.lang.IllegalArgumentException:
Unable to load native library: /data/data/org.wikibook.OpenGL/lib/libnative-activity.so

O sistema não pode carregar sua .so para acessar ao baixo nível do sistema.

Para obter maiores informações você precisa criar um aplicativo Java bem simples para carregar as bibliotecas manualmente:

package com.example.helloandroid;

import android.app.Activity;
import android.os.Bundle;

public class HelloAndroid extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.loadLibrary("native-activity");
    }
}

Pode aparecer um este erro mais especifico, existe uma falha na implementação do STL:

E/AndroidRuntime(3009): java.lang.UnsatisfiedLinkError: Cannot load library:  reloc_library[1311]:
2323 cannot locate '_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc'...

ou falta uma dependência:

E/AndroidRuntime( 3327): java.lang.UnsatisfiedLinkError: Cannot load library:link_image[1962]:
2323 could not load needed library 'libglut.so.3' for 'libnative-activity.so'
(load_library[1104]: Library 'libglut.so.3' not found)

No pior caso, a biblioteca pode não carregar totalmente. Isto pode acontecer caso quando o construtor do C++ de uma global estática quebrar enquanto chama a biblioteca, mesmo antes de sua aplicação ser iniciada. Você precisa reproduzir um carregamento de uma biblioteca em C básico:

#include <stdio.h>
#include <dlfcn.h>

int main(int argc, char* argv[]) {
  const char* err = NULL;
  char* filename = "/data/data/org.wikibooks.OpenGL/lib/libnative-activity.so";

  if (argc == 2)
    filename = argv[1];

  printf("Clearing errors: "); fflush(stdout);
  err = dlerror();
  printf("%s\n", (err == NULL) ? "OK" : err); fflush(stdout);

  printf("Loading library: "); fflush(stdout);
  void* handle = dlopen(filename, RTLD_LAZY);
  err = dlerror();
  printf("%s\n", (err == NULL) ? "OK" : err); fflush(stdout);
  
  if (handle != NULL) {
    printf("Loading symbol: "); fflush(stdout);
    dlsym(handle, "ANativeActivity_onCreate");
    err = dlerror();
    printf("%s\n", (err == NULL) ? "OK" : err); fflush(stdout);
  }
}

Para manda-lo para o dispositivo execute isto:

$ arm-linux-androideabi-gcc test-dlsym.c
$ adb push a.out /
$ adb shell
# /a.out
Clearing errors: OK
Loading library: OK
Loading symbol: OK

Você também pode usar o strace que é mais precisa:

# strace /a.out

Lá não possui um ldd como padrão do Android, mas você pode simular ele usando:

arm-linux-androideabi-objdump -x libs/armeabi/libnative-activity.so | grep NEEDED
# ou
arm-linux-androideabi-readelf -d libs/armeabi/libnative-activity.so | grep NEEDED

Abstração das diferenças entre o OpenGL e o GLES2Editar

Quando você usa apenas as funções do GLES2, seus aplicativos não são portáveis para desktop ou dispositivo moveis. Existe algumas questões que precisam ser tratadas:

  • O GLSL #version é diferente.
  • O GLES2 precisa que alguns pontos de precisão que não são compatíveis com o OpenGL 2.1

Veja nos tutoriais básico 02 e 03 para mais detalhes e a uma solução proposta.

GLMEditar

Para instalar o GLM, você precisa extrair a ultima versão em em /usr/src/glm (de mode que o /usr/src/glm/glm.hpp exista). Ela é apenas uma biblioteca-cabeçalho não é necessário uma compilação separada.

FreeTypeEditar

Se você precisar da FreeType (a biblioteca de fontes), você que fazer uma compilação-cruzada. Nota: O Android usa o FreeType porem apenas internalmente ele não está disponível para um native apps.

Primeiro, prepare o compilador-cruzado pelo NDK:

/usr/src/android-ndk-r7c/build/tools/make-standalone-toolchain.sh --platform=android-9 \
  --install-dir=/usr/src/ndk-standalone-9
PATH=/usr/src/ndk-standalone-9/bin:$PATH

Em seguida use o compilado-cruzado para o freetype:

tar xf freetype-2.4.10.tar.bz2
cd freetype-2.4.10/
./configure --host=arm-linux-androideabi --prefix=/freetype --without-zlib
make -j4
make install DESTDIR=$(pwd)

(Você pode usar o --prefix=/usr/src/ndk-standalone-9/sysrot/usr como alternativa se você não planeja usar o compilador NDK do sistema.)

--without-zlib é usado internalmente para copiar o zlib dentro do freetype bastante usado em compilações independentes para Android, senão dara erros como:

./obj/local/armeabi/libfreetype.a(ftgzip.o): In function `ft_gzip_file_init':
ftgzip.c:(.text+0x3c4): undefined reference to `inflateInit2_'
./obj/local/armeabi/libfreetype.a(ftgzip.o): In function `ft_gzip_file_done':
ftgzip.c:(.text+0x43c): undefined reference to `inflateEnd'
./obj/local/armeabi/libfreetype.a(ftgzip.o): In function `ft_gzip_file_reset':
ftgzip.c:(.text+0x514): undefined reference to `inflateReset'
./obj/local/armeabi/libfreetype.a(ftgzip.o): In function `ft_gzip_file_fill_output':
ftgzip.c:(.text+0x780): undefined reference to `inflate'

Em seguida escreva no Android.mk a nova pasta do freetype:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := freetype
LOCAL_SRC_FILES := lib/libfreetype.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/include/freetype2

include $(PREBUILT_STATIC_LIBRARY)

See docs/STANDALONE-TOOLCHAIN.html and docs/PREBUILTS.html in the NDK for details. The CLEAR_VARS bit is not documented, but is necessary to avoid mixed-up paths; it is used in the native_app_glue NDK module. Para usar o FreeType em seus projetos edite o arquivo Android.mk:

...
LOCAL_STATIC_LIBRARIES := ... freetype
...
$(call import-module,freetype)

Para isto você especificara a pasta aonde o freetype esta, usando a variável NDK_MODULE_PATH. Para instancia-lo, você precisa copiar o freetype na pasta do seu projeto e compilar usando o:

	ndk-build NDK_MODULE_PATH=. ...

Nosso empacotador já esta definido em NDK_MODULE_PATH=$$NDK_MODULE_PATH:.

Nota: Antes na versão 2.4.10, você precisava usar o CFLAGS="-std=gnu99" nas linhas do ./configure, veja nesta discussão. Isto foi oficialmente comunicado e corrigido no canal do FreeType

Veja também uma introdução sobre a compilação do Gnash para o Android:

ReferênciasEditar

  1. http://groups.google.com/group/android-ndk/browse_thread/ebd969a055af3196
  2. http://forum.cyanogenmod.com/topic/27657-run-as-not-working-due-to-datadata-symlink/