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].