Gamedev-id
Tutorial
#0003, Last reviewed : 18 Mei 2002
Menampilkan
Gambar BMP Dengan DirectDraw 7
Tutorial ini akan menjelaskan langkah awal menggunakan DirectDraw, dimulai
dengan menampilkan sebuah gambar BMP.
Source
code & Executable: tut0003.zip (179 kb)
Syarat minimal: C/C++ Compiler for Windows (Rekomendasi: Visual C++),
DirectX SDK 7.0
Daftar Isi:
1. Persiapan
2. Definisi
3. Variabel
4. Struktur Utama
4a. Windows Procedure
5.
Inisialisasi Windows
6. Inisialisasi DirectDraw
6a. Inisialisasi DirectDraw Windowed
6b. Inisialisasi DirectDraw Fullscreen
7. Deinisialisasi DirectDraw
8. Loading Gambar
9. Menampilkan Gambar
10. Akhir
1.
Persiapan
Sebelum mulai pastikan DirectX
SDK telah terinstal, dan seting direktori include dan library SDK tersebut
sudah diset dengan benar pada compiler yang digunakan. Lihat artikel DirectX
SDK dan Instalasi untuk lebih jelas.
Pada code kita siapkan beberapa header:
#include
"windows.h"
#include "ddraw.h"
#include "ddutil.h"
Seperti biasa kita membutuhkan windows.h karena berisi definisi esensial untuk program Window. Kemudian ddraw.h yang digunakan karena kita akan menggunakan DirectDraw (apabila setting SDK tidak benar maka file ini tidak akan ketemu). Header terakhir ddutil.h merupakan utiliti dari SDK DirectX 7 (disertakan di source code di atas) yang membantu untuk me-load gambar dalam format BMP.
2.
Definisi
Setelah siap kita akan mendefinisikan beberapa parameter, sementara ini fix atau hard-code, jadi tidak bisa berubah setelah program jadi.
#define APP_CLASS "Tut03" #define APP_TITLE "Gamedevid Tutorial #03 - My First DirectX Apps"
#define SPRITE_WIDTH 640 #define SPRITE_HEIGHT 480 #define SPRITE_NAME "bg.bmp"
#define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 #define SCREEN_BPP 32
#define FULLSCREEN false
Parameter
di atas mendefinisikan nama class, judul aplikasi, ukuran gambar dan namanya
yang akan ditampilkan, ukuran window dan mode aplikasi (fullscreen atau
dengan window). Semua parameter ini boleh diganti bebas menyesuaikan.
Contoh ukuran sprite menyesuaikan file gambar yang digunakan, ukuran layar
fullscreen harus proporsional/standar, tidak mungkin 100x100, dan sebagainya.
3.
Variabel
Dalam aplikasi kali ini kita akan mendefinisikan semua variabel secara
global.
HWND g_hWnd = 0;
LPDIRECTDRAW7 g_pDDraw = 0;
LPDIRECTDRAWSURFACE7 g_pFrontBuffer = 0;
LPDIRECTDRAWSURFACE7 g_pSprite = 0;
HWND jelas sebagai handle window/aplikasi yang akan kita buat. Kemudian
ada obyek Direct Draw yang akan disimpan dalam variabel g_pDDraw, yaitu
engine utama dari sistem DirectDraw. Ditambah 2 buah 'surface' untuk Direct
Draw yaitu dimana kita akan meletakkan gambar atau menggambar. FrontBuffer
merupakan layar utama yang akan ditampilkan ke monitor, sedangkan Sprite
merupakan layar yang akan kita isi dan kita tampilkan ke Front Buffer.
4.
Struktur Utama
Dalam fungsi WinMain akan dilakukan 3 langkah inisialisasi:
1. Inisialisasi window ( dan menampilkan )
2. Inisialisasi DirectDraw
3. dan Loading Gambar
kemudian akan disertai dengan message loop dan akhirnya proses di-deinisialisasi.
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow ) {
// ----- Init windows ------
g_hWnd = InitWindow( hInstance );
if (!g_hWnd)
{
MessageBox(NULL, "Creating window failed", APP_TITLE, 0);
return false;
}
ShowCursor( !FULLSCREEN ); // if fullscreen, hide cursor
ShowWindow(g_hWnd, SW_SHOWNORMAL);
UpdateWindow(g_hWnd);
// ----- Init DirectX ------
bool result;
if ( FULLSCREEN )
{
result = InitDDrawFullScreen();
}
else
{
result = InitDDrawWindowed();
}
if ( !result )
{
MessageBox(NULL, "DDraw initialization failed", APP_TITLE, 0);
DestroyObjects();
return false;
}
// ----- Load Sprites ------
LoadSomething();
// ----- Play !! (Loop) ------
MSG msg; while(1) { if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if( msg.message == WM_QUIT ) break; TranslateMessage(&msg); DispatchMessage(&msg); } else { DoSomething(); } }
// ----- TheEnd ------
DestroyObjects();
return 0; } // end of WinMain
4a.
Window Procedure
Jangan lupa menambahkan Window Procedure untuk menangkap pesan (messages).
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0L;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
5.
Inisialisasi Windows
Di sini inisialisasi windows seperti biasa, hanya dibedakan
pada setting style window dan ukuran.
Untuk style, pada windowed mode menggunakan style standar yaitu WS_OVERLAPPEDWINDOW
tanpa tombol maximize. Sedangkan untuk mode fullscreen menggunakan WS_POPUP
yaitu aplikasi tanpa window sama sekali atau kosong, dan juga diset agar
selalu di atas (mencegah tertutup oleh aplikasi lain). Pada mode fullscreen
kursor dimatikan.
Pada windowed mode, ukuran window dimodifikasi dengan ditambah ukuran-ukuran
pinggiran window. Perlu diingat bahwa ukuran yang kita sebut adalah ukuran
aplikasi window keseluruhan bukan hanya isinya (client) sehingga apabila
kita memiliki ukuran isi 640 x 480 maka ukuran window sebenarnya harus
lebih besar ( lebar ditambah kedua border, tinggi ditambah kedua border,
menu dan title bar ).
HWND InitWindow( HINSTANCE hInstance )
{
// setting params
HCURSOR hCursor; DWORD dwStyle = 0, dwStyleEx = 0; int width = SCREEN_WIDTH, height = SCREEN_HEIGHT; int left = 0, top = 0;
if ( FULLSCREEN )
{
hCursor = NULL;
dwStyle = WS_POPUP;
dwStyleEx = WS_EX_TOPMOST;
}
else
{
hCursor = LoadCursor(NULL, IDC_ARROW);
dwStyle = WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX;
width += GetSystemMetrics( SM_CXSIZEFRAME ) * 2;
height += GetSystemMetrics( SM_CYSIZEFRAME ) * 2 +
GetSystemMetrics( SM_CYMENU ) +
GetSystemMetrics( SM_CYCAPTION );
left = CW_USEDEFAULT;
top = CW_USEDEFAULT;
}
// Registering Window //////////////////////
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hIconSm = 0;
wndclass.hCursor = hCursor;
wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = APP_CLASS;
if (RegisterClassEx(&wndclass) == 0)
{
MessageBox(NULL, "Registering window failed", APP_TITLE, 0);
return false;
}
// Creating Window //////////////////////
HWND hWnd = CreateWindowEx( dwStyleEx,
APP_CLASS,
APP_TITLE,
dwStyle,
left,
top,
width,
height,
NULL,
NULL,
hInstance,
NULL );
return hWnd;
}
6. Inisialisasi DirectDraw
Inisialisasi membutuhkan 3 langkah minimal:
1. Membuat obyek DirectDraw
2. Mengeset level kooperatif
3. Buat
primary surface
Untuk
mode windowed tambahkan langkah ke-4:
4. Buat clipper
Obyek
DirectDraw wajib dibuat karena merupakan engine utama. Level kooperatif
digunakan secara internal oleh DirectDraw untuk mengatur kerjasamanya
dengan sistem operasi Windows. Setelah siap maka sebuah surface primary
harus dibuat sebagai tempat untuk menggambar bitmap. Sebagai tambahan
untuk mode windowed, harus memiliki clipper yang berfungsi untuk memotong
layar apabila window aplikasi tertutup oleh window lain. Tentu saja mode
full screen tidak perlu karena seharusnya tidak dapat terhalang oleh window
lain.
Langkah-langkah di atas merupakan langkah wajib yang harus dilakukan.
6a.
Inisialisasi DirectDraw Windowed
bool InitDDrawWindowed()
{
HRESULT result;
result = DirectDrawCreateEx(NULL, (VOID**)&g_pDDraw, IID_IDirectDraw7, NULL);
if (result != DD_OK)
{
g_pDDraw = 0;
return false;
}
result = g_pDDraw->SetCooperativeLevel(g_hWnd, DDSCL_NORMAL);
if (result != DD_OK)
{
return false;
}
DirectDraw
dibuat dengan memanggil fungsi DirectDrawCreateEx() dengan parameter :
1. GUID (global identifier) untuk driver display, NULL untuk display yang
aktif.
2. Alamat ke pointer DDraw
3. Nama interface DirectDraw, harus IID_DIrectDraw7 untuk DDraw 7.
4. Selalu NULL, parameter cadangan.
Setelah
dibuat maka diset level kooperasinya normal seperti di atas, sedangkan
untuk fullscreen nantinya digunakan setting eksklusif karena sifatnya
spesial menempati seluruh layar (lihat kode 6b.).
DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
ddsd.dwFlags = DDSD_CAPS;
result = g_pDDraw->CreateSurface(&ddsd, &g_pFrontBuffer, NULL);
if (result != DD_OK) return FALSE;
Cara
membuat surface DirectDraw dilakukan dengan mendeskripsikan surface yang
akan kita buat dan kemudian memanggil fungsi CreateSurface(). Sebelum
digunakan Direct Draw surface description dibersihkan dulu dengan diset
0 agar bersih. Sebelum diisi dengan data, surface description harus diberitahu
terlebih dahulu data apa yang akan diisi, di bagian flag. Seperti pada
kode di atas, disebutkan agar mengecek parameter caps yang diisi DDSCAPS_PRIMARYSURFACE.
Parameter ini spesial, memberitahukan bahwa surface ini primary surface
yang berhubungan dengan display secara langsung. Parameter 3 selalu NULL.
Untuk cara membuat surface standard diperlihatkan di bawah ini.
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
ddsd.dwWidth = SCREEN_WIDTH;
ddsd.dwHeight = SCREEN_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE;
result = g_pDDraw->CreateSurface(&ddsd, &g_pBackBuffer, NULL);
if (result != DD_OK)
{
return false;
}
Pada dasarnya mode windowed tidak memiliki back buffer, maka dibuat back buffer sementara yang sebenarnya sama dengan surface biasa, bukan spesial seperti yang ada pada mode fullscren (lihat kode 6b.). Seperti disebutkan di atas bahwa untuk mengisi data, surface description harus terlebih dulu diberitahu di flag. Di sini disebutkan ukuran lebar dan tinggi (yang mana tidak dibutuhkan saat membuat primary surface yang spesial), dan beberapa caps standard.
LPDIRECTDRAWCLIPPER pClipper;
result = g_pDDraw->CreateClipper(0, &pClipper, NULL);
if (result != DD_OK)
return false;
pClipper->SetHWnd(0, g_hWnd);
g_pFrontBuffer->SetClipper(pClipper);
pClipper->Release();
pClipper = NULL;
return true;
}
Sebelum selesai, khusus untuk mode windowed harus dibuat clipper. Gunanya untuk memotong gambar apabila tertutup oleh window lain (dan hanya dimungkinkan pada mode windowed). Setelah obyek Clipper dibuat dan diberikan ke surface yang akan di-clip (yaitu surface utama) maka obyek Clipper bisa dilepas. Dan selesailah DirectDraw beserta 2 surface dibuat.
6b. Inisialisasi DirectDraw Fullscreen
Mode fullscreen tidak jauh berbeda dengan mode windowed kecuali untuk beberapa hal. Pertama yaitu level kooperasi yang diset ekslusif dan tentu saja harus mengubah resolusi monitor. Perlu diingat bahwa ukuran lebar, tinggi dan kedalaman warna (16, 256, true color, 32-bit), harus didukung oleh hardware. DirectX sendiri mempunyai fitur untuk mengetahui hal ini yangmana tidak dibahas di sini.
bool InitDDrawFullScreen()
{
HRESULT result;
//1. Create DDraw Device
result = DirectDrawCreateEx(NULL, (VOID**)&g_pDDraw, IID_IDirectDraw7, NULL);
if (result != DD_OK)
{
g_pDDraw = 0;
return false;
}
//2. Set Cooperative Level
result = g_pDDraw->SetCooperativeLevel( g_hWnd, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN );
if (result != DD_OK) return false;
// Set the display mode
if( FAILED( g_pDDraw->SetDisplayMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, 0, 0 ) ) )
{
MessageBox(NULL, "Unsupported display mode", APP_TITLE, 0);
return false;
}
Perbedaan utama pada saat pembuatan surface adalah didefinisilannya DDSCAPS_FLIP yang memberitahukan bahwa akan digunakan mekanisme flip dan parameter yang memberitahu jumlah back buffer yang akan digunakan (bisa lebih dari 1). Maka otomatis backbuffer akan juga dibuat. Untuk mendapatkan back buffer maka digunakan GetAttachedSurface dengan struktur DDSCAPS2 untuk mendefinisikan permintaan.
//3. Creating Surfaces
DDSURFACEDESC2 ddsd;
// Set surface description
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |
DDSCAPS_COMPLEX | DDSCAPS_3DDEVICE;
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.dwBackBufferCount = 1;
result = g_pDDraw->CreateSurface(&ddsd, &g_pFrontBuffer, NULL);
if (result != DD_OK)
return false;
// Get a pointer to the back buffer
DDSCAPS2 ddscaps;
ZeroMemory( &ddscaps, sizeof( ddscaps ) );
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
if( FAILED( result = g_pFrontBuffer->GetAttachedSurface( &ddscaps,
&g_pBackBuffer ) ) ) { return false; }
return true;
}
7.
Deinisialisasi DirectDraw
Apabila semua telah terinisialiasi maka DirectDraw siap digunakan. Sebelum
melihat penggunaan, maka sebelumnya kita lihat proses deinisialisasinya.
void DestroyObjects()
{
if (g_pBackBuffer)
{
g_pBackBuffer->Release();
g_pBackBuffer = NULL;
}
if (g_pFrontBuffer)
{
g_pFrontBuffer->Release();
g_pFrontBuffer = NULL;
}
if (g_pDDraw)
{
g_pDDraw->SetCooperativeLevel( g_hWnd, DDSCL_NORMAL );
g_pDDraw->Release();
g_pDDraw = NULL;
}
ShowCursor( true ); }
Prosesnya cukup mudah dengan tidak lupa me-release semua obyek yang kita buat. Tambahan pada deinisialisasi DirectDraw dengan mengembalikan resolusi layar secara eksplisit. Dan juga mengembalikan kursor supaya muncul kembali apabila sebelumnya tidak ditampilkan.
8.
Loading gambar.
void LoadSomething()
{
g_pSprite = DDLoadBitmap(g_pDDraw, TILE_NAME, TILE_WIDTH, TILE_HEIGHT);
}
Cara me-load gambar cukup mudah dengan hanya memanggil 1 fungsi yang disediakan dari ddutil. Fungsi menggunakan parameter obyek DirectDraw, nama file (atau resource), lebar, tinggi, dan menghasilkan surface DirectDraw siap dengan gambar sprite.
9.
Menampilkan gambar.
void DoSomething()
{
/// Clear ////////////////////////////////////////////////////////////////////////////////
DDBLTFX ddbltfx;
ZeroMemory(&ddbltfx, sizeof(ddbltfx));
ddbltfx.dwSize = sizeof(ddbltfx);
ddbltfx.dwFillColor = 0; // fill with black color (0)
g_pBackBuffer->Blt(0, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx);
/// Display ////////////////////////////////////////////////////////////////////////////////
g_pBackBuffer->Blt(0, g_pSprite, 0, DDBLT_WAIT, 0);
/// Flip ////////////////////////////////////////////////////////////////////////////////
if (FULLSCREEN)
{
g_pFrontBuffer->Flip(NULL, DDFLIP_WAIT);
}
else
{
RECT rect;
GetClientRect( g_hWnd, &rect );
ClientToScreen( g_hWnd, (POINT*)&rect );
ClientToScreen( g_hWnd, (POINT*)&rect+1 );
while (g_pFrontBuffer->GetBltStatus(DDGBS_ISBLTDONE) == DDERR_WASSTILLDRAWING);
g_pFrontBuffer->Blt(&rect, g_pBackBuffer, 0, DDBLT_WAIT, 0);
}
}
Fungsi
utama dari menggambar atau memindahkan gambar dari satu surface ke surface
lain ada di bagian 'Display'. Gunakan fungsi Blt yang menggunakan parameter:
1. Rectangle Destination, daerah dimana gambar akan di-blit atau digambar.
2. Surface yang akan di-blit.
3. Rectangle Source, daerah dimana gambar asal diambil.
4. Flag yang mengontrol cara blit bekerja.
5. Parameter tambahan yang biasanya melengkapi parameter ke-4.
Apabila ukuran daerah yang akan di-blit dan targetnya tidak sama maka
gambar akan diskala (perbesar atau perkecil). Parameter DDBLT_WAIT mewajibkan
proses blit untuk melakukan blit hingga proses blit berhasil, hal ini
dipakai karena tidak selalu setiap fungsi blit berhasil dan berujung pada
belumkeluarnya gambar pada surface target. Parameter WAIT memastikan bahwa
fungsi tidak akan kembali sebelum proses blit berhasil.
Parameter ke-4 merupakan parameter paling kompleks. Bisa merujuk ke dokumentasi
DirectX SDK untuk bagian ini.
Bagian pertama dari fungsi penampil gambar adalah membersihkan dulu surface
(back) dengan warna hitam, fungsi ini memastikan bahwa surface selalu
bersih sebelum kita gambari karena ada kemungkinan sisa-sisa program sebelumnya
masih ada pada surface. Fungsi membersihkan dilakukan dengan mengisi warna
ke layar. Digunakan fungsi blit dengan 3 parameter pertama kosong (karena
tidak melakukan blit apapun), kemudian parameter ke 4 memberitahu bahwa
ini proses pengisian warna dengan warnanya sendiri dimasukkan ke parameter
ke-5.
Kemudian gambar di-blit ke backbuffer. Karena digambar ke back-buffer
maka gambar tidak langsung muncul di layar. Sebenarnya gambar bisa digambar
langsung ke Front-buffer (primary) tetapi akan nampak tidak halus (efek
flicker) maka digunakan teknik back buffer yang sudah sangat umum ini.
Bagian Flip digunakan apabil gambar digambarkan melalui backbuffer.
Untuk mode fullscreen, tinggal dipanggil fungsi Flip pada FrontBuffer
dan otomatis gambar akan muncul ke layar.
Pada mode windowed, proses ini digantikan proses blit biasa. Yang khusus
adalah rectangle atau area target yang akan digambar tidak bisa diarahkan
ke 0,0, karena DirectDraw sebenarnya selalu meletakkan 0,0 di ujung kiri
atas monitor, maka apabila window kita di tengah-tengah maka kondisi ini
tidak benar. Maka fungsi di atas mengambil area display dari aplikasi
kita (GetClientRect) dan kemudian meminta sistem mentranslasikannya ke
koordinat absolut layar untuk pojok kiri atas dan kanan bawah (ClientToScreen).
Setelah siap maka dilakukan GetBltStatus() yang melakukan pengecekan berulang-ulang
untuk memastikan blitter sedang tidak sibuk, dan pada saat yang tepat
baru melakukan penggambaran. Apabila tidak melakukan proses ini maka seringkali
terjadi stall (seperti hang) karena melakukan blit pada saat yang salah,
sehingga fungsi Blt() akan terus menunggu.
10.
Akhir
Tutorial ini memberikan dasar untuk menyiapkan DirectDraw dan menampilkan
gambar. Diharap bisa menjadi dasar untuk membuat aplikasi yang lebih jauh
menggunakan DirectDraw, termausk animasi dengan dasar-dasar yang sudah
ada dari tutorial ini.
back
to Gamedev-id Tutorial
©2002, [email protected].