Programação 3D no Zeebo! - Habilitando aceleração de hardware via OpenGL ES

Ir em baixo

Programação 3D no Zeebo! - Habilitando aceleração de hardware via OpenGL ES

Mensagem por Mario Olofo em Sex 29 Jan 2010 - 0:45

Programação 3D no Zeebo

1 - Introdução

Este tutorial é a continuação do "Programação BREW - Teoria e Olá mundo", e é aconselhável dar uma lida novamente nele para refrescar
a memória sobre como programar o básico em BREW para rodar nosso aplicativo. Nos códigos ilustrados abaixo,
toda referência da variável app é referência a nossa estrutura de aplicação, criada no tutorial anterior.

Este tutorial tem como intuito ensinar o mínimo necessário para habilitar a aceleração 3D no Zeebo via OpenGL ES.
É pre requisito saber ao menos o básico sobre C e/ou C++.


2 - Perfis de OpenGL ES

Neste tutorial iremos ver sobre OpenGL ES versão 1.1, que é a versão utilizada no Zeebo.
O OpenGL ES vem com dois perfis diferentes: Common e Common-Lite.

Uma das principais diferenças entre os perfis é que o perfil Common-Lite é voltado a plataformas que não tem
suporte eficiente a calculos com ponto flutuante, sendo usado apenas ponto fixo para simular casas decimais.

Quem quiser dar uma lida na especificação completa desta versão do OpenGL ES pode acessar o documento em PDF
no site da Khronos (em inglês): http://www.khronos.org/registry/gles/specs/1.1/es_full_spec_1.1.12.pdf

Iremos utilizar apenas a versão Common-Lite, já que o Zeebo e a placa de vídeo dele não suportam calculos com ponto flutuante.


3 - Iniciando o OpenGL ES

O OpenGL ES apenas define a API para renderizar poligonos 2D/3D e modificar seus atributos, sendo necessário
utilizar outra API para criar uma janela com um framebuffer compativel. A API multiplataforma recomendada é a EGL,
responsável por fazer link entre o OpenGL ES e o subsistema de vídeo nativo da plataforma. A especificação da EGL
pode ser encontrada também no site da Khronos, no link (em inglês): http://www.khronos.org/registry/egl/

Primeiramente precisaremos incluir alguns cabeçalhos no nosso código para utilizar o OpenGL ES:

Código:

#include <EGL_1x.h>
#include <GLES_1x.h>
#include <gles/egl.h>
#include <gles/gl.h>
#include <gles/glESext.h>

A inicialização pode ser dividida em:
- Requisição de uma conexão com o sistema gráfico nativo;
- Seleção da configuração do formato de pixel nativo mais próximo do formato que queremos;
- Criação do buffer para renderização;
- Criação de contexto para o display ao framebuffer com aceleração 3D;

Precisaremos de três variáveis para criar e gerenciar a conexão com o OpenGL ES:

Código:

EGLDisplay  eglDisplay;  // Responsável pelo acesso ao display
EGLSurface  eglSurface;  // framebuffer com aceleração 3D em hardware
EGLContext  eglContext;  // variável de gerenciamento de contexto

Estas variáveis devem ser adicionadas a estrutura nossa aplicação para poderem ser acessadas durante
a execução do nosso programa:

Código:

typedef struct
{
   AEEApplet a;
   AEEDeviceInfo  sDeviceInfo;

   /* Add suas variáveis aqui */

   /* OpenGL ES */
   EGLDisplay  eglDisplay;
   EGLSurface  eglSurface;
   EGLContext  eglContext;
}
Game;

Na BREW, para conseguirmos utilizar as funções de EGL e OpenGL ES como funções normais sem a necessidade de um
ponteiro para a interface correspondente, devemos chamar duas funções que encapsulam a carga das interfaces do EGL e OpenGL ES,
EGL_Init e GLES_Init:

Código:

int EGL_Init( IShell* pIShell );
int GLES_Init( IShell* pShell );

Código:

if( EGL_Init( app->a.m_pIShell ) != SUCCESS )
{
   return FALSE;
}

if( GLES_Init( app->a.m_pIShell ) != SUCCESS )
{
   return FALSE;
}

Após carregar as interfaces, podemos iniciar a conexão com o display genérico chamando a função eglGetDisplay:

Código:

EGLDisplay eglGetDisplay( NativeDisplayType display_id );

Código:

app->eglDisplay = eglGetDisplay( EGL_DEFAULT_DISPLAY );

NativeDisplayType é uma tipedef de ponteiro void (ou ponteiro para uma interface IDIB, no simulador) e o define
EGL_DEFAULT_DISPLAY é um valor nulo.


Agora precisamos iniciar o sistema de rasterização 3D (no Zeebo), chamando a função eglInitialize:

Código:

EGLBoolean eglInitialize( EGLDisplay dpy, EGLint* major, EGLint* minor );

Código:

if( eglInitialize( app->eglDisplay, NULL, NULL ) != EGL_TRUE )
{
   return FALSE;
}

Os dois ultimos parâmetros da função eglInitialize são ponteiros para variáveis do tipo inteiro, usadas para retornar a versão de OpenGL ES iniciada,
mas não são obrigatórios.

NOTA: A placa gráfica do Zeebo tem um sistema de economia de energia, desabilitando alguns componentes do hardware não utilizados.
O subsistema 3D é ativado quando a rotina eglInitialize é chamada e só é desativado novamente quando fechamos a comunicação com o display via eglTerminate (descrito mais pra frente).

Para criar o framebuffer com aceleração 3D, devemos informar ao EGL qual o formato de buffer que queremos, quantos bits para
os buffers de profundidade (depth buffer) e máscara (stencil buffer), para que ele possa nos indicar o índice da melhor
configuração suportada pelo hardware que atenda aos nossos requisitos.

Para fazer isso, precisamos criar uma lista de inteiros contendo o formato de pixel que queremos, da seguinte forma:

Código:

EGLint attributos[ ] =
{
   EGL_RED_SIZE,      5,
   EGL_GREEN_SIZE,    6,
   EGL_BLUE_SIZE,      5,
   EGL_DEPTH_SIZE,    16,
   EGL_STENCIL_SIZE,  4,
   EGL_NATIVE_RENDERABLE, EGL_TRUE,
   EGL_NONE
};

A sequência é bem simples, informando o atributo que iremos configurar seguido pelo valor que queremos que ele tenha.
O fim da lista é indicado pelo valor EGL_NONE.
A configuração acima segue o formato de pixel suportado pelo Zeebo, com formato de cor RGB 565, buffer de profundidade de
16 bits e 4 bits de buffer de máscara.

Existem vários outros atributos que podem ser modificados, e estão detalhados no documento da especificação do EGL (página 13).

Para saber qual a configuração de pixel que bate com os atributos que queremos, usamos a rotina eglChooseConfig:

Código:

EGLBoolean eglChooseConfig(
   EGLDisplay    dpy,
   const EGLint* atributos,
   EGLConfig*    configs,
   EGLint        config_size,
   EGLint*      num_config );

dpy é um ponteiro para a conexão com o display, atributos é a nossa lista de atributos, configs é um ponteiro para uma lista
de estruturas de formato EGLConfig, onde as configurações que baterem com os atributos que passamos serão armazenadas,
respeitando o limite de quantidade de estruturas, passado pelo parâmetro config_size e num_config é um ponteiro para um inteiro
que receberá um número indicando quantidade de configurações que tem os requisitos mínimos passados.
No Zeebo para simplificar, passamos apenas uma estrutura pois já sabemos que este é o formato nativo suportado nele:

Código:

EGLint    numConfigs = 0;
EGLConfig config = { 0 };
EGLint atributos[ ] =
{
   EGL_RED_SIZE,      5,
   EGL_GREEN_SIZE,    6,
   EGL_BLUE_SIZE,      5,
   EGL_DEPTH_SIZE,    16,
   EGL_STENCIL_SIZE,  4,
   EGL_NATIVE_RENDERABLE, EGL_TRUE,
   EGL_NONE
};

if( eglChooseConfig( app->eglDisplay, atributos, &config, 1, &numConfigs ) != EGL_TRUE )
{
   return FALSE;
}

Estamos quase lá, agora falta criar o framebuffer onde o OpenGL ES irá renderizar.
A rotina usada para esta função é a eglCreateWindowSurface:

Código:

EGLSurface eglCreateWindowSurface(
   EGLDisplay dpy,
   EGLConfig config,
   NativeWindowType win,
   const EGLint *attrib list );

O NativeWindowType é um ponteiro void, pois é uma abstração de uma janela nativa, dependente do sistema.
Esta janela nativa deve ser criada com os mesmos atributos que queremos ter no OpenGL ES, mas no caso da BREW,
vamos passar um ponteiro para uma estrutura com informações do display nativo.
Na BREW, a interface IDisplay tem um IBitmap, que por sua vez contem internamente as definições do buffer do display, que é o que queremos:

Código:

IBitmap* pIBitmapDDB; // Bitmap do display
IDIB*    pDIB;        // Device independent bitmap, requisitado através do bitmap do display

/* Cadê o bitmap do display? */
if( IDISPLAY_GetDeviceBitmap( app->a.m_pIDisplay, &pIBitmapDDB ) != SUCCESS )
{
   return FALSE;
}

/* Hmm blz, agora eu quero ele em formato independente, com acesso aos seus pixels: */
if( IBITMAP_QueryInterface( pIBitmapDDB, AEECLSID_DIB, ( void ** )&pDIB ) != SUCCESS )
{
   /* Erro, libera para decrementar referência: */
   IBITMAP_Release( pIBitmapDDB );

   return FALSE;
}

/* Agora sim, vamos criar um framebuffer com aceleração 3D e vincular ele ao IDIB do display: */
app->eglSurface = eglCreateWindowSurface( app->eglDisplay, myConfig, ( NativeWindowType )pDIB, NULL );

/* Libera para decrementar referência: */
IDIB_Release( pDIB );
IBITMAP_Release( pIBitmapDDB );

/* Não deu certo? retorna */
if( app->eglSurface == EGL_NO_SURFACE )
{
   return FALSE;
}

Por fim, vamos criar um contexto para gerenciar a renderização no framebuffer criado. A função para isso é a eglCreateContext:

Código:

EGLContext eglCreateContext(
   EGLDisplay dpy,
   EGLConfig config,
   EGLContext share_context,
   const EGLint *attrib_list);

Os únicos parâmetros que iremos passar serão os dois primeiros, sendo o primeiro o ponteiro para o EGLDisplay criado e o segundo a referência para a estrutura com o formato de pixel que queremos:

Código:

/* Criação do contexto de gerenciamento da renderização no nosso framebuffer */
app->eglContext = eglCreateContext( app->eglDisplay, myConfig, 0, 0 );
if( app->eglContext == EGL_NO_CONTEXT )
{
   return FALSE;
}

Agora é só mudar o buffer de renderização atual para o nosso framebuffer:

Código:

/* Indica que nosso framebuffer será usado à partir de agora: */
if( eglMakeCurrent( app->eglDisplay, app->eglSurface, app->eglSurface, app->eglContext ) != EGL_TRUE )
{
   return FALSE;
}

Pronto!
À partir daqui podemos dar comandos de OpenGL ES normalmente, e eles serão interpretados e executados na placa de vídeo do Zeebo!


4 - Brincando com o OpenGL ES e exibindo a cena renderizada

Como usaremos o perfil Common-Lite, preste bem atenção nos nomes das rotinas da API do OpenGL ES.
Sempre que tiver alguma rotina que receba valores em ponto flutuante, procure a respectiva que aceite ponto fixo. Normalmente essas rotinas em ponto fixo terminam com um "x", indicando isso.

Em BREW, para conseguirmos atualizar o display repetidamente, devemos usar um timer para nos avisar quando chegar a hora de atualizar o display novamente.
A função usada para isso é a ISHELL_SetTimer:

Código:

int ISHELL_SetTimer( IShell* pShell, int milisegundos, PFNNOTIFY func, void* IApplet );

Para que o sistema nos alerte numa frequência de 30 frames por segundo fazemos o seguinte:

Código:

ISHELL_SetTimer( app->a.m_pIShell, 33, ( PFNNOTIFY )GAME_TrataTimer, ( void * )app );

Note que 33 milisegundos = ~ 1 / 30 fps

Código:

#define VAL_UM    ( 1 << 16 )
#define ADD(a,b)  ( (a) + (b) )
#define SUB(a,b)  ( (a) - (b ) )
#define MUL(a,b)  ( ( ( long long )(a) *  (b) )  >> 16)
#define DIV(a,b)  ( ( (a)<<16) / (b) )

void TrataNovoFrame( void* IApplet )
{
   int aspecto;
   Game* app = ( Game* )IApplet;
   int verts[ ] = {
   /*  x          y    z */
       0,    VAL_UM * 2 - VAL_UM / 2,  0,
    VAL_UM, -VAL_UM,  0,
   -VAL_UM, -VAL_UM,  0
   };
   int cores[ ] = {
      0xffff0000,
      0xff00ff00,
      0xff0000ff
   };

   /* vamos querer ser alertados daqui a 33 ms de novo, entao avisamos o sistema que devemos ser chamados novamente: */   
   ISHELL_SetTimer( app->a.m_pIShell, 33, ( PFNNOTIFY )TrataNovoFrame, app );

   /* Vamos usar as dimensões da tela para redimensionar a nossa área de renderização */
   app->sDeviceInfo.wStructSize = sizeof( app->sDeviceInfo );
   ISHELL_GetDeviceInfo( app->a.m_pIShell, &app->sDeviceInfo );

   aspecto = DIV( ( app->sDeviceInfo.cyScreen << 16 ), ( app->sDeviceInfo.cxScreen << 16 ) );

   glViewport( 0, 0, app->sDeviceInfo.cxScreen, app->sDeviceInfo.cyScreen );
   glMatrixMode( GL_PROJECTION );
   glLoadIdentity( );
/* Perceba o "x" no final do nome da função. Indicativo que ela recebe valores em ponto fixo */
   glFrustumx( -VAL_UM, VAL_UM, -aspecto, aspecto, VAL_UM, 50 << 16 );
   glMatrixMode( GL_MODELVIEW );
   glLoadIdentity( );
   glEnable( GL_DEPTH_TEST );
   glShadeModel( GL_SMOOTH );
   glDisable( GL_LIGHTING );

   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

   i = GETTIMEMS( ) / 10;
   i = ( i << 16 );
   glLoadIdentity( );
   glTranslatex( 0, 0, -VAL_UM * 4 );
   glRotatex( i, 0, 0, VAL_UM );

   glEnableClientState( GL_VERTEX_ARRAY );
   glEnableClientState( GL_COLOR_ARRAY );

   glColorPointer( 4, GL_UNSIGNED_BYTE, 4, cores );
   glVertexPointer( 3, GL_FIXED, sizeof( int ) * 3, verts );

   glDrawArrays( GL_TRIANGLES, 0, 3 );

   eglSwapBuffers( app->eglDisplay, app->eglSurface );
}

Note que devemos registrar esta rotina no início do nosso aplicativo ou chamá-la ao menos uma vez manualmente para que ela se registre para ser chamada novamente depois de alguns milisegundos.


5 - Finalizando o OpenGL ES

Finalizar o OpenGL ES é muito simples.
Devemos indicar que não iremos mais renderizar em nosso contexto, destruir o contexto e o framebuffer para liberar memória e chamar eglTerminate, que irá desligar o hardware de aceleração 3D para economizar energia:

Código:

void FinalizaOpenGLES( Game* app )
{
   if( app->eglDisplay != EGL_NO_DISPLAY )
   {
      eglMakeCurrent( EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
      eglDestroyContext( app->eglDisplay, app->eglContext );
      eglDestroySurface( app->eglDisplay, app->eglSurface );
      eglTerminate( app->eglDisplay );
      app->eglDisplay = EGL_NO_DISPLAY;

      /* Devemos liberar as interfaces do EGL e OpenGL ES nesta ordem: */
      GLES_Release( );
      EGL_Release( );
   }
}


6 - Conclusão

Basicamente é isso!
A inicialização do OpenGL ES podemos colocar no tratamento do evento EVT_APP_START, iniciando o timer para chamar nossa rotina de renderização periodicamente.
Para finalizar, colocamos a rotina FinalizaOpenGLES dentro do tratamento EVT_APP_STOP. Lembre-se de cancelar qualquer timer pendente antes de finalizar o OpenGL ES, para que o sistema não trave. Para tal basta usar a função ISHELL_CancelTimer:

Código:

ISHELL_CancelTimer( app->a.m_pIShell, NULL, ( void * )app );

É isso, com isso concluimos a sequência de tutoriais ensinando a criar o ambiente de desenvolvimento para criação de jogos para Zeebo, iniciamos o aplicativo em BREW e habilitamos aceleração 3D em hardware via OpenGL ES!

No próximo tutorial irei ensinar a tratar os eventos de joystick do Zeebo, para criarmos aplicativos mais interativos.

Mario

Mario Olofo
Iniciante
Iniciante

Cheia
Mensagens : 32
Data de inscrição : 04/11/2009
Tem o Zeebo? Tem o Zeebo? : Não

Voltar ao Topo Ir em baixo

Re: Programação 3D no Zeebo! - Habilitando aceleração de hardware via OpenGL ES

Mensagem por Amatekyou em Sex 29 Jan 2010 - 19:06

Valeu Mario! Espero um dia conseguir fazer uns "testes" de jogos para o Zeebo!
avatar
Amatekyou
Iniciante
Iniciante

Cheia
Mensagens : 69
Data de inscrição : 24/12/2009
Idade : 22
Localização : São Paulo,SP
Tem o Zeebo? Tem o Zeebo? : Não

Voltar ao Topo Ir em baixo

Voltar ao Topo

- Tópicos similares

 
Permissão deste fórum:
Você não pode responder aos tópicos neste fórum