Curso completo de DirectX 9 com C\C++
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
Fase 01-9
01.9 Recipientes do directx
1.1 Visão geral
A aplicação de exemplo deste tópico replica a aplicação do tópico
anterior com a diferença de utilizar os recipientes de vértices e de
índices providos pelas interfaces do directx. De um certo ponto de
vista o buffer de índices e o buffer de vértices são formas de
recipientes para conter índices e vértices. O mesmo podemos pensar da
textura e de outras superfícies de imagens como sendo recipientes de
informação de cor.
Estes recipientes providos pelas interfaces do directx são chamados
recursos ( resources ) na documentação e na codificação da Microsoft.
Uma das primeiras vantagens em se utilizar os recipientes do directx
é velocidade: de acordo com a configuração dada ao recipiente o
directx pode mandá-lo para estocagem e processamento na placa
aceleradora fato que melhora a eficiência da aplicação. As outras
vantagens consiste no fato do próprio directx fazer a gestão dos
dados do recipiente e ainda prover alguns métodos comuns para
facilitar algumas operações. Por exemplo, na questão da textura
você vai encontrar funções para ler e salvar a textura no disco.
A gestão dos dados dos recipientes não é uma coisa simples pois entra
em questão a concorrência no acesso aos dados pela aplicação, pelo
directx e pela placa aceleradora bem como a questão da troca e da
posse de dados entre cada ponto. Então essas questões impactam na
configuração inicial e no acesso aos recipientes pela aplicação. É
comum na criação dos recipientes pelas interfaces do directx a
indicação de uso que a aplicação vai fazer dos dados, se vai ler ou
modificar com frequência; a antecipação dessa informação permite ao
directx selecionar o melhor local para guardar esses dados que pode
ser a memória gerenciada pelo directx, a memória gerenciada pelo
Sistema ou na memória da placa de vídeo. Essa diferença de local de
memória dá origem a classes de memória também chamado de piscina ou
pool pelo directx. Por fim, na solicitação de acesso aos dados, a
informação se vai ser apenas para leitura ou escrita permite ao
directx fazer otimizações internas de como ele vai liberar o acesso
à aplicação esses dados.
Para facilitar a fluência do texto a expressão buffer de vértices vai
ser referida também pelo termo inglês vertexbuffer e o mesmo ocorrerá
com o buffer de índices a ser chamado de indexbuffer. As expressões
em inglês e em português serão usadas de maneira intercambiável sem
perda do significado do que são estes recipientes.
1.2 Estrutura principal da aplicação
Arquivo: motor.cpp
Aspectos globais
Declaração do ponteiro global para o buffer de vértices
Declaração do ponteiro global para o buffer de índices
initGfx()
Inicializa objeto Direct3d
Inicializa dispositivo renderizador
chama inicializar_Buffers() para inicializar o buffer de
índices e o buffer de vértices.
chama montar_Geometria() para configurar os vértices no
buffer de vértices e os índices no buffer de índices.
inicializar_Buffers()
Criação do buffer de vértices (vertexbuffer)
Criação do buffer de índices (indexbuffer)
montar_Geometria()
Acessa o buffer de vértices
Configura posição dos 4 vértices no vertexbuffer
Acessa o buffer de índices
Configura posição dos 6 índices no indexbuffer
renderizar_Geometria()
Declara o formato de vértice utilizado ao directx
Indica ao dispositivo o buffer de vértices que será utilizado
g_device->SetStreamSource()
Indica ao dispositivo o buffer de índices que será utilizado
g_device->SetIndices()
Renderiza os vértices com g_device->DrawIndexedPrimitive()
Renderizar()
Limpa a tela
Desenha a cena
chama renderizar_Geometria() para desenhar o quadrado
Apresenta a cena
Limpar()
Libera o buffer de vértices
Libera o buffer de índices
Libera dispositivo renderizador
Libera objeto Direct3d
2.1.1 Aspectos globais - Arquivo: motor.h
// Projeto: prj_Buffers - Arquivo: motor.h
// Esta aplicação ilustra como renderizar um quadrado
// utilizando as interfaces do directx para buffer de
// índices e buffer de vértices. By www.gameprog.com.br
#ifndef motor_h
#define motor_h
// Esta função inicializa o Direct3D
HRESULT initGfx (HWND hJanela);
// Essa função libera os objetos utilizados
void Limpar();
// Essa função desenha a cena
void Renderizar();
// Essa função inicializa o buffer de índices e
// o buffer de vértices
void inicializar_Buffers(void);
// Essa função monta formas geométricas
void montar_Geometria (void);
// Renderiza os vértices em formas geométricas
void renderizar_Geometria (void);
// Declaração da função que atende as mensagens da janela
LRESULT CALLBACK processaJanela (HWND hJanela, UINT mensagem,
WPARAM wParam, LPARAM lParam);
#endif
void inicializar_Buffers(void);
Essa função inicializa o buffer de índices e o buffer de vértices. Na
verdade, essa inicialização consiste na criação desses dois
recipientes com a configuração básica que cada um exige. Decidimos
criar esses dois recipientes aqui na mesma função para facilitar a
percepção de que eles têm aspectos e configurações semelhantes.
void montar_Geometria (void);
Essa função configura os vértices no vertexbuffer e os índices no
indexbuffer para formar o quadrado.
void renderizar_Geometria (void);
Essa função vai renderizar o quadrado que está confinado na
configuração e contéudos do vertexbuffer e do indexbuffer. É
importante destacar que tanto o indexbuffer e o vertexbuffer podem
ser renderizados parcialmente. A aplicação pode possuir vários
recipientes mas a melhor eficiência é obtida quando todos os objetos
3d estão no mesmo pacote com a aplicação gerenciando a parte do
pacote que quer ter renderizada, certamente com cada parte do
recipiente global e geral confinando uma forma geométrica.
2.1.2 Aspectos globais - Arquivo: motor.cpp
// Representa o buffer de vértices
IDirect3DVertexBuffer9* g_vbVertices = NULL;
// Representa o buffer de índices
IDirect3DIndexBuffer9* g_ibIndices = NULL;
IDirect3DVertexBuffer9* g_vbVertices = NULL;
Esta é a interface que representa o buffer de vértices. Depois na
aplicação segue o padrão do directx que já vimos: eles vão ser
inicializados por uma função Create...() e depois dispensados com
Release() no final da aplicação.
IDirect3DIndexBuffer9* g_ibIndices = NULL;
Essa é a interface que representa o buffer de índices.
2.2 Inicializando o motor gráfico
Segue a listagem de initGfx() que chama as funções inicializar_Buffers()
e montar_Geometria() para o trabalho de estabelecer o cenário inicial
da aplicação.
// initGfx() - Inicializa o Direct3D
HRESULT initGfx( HWND hJanela )
{
// Cria o objeto Direct3D que é necessário para criar o dispositivo gráfico
g_Direct3d = Direct3DCreate9( D3D_SDK_VERSION);
// Verifica se o objeto Direct3D foi criado
if(g_Direct3d == NULL)
{
MessageBox (NULL,
"Falha na inialização do Direct3D", "InitGfx()", MB_OK);
return E_FAIL;
} // endif
// Declara a variável para os parâmetros de apresentação
D3DPRESENT_PARAMETERS pps;
// Limpa a estrutura
ZeroMemory( &pps, sizeof(pps) );
// Configura os parâmetros de apresentação
// A aplicação vai ter janela
pps.Windowed = TRUE;
// Esse método transfere rapidamente o backbuffer para a tela imediata
pps.SwapEffect = D3DSWAPEFFECT_DISCARD;
// Esse formato vai procurar se encaixar no modo de video corrente
pps.BackBufferFormat = D3DFMT_UNKNOWN;
// Configuração do renderizador a ser criado
// Adaptador default (0)
int nAdaptador = D3DADAPTER_DEFAULT;
// Tipo de dispositivo Hardware ou emulador de referência (software)
D3DDEVTYPE dispositivo_tipo = D3DDEVTYPE_HAL;
// Flags de configuração do dispositivo
DWORD create_flags = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
// Criamos aqui o dispositivo renderizador
g_hr = g_Direct3d->CreateDevice( nAdaptador, dispositivo_tipo,
hJanela, create_flags, &pps, &g_device );
// Verifica se houve falha no processo
if( FAILED( g_hr ) ) {
MessageBox (NULL, "Falha na criação: g_device", "initGfx()", MB_OK);
return E_FAIL;
} // endif
inicializar_Buffers();
montar_Geometria();
return S_OK;
} // initGfx().fim
2.3 Criação do buffer de vértices e do buffer de índices
void inicializar_Buffers(void)
{
// Segue abaixo os argumentos utilizados na criação do buffer
// de vértices. Alguns deles aparecem também na criação do
// buffer de índices.
// Quantidade de vértices do vertexbuffer
UINT nVertices = 4;
// Tamanho do vertexbuffer em bytes
UINT vbTamanho = sizeof(CustomVertex_TransformedColored) * nVertices;
// Dica de uso.
// É usado usado também na configuração do buffer de índices
DWORD uso_flags = D3DUSAGE_WRITEONLY;
// Formato do vértice dos vértices do vertexbuffer
DWORD vbFormato = CustomVertex_TransformedColored_Format;
// Definição da classe de memória (alojamento)
// É usado também na configuração do buffer de índices
D3DPOOL Piscina = D3DPOOL_MANAGED;
// Argumento sem uso
// Aparece também na criação do buffer de índices
HANDLE* hSem_Uso = NULL;
// Criação efetiva do buffer de vértices (vertexbuffer)
g_hr = g_device->CreateVertexBuffer(vbTamanho, uso_flags,
vbFormato, Piscina, &g_vbVertices, hSem_Uso);
// Verifica falha na criação
if(FAILED (g_hr) ) {
MessageBox (NULL,
"Falha na criação do buffer de vértices",
"inicializar_Buffers()", MB_OK);
return;
} // endif
// Quantidade de triângulos
UINT nTriangulos = 2;
// Cálculo do tamanho do buffer de índices
UINT ibTamanho = nTriangulos * 3 * 2;
// Formato do tipo de dado do buffer de índices
D3DFORMAT ibFormato = D3DFMT_INDEX16;
// Criação efetiva do buffer de índices
g_hr = g_device->CreateIndexBuffer(ibTamanho, uso_flags,
ibFormato, Piscina, &g_ibIndices, hSem_Uso);
// Verifica falha na criação
if(FAILED (g_hr) ) {
MessageBox (NULL,
"Falha na criação do buffer de vértices",
"inicializar_Buffers()", MB_OK);
return;
} // endif
} // inicializar_Buffers().fim
Segue abaixo os argumentos utilizados na criação do buffer de
de vértices. Alguns deles aparecem também na criação do buffer de
índices e os demais acabam guardando certa semelhança de finalidade.
Vamos discutir primeiro os argumentos que são iguais para ambos:
DWORD uso_flags = D3DUSAGE_WRITEONLY;
O valor deste flag indica que o recipiente vai ser usado apenas para
escrita. É uma das melhores configurações.Geralmente o recipiente vai
ter o conteúdo escrito na configuração inicial e depois ficará aos
cuidados exclusivos do directx. Geralmente você vai usar isso para
cenários ou objetos 3d estáticos. Lembramos que este flag representa
um dica de uso que a aplicação passa ao directx para que ele tome a
a melhor providência de alojamento e gerenciamento do recurso. Essa
melhor providência não é uma fatalidade e pode ser que devido aos
limites do contexto o directx não consiga as condições ideais para o
recurso. Outro valor comum para este flag é D3DUSAGE_DYNAMIC para
recipientes dinâmicos que vão re-escrever ou adicionar elementos novos
ao recipiente; um exemplo de uso é um sistema de partículas com as
partículas sendo modificadas ou adicionadas gradualmente.
// Definição da classe de memória (alojamento)
// É usado também na configuração do buffer de índices
D3DPOOL Piscina = D3DPOOL_MANAGED;
A variável Piscina define a classe de memória para o alojamento do
recurso. Esta opção, D3DPOOL_MANAGED, é uma das melhores opções
para a maioria das aplicações pois é gerenciada pelo directx e de
fácil acesso ao dispositivo renderizador. Outra vantagem dessa
classe é que quando o dispositivo renderizador é perdido o próprio
directx recupera os recursos que estão nesta classe de memória. O
dispositivo renderizador pode ser perdido quando o usuário alterna o
foco de atenção para outra aplicação.
HANDLE* hSem_Uso = NULL;
Este é o último argumento que coincide exatamente na criação dos dois
recipientes. Este argumento não tem uso dentro do directx e existe
apenas como reserva para o futuro. Seu valor deve ser sempre NULL.
// Quantidade de vértices do vertexbuffer
UINT nVertices = 4;
// Tamanho do vertexbuffer em bytes
UINT vbTamanho = sizeof(CustomVertex_TransformedColored) * nVertices;
A função de criação dos recipientes necessita saber qual o tamanho que
estes recipientes devem ter. Aqui é a preparação da informação do
tamanho do vertexbuffer.
// Formato do vértice dos vértices do vertexbuffer
DWORD vbFormato = CustomVertex_TransformedColored_Format;
A função de criação dos recipientes necessita saber qual o formato
do tipo do dado principal dos recipientes. Aqui é a indicação do
formato dos vértices que vão no vertexbuffer.
g_hr = g_device->CreateVertexBuffer(vbTamanho, uso_flags,
vbFormato, Piscina, &g_vbVertices, hSem_Uso);
Aqui ocorre a criação efetiva do buffer de vértices (vertexbuffer).
Agora vamos adentrar na configuração inicial do buffer de índices. Os
argumentos Piscina, uso_flags e hSem_Uso já foram discutidos pois são
exatamente os mesmos na mesma configuração ideal. Destacamos que
seria mais natural configurar o buffer de índices em uma função
própria para isso e dar a ele configurações diferentes conforme a
necessidade da aplicação. Mas aqui aproximamos os dois para mostrar
a estreita semelhança na criação. O mesmo vale para o acesso aos dados
em montar_Geometria() e na rederização em renderizar_Geometria() que
apresentam fatores em comuns no trabalho com estes recipientes.
// Quantidade de triângulos
UINT nTriangulos = 2;
// Cálculo do tamanho do buffer de índices
UINT ibTamanho = nTriangulos * 3 * 2;
Aqui é a preparação da informação do tamanho do indexbuffer. O tamanho
é obtido pela multiplicação sucessiva da quantidade de triângulos
(nTriangulos) que vão ser montados pela ocupação de índices que cada
triângulo toma (3) pelo tamanho em bytes (2) do tipo de dado desse
índice ( D3DFMT_INDEX16 ).
D3DFORMAT ibFormato = D3DFMT_INDEX16;
Aqui é a definição do formato do tipo de dado do buffer de índices.
Atualmente (2014) como é comum computadores e sistemas de 32 bits
para melhor performance utilize sempre que puder formatos de 32bits
onde for possível (D3DFMT_INDEX32). Naturalmente a placa aceleradora
precisa suportar as configurações feitas. Aqui utilizamos o mínimo
para rodar em qualquer placa de vídeo.
g_hr = g_device->CreateIndexBuffer(ibTamanho, uso_flags,
ibFormato, Piscina, &g_ibIndices, hSem_Uso);
Aqui ocorre a criação efetiva do buffer de índices (indexbuffer).
2.4 Montagem da geometria
void montar_Geometria(void)
{
// Posicionamento de profundidade
float zpos = 1.0f;
// Segue abaixo os argumentos utilizados no acesso ao buffer
// de vértices. Alguns deles aparecem também no acesso ao
// buffer de índices.
// Ponteiro de acesso aos dados do buffer de vértices
CustomVertex_TransformedColored* pVerts;
// nPosInicial e nQtd permite acesso parcial ao buffer de vértices
// Ambos configurados como zero (0) indica acesso ao conteúdo total
UINT nPosInicial = 0;
UINT nQtd = 0;
// Estabelece o modo de acesso ao buffer
DWORD tranca_flags = 0;
// nPosInicial, nQtd, tranca_flags também são utilizados no
// acesso ao buffer de índices com os mesmos significados.
// Aqui a aplicação ganha acesso à memória do buffer de vértices
g_vbVertices->Lock( nPosInicial, nQtd, (void**) &pVerts, tranca_flags );
// Configuração normal dos vértices agora
pVerts[0] = CustomVertex_TransformedColored( 128.0f, 50.0f, zpos,
0.7f, vermelho);
pVerts[1] = CustomVertex_TransformedColored( 512.0f, 384.0f, zpos,
0.8f, verde);
pVerts[2] = CustomVertex_TransformedColored( 128.0f, 384.0f, zpos,
1.0f, azul);
pVerts[3] = CustomVertex_TransformedColored( 512.0f, 50.0f, zpos,
1.0f, cinza);
// Buffer de índices local temporário
WORD Indices[6] = {0, 1, 2, 0, 3, 1};
// Ponteiro de acesso aos dados do buffer de índices
WORD *pIndices = NULL;
// Aqui a aplicação ganha acesso à memória do buffer de índices
g_ibIndices->Lock( nPosInicial, nQtd, (void**) &pIndices, tranca_flags );
// Configuração normal do buffer de índices
// Copiando dados do buffer local para o indexbuffer definitivo
for (int ncx=0; ncx <6; ncx++) pIndices[ncx] = Indices[ncx];
// Liberação dos buffers (vertexbuffer, indexbuffer) ao directx
// que pode mandá-los ou não para a memória de vídeo da placa
g_vbVertices->Unlock();
g_ibIndices->Unlock();
} // montar_Geometria().fim
Segue abaixo os argumentos utilizados no acesso ao buffer de
de vértices. Alguns deles aparecem também no acesso ao buffer de
índices e os demais acabam guardando certa semelhança de finalidade.
Vamos discutir primeiro os argumentos que são iguais para ambos:
UINT nPosInicial = 0;
UINT nQtd = 0;
nPosInicial e nQtd permite acesso parcial aos dados do recipiente.
Ambos configurados como zero (0) indica que a aplicação quer acesso
ao conteúdo total do recipiente.
DWORD tranca_flags = 0;
Esse flag estabelece a forma de acesso ao buffer. O zero (0) quer
dizer que sua aplicação não se preocupa com ele. Valores comuns são
D3DLOCK_READONLY ( apenas leitura ); D3DLOCK_NOOVERWRITE ( append ou
adição de dados ) e D3DLOCK_DISCARD (totalmente descartável ) que
só podem ser usados quando o uso do recipiente foi indicado como
dinâmico ( D3DUSAGE_DYNAMIC ).
CustomVertex_TransformedColored* pVerts;
Aqui é o ponteiro que vai receber o endereço de acesso aos dados do
vertexbuffer.
g_vbVertices->Lock( nPosInicial, nQtd, (void**) &pVerts, tranca_flags );
Aqui a aplicação ganha acesso ao local de memória dos dados do
recipiente.
pVerts[0] = CustomVertex_TransformedColored( 128.0f, 50.0f, zpos,
0.7f, vermelho);
// (...)
pVerts[3] = CustomVertex_TransformedColored( 512.0f, 50.0f, zpos,
1.0f, cinza);
Veja que na sequência segue a configuração normal dos vértices como se
estivessem em uma array de usuário.
Agora seguimos para a configuração do buffer de índices.
WORD Indices[6] = {0, 1, 2, 0, 3, 1};
Este é o indexbuffer de usuário do tópico anterior cujos dados serão
copiados para o indexbuffer do directx.
WORD *pIndices = NULL;
Aqui é o ponteiro que vai receber o endereço de acesso aos dados do
indexbuffer.
g_ibIndices->Lock( nPosInicial, nQtd, (void**) &pIndices, tranca_flags );
Aqui a aplicação ganha acesso ao local de memória dos dados do
recipiente. Lembramos que para ambos os recipientes, nPosInicial, nQtd
e tranca_flags foram todos configurados como zero (0) para acesso
total e incondicional aos dados nesse momento inicial de configuração.
for (int ncx=0; ncx <6; ncx++) pIndices[ncx] = Indices[ncx];
Aqui é a configuração normal do buffer de índices como se estivessem
em uma array de usuário. Aqui copiamos os dados do buffer local para
o indexbuffer definitivo.
// Liberação dos buffers (vertexbuffer, indexbuffer) ao directx
// que pode mandá-los ou não para a memória de vídeo da placa
g_vbVertices->Unlock();
g_ibIndices->Unlock();
Aqui é uma operação fundamental: a liberação dos recipientes para uso
exclusivo do directx e placa de vídeo.
2.5 Renderização da geometria
void renderizar_Geometria()
{
// Declara o formato de vértice utilizado pela aplicação
g_device->SetFVF( CustomVertex_TransformedColored_Format);
// Argumentos da função DrawIndexedPrimitive()
// A finalidade destes 5 argumentos é permitir a renderização
// parcial ou uma região localizada do vertexbuffer.
// A configuração deste contexto é para renderizar totalmente
// o buffer de vértices.
UINT nVerticeBase = 0;
UINT nIndiceInicial = 0;
UINT nMinVertexIndice = 0;
UINT nVerticeQtd = 4;
UINT nContagemPrim = 2;
// O passo (stride) permite ao directx navegar com segurança pelos
// vértices apresentados.
UINT nPasso = sizeof(CustomVertex_TransformedColored);
// Indicação do stream fonte dos dados
UINT idStream = 0;
// Informação do buffer de vértices utilizado
g_device->SetStreamSource( idStream, g_vbVertices, nVerticeBase, nPasso);
// Informação do buffer de índices utilizado
g_device->SetIndices( g_ibIndices);
// Renderização efetiva dos vértices
g_device->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, nVerticeBase,
nMinVertexIndice, nVerticeQtd, nIndiceInicial, nContagemPrim);
} // renderizar_Geometria().fim
// Declara o formato de vértice utilizado pela aplicação
g_device->SetFVF( CustomVertex_TransformedColored_Format);
Aqui é a configuração do formato de vértice que deve ser usado para
renderizar o vertexbuffer apresentado. Esta configuração não precisa
bater exatamente com a configuração dos vértices mas não pode ser
mais volumosa que a configuração do formato dos vértices; não posso
renderizar luz ou textura se o formato de vértice não trouxer essa
informação mas posso escolher não renderizar esses dados se eles
estiverem presentes.
Agora discutiremos os argumentos da função DrawIndexedPrimitive().
UINT nVerticeBase = 0;
UINT nIndiceInicial = 0;
UINT nMinVertexIndice = 0;
A finalidade destes argumentos é permitir que ocorra a renderização
parcial do vertexbuffer.Como todos estão configurados como zero (0)
que é o índice base de qualquer array indicamos que queremos
renderizar totalmente o vertexbuffer.
UINT nVerticeQtd = 4;
UINT nContagemPrim = 2;
Aqui indicamos a quantidade presente de vértices no vertexbuffer e
quantas primitivas ( triângulos ) queremos produzir com eles.
UINT nPasso = sizeof(CustomVertex_TransformedColored);
O passo (stride) permite ao directx navegar com segurança pelos
vértices apresentados usando a matemática dos ponteiros.
// Indicação do stream fonte dos dados
UINT idStream = 0;
Este argumento é pouco esclarecido na documentação original do directx
e ao longo de todo o nosso curso esse valor é zero (0). Ele indica
um fluxo de dados que traz apenas um componente dos vértices tal como
posicionamento ou apenas coordenadas de textura.
// Informação do buffer de vértices utilizado
g_device->SetStreamSource( idStream, g_vbVertices, nVerticeBase, nPasso);
Aqui é indicado ao dispositivo renderizador o vertexbuffer de
trabalho.
g_device->SetIndices( g_ibIndices);
Aqui é indicado ao dispositivo renderizador o indexbuffer de
trabalho.
g_device->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, nVerticeBase,
nMinVertexIndice, nVerticeQtd, nIndiceInicial, nContagemPrim);
Aqui ocorre a renderização efetiva do vertexbuffer.
3. Código fonte do projeto de exemplo: prj_Buffers
// Projeto: prj_Buffers - Arquivo: motor.h
// Esta aplicação ilustra como renderizar um quadrado
// utilizando as interfaces do directx para buffer de
// índices e buffer de vértices. By www.gameprog.com.br
#ifndef motor_h
#define motor_h
// Esta função inicializa o Direct3D
HRESULT initGfx (
HWND hJanela);
// Essa função libera os objetos utilizados
void Limpar();
// Essa função desenha a cena
void Renderizar();
// Essa função inicializa o buffer de índices e
// o buffer de vértices
void inicializar_Buffers(
void);
// Essa função monta formas geométricas
void montar_Geometria (
void);
// Renderiza os vértices em formas geométricas
void renderizar_Geometria (
void);
// Declaração da função que atende as mensagens da janela
LRESULT CALLBACK processaJanela (
HWND hJanela,
UINT mensagem,
WPARAM wParam,
LPARAM lParam);
#endif
// -----------------------------------------------------------------------------
// Projeto: prj_Buffers - arquivo: motor.cpp
// Esta aplicação ilustra como renderizar um quadrado
// utilizando as interfaces do directx para buffer de
// índices e buffer de vértices. By www.gameprog.com.br
// -----------------------------------------------------------------------------
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <stdio.h>
#include "motor.h"
// Inclui as bibliotecas do Direct3D
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
// Variáveis globais
// Representa o dispositivo Direct3D
LPDIRECT3D9 g_Direct3d = NULL;
// Representa o dispositivo Renderizador
IDirect3DDevice9* g_device = NULL;
// Representa o buffer de vértices
IDirect3DVertexBuffer9* g_vbVertices = NULL;
// Representa o buffer de índices
IDirect3DIndexBuffer9* g_ibIndices = NULL;
// Essa variável recebe informação de erro do Directx
HRESULT g_hr = 0;
// Tamanho da janela
extern int g_xtela;
extern int g_ytela;
// Constante para cores
const DWORD vermelho = 0xFFFF0000;
const DWORD branco = 0xFFFFFFFF;
const DWORD verde = 0xFF00FF00;
const DWORD azul = 0xFF0000FF;
const DWORD cinza = 0xFF101010;
// Definição do formato de vértice utilizado por esta aplicação
#define CustomVertex_TransformedColored_Format(D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
// Estrutura do vértice customizado
struct CustomVertex_TransformedColored
{
float x, y, z;
float rhw;
DWORD cor;
// Construtor default
CustomVertex_TransformedColored() {}
CustomVertex_TransformedColored( float _x, float _y, float _z,
float _rhw, DWORD _cor)
{
x = _x;
y = _y;
z = _z;
rhw = _rhw;
cor = _cor;
}
}; // fim da estrutura CustomVertex_TransformedColored
// initGfx() - Inicializa o Direct3D
HRESULT initGfx( HWND hJanela )
{
// Cria o objeto Direct3D que é necessário para criar o dispositivo gráfico
g_Direct3d = Direct3DCreate9( D3D_SDK_VERSION);
// Verifica se o objeto Direct3D foi criado
if(g_Direct3d == NULL)
{
MessageBox (NULL,
"Falha na inialização do Direct3D", "InitGfx()", MB_OK);
return E_FAIL;
} // endif
// Declara a variável para os parâmetros de apresentação
D3DPRESENT_PARAMETERS pps;
// Limpa a estrutura
ZeroMemory( &pps, sizeof(pps) );
// Configura os parâmetros de apresentação
// A aplicação vai ter janela
pps.Windowed = TRUE;
// Esse método transfere rapidamente o backbuffer para a tela imediata
pps.SwapEffect = D3DSWAPEFFECT_DISCARD;
// Esse formato vai procurar se encaixar no modo de video corrente
pps.BackBufferFormat = D3DFMT_UNKNOWN;
// Configuração do renderizador a ser criado
// Adaptador default (0)
int nAdaptador = D3DADAPTER_DEFAULT;
// Tipo de dispositivo Hardware ou emulador de referência (software)
D3DDEVTYPE dispositivo_tipo = D3DDEVTYPE_HAL;
// Flags de configuração do dispositivo
DWORD create_flags = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
// Criamos aqui o dispositivo renderizador
g_hr = g_Direct3d->CreateDevice( nAdaptador, dispositivo_tipo,
hJanela, create_flags, &pps, &g_device );
// Verifica se houve falha no processo
if( FAILED( g_hr ) ) {
MessageBox (NULL, "Falha na criação: g_device", "initGfx()", MB_OK);
return E_FAIL;
} // endif
inicializar_Buffers();
montar_Geometria();
return S_OK;
} // initGfx().fim
void inicializar_Buffers(void)
{
// Segue abaixo os argumentos utilizados na criação do buffer
// de vértices. Alguns deles aparecem também na criação do
// buffer de índices.
// Quantidade de vértices do vertexbuffer
UINT nVertices = 4;
// Tamanho do vertexbuffer em bytes
UINT vbTamanho = sizeof(CustomVertex_TransformedColored) * nVertices;
// Dica de uso.
// É usado usado também na configuração do buffer de índices
DWORD uso_flags = D3DUSAGE_WRITEONLY;
// Formato do vértice dos vértices do vertexbuffer
DWORD vbFormato = CustomVertex_TransformedColored_Format;
// Definição da classe de memória (alojamento)
// É usado também na configuração do buffer de índices
D3DPOOL Piscina = D3DPOOL_MANAGED;
// Argumento sem uso
// Aparece também na criação do buffer de índices
HANDLE* hSem_Uso = NULL;
// Criação efetiva do buffer de vértices (vertexbuffer)
g_hr = g_device->CreateVertexBuffer(vbTamanho, uso_flags,
vbFormato, Piscina, &g_vbVertices, hSem_Uso);
// Verifica falha na criação
if(FAILED (g_hr) ) {
MessageBox (NULL,
"Falha na criação do buffer de vértices",
"inicializar_Buffers()", MB_OK);
return;
} // endif
// Quantidade de triângulos
UINT nTriangulos = 2;
// Cálculo do tamanho do buffer de índices
UINT ibTamanho = nTriangulos * 3 * 2;
// Formato do tipo de dado do buffer de índices
D3DFORMAT ibFormato = D3DFMT_INDEX16;
// Criação efetiva do buffer de índices
g_hr = g_device->CreateIndexBuffer(ibTamanho, uso_flags,
ibFormato, Piscina, &g_ibIndices, hSem_Uso);
// Verifica falha na criação
if(FAILED (g_hr) ) {
MessageBox (NULL,
"Falha na criação do buffer de vértices",
"inicializar_Buffers()", MB_OK);
return;
} // endif
} // inicializar_Buffers().fim
void montar_Geometria(void)
{
// Posicionamento de profundidade
float zpos = 1.0f;
// Segue abaixo os argumentos utilizados no acesso ao buffer
// de vértices. Alguns deles aparecem também no acesso ao
// buffer de índices.
// Ponteiro de acesso aos dados do buffer de vértices
CustomVertex_TransformedColored* pVerts;
// nPosInicial e nQtd permite acesso parcial ao buffer de vértices
// Ambos configurados como zero (0) indica acesso ao conteúdo total
UINT nPosInicial = 0;
UINT nQtd = 0;
// Estabelece o modo de acesso ao buffer
DWORD tranca_flags = 0;
// nPosInicial, nQtd, tranca_flags também são utilizados no
// acesso ao buffer de índices com os mesmos significados.
// Aqui a aplicação ganha acesso à memória do buffer de vértices
g_vbVertices->Lock( nPosInicial, nQtd, (void**) &pVerts, tranca_flags );
// Configuração normal dos vértices agora
pVerts[0] = CustomVertex_TransformedColored( 128.0f, 50.0f, zpos,
0.7f, vermelho);
pVerts[1] = CustomVertex_TransformedColored( 512.0f, 384.0f, zpos,
0.8f, verde);
pVerts[2] = CustomVertex_TransformedColored( 128.0f, 384.0f, zpos,
1.0f, azul);
pVerts[3] = CustomVertex_TransformedColored( 512.0f, 50.0f, zpos,
1.0f, cinza);
// Buffer de índices local temporário
WORD Indices[6] = {0, 1, 2, 0, 3, 1};
// Ponteiro de acesso aos dados do buffer de índices
WORD *pIndices = NULL;
// Aqui a aplicação ganha acesso à memória do buffer de índices
g_ibIndices->Lock( nPosInicial, nQtd, (void**) &pIndices, tranca_flags );
// Configuração normal do buffer de índices
// Copiando dados do buffer local para o indexbuffer definitivo
for (int ncx=0; ncx <6; ncx++) pIndices[ncx] = Indices[ncx];
// Liberação dos buffers (vertexbuffer, indexbuffer) ao directx
// que pode mandá-los ou não para a memória de vídeo da placa
g_vbVertices->Unlock();
g_ibIndices->Unlock();
} // montar_Geometria().fim
void renderizar_Geometria()
{
// Declara o formato de vértice utilizado pela aplicação
g_device->SetFVF( CustomVertex_TransformedColored_Format);
// Argumentos da função DrawIndexedPrimitive()
// A finalidade destes 5 argumentos é permitir a renderização
// parcial ou uma região localizada do vertexbuffer.
// A configuração deste contexto é para renderizar totalmente
// o buffer de vértices.
UINT nVerticeBase = 0;
UINT nIndiceInicial = 0;
UINT nMinVertexIndice = 0;
UINT nVerticeQtd = 4;
UINT nContagemPrim = 2;
// O passo (stride) permite ao directx navegar com segurança pelos
// vértices apresentados.
UINT nPasso = sizeof(CustomVertex_TransformedColored);
// Indicação do stream fonte dos dados
UINT idStream = 0;
// Informação do buffer de vértices utilizado
g_device->SetStreamSource( idStream, g_vbVertices, nVerticeBase, nPasso);
// Informação do buffer de índices utilizado
g_device->SetIndices( g_ibIndices);
// Renderização efetiva dos vértices
g_device->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, nVerticeBase,
nMinVertexIndice, nVerticeQtd, nIndiceInicial, nContagemPrim);
} // renderizar_Geometria().fim
// Esta função é chamada por DispatchMessage()
LRESULT CALLBACK processaJanela (HWND hJanela, UINT mensagem,
WPARAM wParam, LPARAM lParam)
{
switch (mensagem)
{
case WM_DESTROY:
// Coloca uma mensagem WM_QUIT na fila de mensagem
Limpar();
PostQuitMessage (0);
break;
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
{
Limpar();
PostQuitMessage( 0);
} // endif
break;
// Essa mensagem vai ocorrer a todo momento
case WM_PAINT:
// Renderiza a cena
Renderizar();
// Invalida a tela para chamar WM_PAINT novamente
InvalidateRect( hJanela, NULL, false);
break;
// Processamento default de mensagens não tratada pela aplicação
default:
return DefWindowProc (hJanela, mensagem, wParam, lParam);
} // endswitch
return 0;
} // processaJanela().fim
// Limpar() - Libera todos os objetos previamente inicializados
// -----------------------------------------------------------------------------
VOID Limpar()
{
// Libera o buffer de vértices
if( g_vbVertices != NULL) g_vbVertices->Release();
// Libera o buffer de índices
if( g_ibIndices != NULL) g_ibIndices->Release();
// Libera o dispositivo gráfico
if( g_device != NULL) g_device->Release();
// Libera o motor do Direct3D
if( g_Direct3d != NULL) g_Direct3d->Release();
} // Limpar().fim
// -----------------------------------------------------------------------------
// Renderizar() - Desenha a cena
// -----------------------------------------------------------------------------
VOID Renderizar()
{
// Retorne se o dispositivo estiver nulo
if( g_device == NULL) return;
// Limpa o backbuffer com uma cor branca
g_device->Clear( 0, NULL, D3DCLEAR_TARGET, branco, 1.0f, 0);
// Começa a cena
if( SUCCEEDED( g_device->BeginScene() ) )
{
// Vamos renderizar a geometria
renderizar_Geometria();
// Finalizando a cena
g_device->EndScene();
} // endif
// Apresenta o conteúdo do backbuffer na tela
g_device->Present( NULL, NULL, NULL, NULL);
} // Renderizar().fim
//-----------------------------------------------------------------------------
// Projeto: prj_Buffers - arquivo: entrada.cpp
// Esta aplicação ilustra como renderizar um quadrado
// utilizando as interfaces do directx para buffer de
// índices e buffer de vértices. By www.gameprog.com.br
//-----------------------------------------------------------------------------
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include "motor.h"
// Variável global da classe da janela
char sclasseJanela[ ] = "cls_directx";
// Dimensões da janela
int g_xtela = 640;
int g_ytela = 480;
int WINAPI WinMain (HINSTANCE app_instancia, HINSTANCE app_anterior,
LPSTR sComando,int nExibir) {
// alça da janela
HWND hJanela;
// Estrutura de recepção das mensagens
MSG mensagem;
// Estrutura de descrição da janela
WNDCLASSEX wcls;
// Estrutura que descreve a janela
wcls.hInstance = app_instancia;
wcls.lpszClassName = sclasseJanela;
wcls.lpfnWndProc = processaJanela;
wcls.style = CS_HREDRAW | CS_VREDRAW;
wcls.cbSize = sizeof (WNDCLASSEX);
// O cursor e os ícones da aplicação são default
wcls.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wcls.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wcls.hCursor = LoadCursor (NULL, IDC_ARROW);
// Aplicação sem menu
wcls.lpszMenuName = NULL;
// Nada de espaço extra atrelado a classe da janela (wcls)
wcls.cbClsExtra = 0;
// Nada de espaço extra atrelado a janela
wcls.cbWndExtra = 0;
// Cor default da janela
wcls.hbrBackground = ( HBRUSH) COLOR_BACKGROUND;
// Registra a janela e retorna se esta operação falhar
int status = RegisterClassEx (&wcls);
if(status == 0) {
MessageBox(NULL, "Registro falhou!", "WinMain()", MB_OK);
return 0;
} // endif
// Com a classe criada pode-se criar a janela
DWORD estiloExtra = 0;
const char janelaTitulo[] = "prj_Buffers";
DWORD controleEstilo = WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX;
int xpos = 160;
int ypos = 120;
HWND hjanelaPai = HWND_DESKTOP;
HMENU sem_menu = NULL;
LPVOID dadoExtra = NULL;
// Cria a janela
hJanela = CreateWindowEx(estiloExtra, sclasseJanela, janelaTitulo,
controleEstilo, xpos, xpos, g_xtela, g_ytela, hjanelaPai, sem_menu,
app_instancia, dadoExtra );
// Verifica se janela foi criada
if(hJanela == NULL) {
MessageBox(NULL, "Falha na criação da janela!", "WinMain()", MB_OK);
return 0;
} // endif
// Essa variável recebe informação de erro do Directx
HRESULT hr;
// Inicia o Direct3D
hr = initGfx ( hJanela );
// Encerre a aplicação se houve falha
if(FAILED (hr) ) {
MessageBox (hJanela,
"Direct3D: falha na inicialização", "WinMain()", MB_OK);
UnregisterClass( sclasseJanela, wcls.hInstance);
return E_FAIL;
} // endif
// Mostra a janela
ShowWindow(hJanela, nExibir);
UpdateWindow(hJanela );
// Rode a bombeamento de mensagens até GetMessage() retornar 0
while (GetMessage(&mensagem, NULL, 0, 0))
{
// Traduz mensagem de tecla virtual em mensagem de caracteres
TranslateMessage( &mensagem );
// Despacha a mensagem para a função processaJanela */
DispatchMessage( &mensagem );
} // endwhile
// O valor de retorno é zero(0) passado por PostQuitMessage()
UnregisterClass( sclasseJanela, wcls.hInstance);
return mensagem.wParam;
} // WinMain().fim