Win32APIでの画像のダブルバッファが分からない

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
sujiniku
記事: 39
登録日時: 5年前

Win32APIでの画像のダブルバッファが分からない

#1

投稿記事 by sujiniku » 5年前

私は現在、RPGゲームの初歩的なゲームエンジンもどきの作成をしようとしており、
Win32APIを用いたソフト中で、主人公がマップ中を移動した際のゲームマップ表示のためにダブルバッファをしようとしております。

現在、下記のプログラムまでは書けていますが、このままでは画面が、ちらついてしまいます。
なお、下記コード中のhCDCは、裏画面のつもりです。また、マップチップ1マスあたりのサイズは32ピクセルです。マップサイズが現在7×10なので、それに相当する配列を用意しており、下記コードのFor文で、マップ読み込みをしています。

コード:

 
static  HDC hCDC;
hCDC = CreateCompatibleDC(hdc);

static HDC     hMdc;
HBITMAP     hbmp;

//BMP画像をファイルから読み込む
hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);

hMdc = CreateCompatibleDC(hdc);
SelectObject(hCDC, hbmp);

for (int x = 0; x <= 9; ++x)
{
	for (int y = 0; y <= 6; ++y)
	{
		switch (maptable[y][x])
		{
		case (0):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;

		case (1):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_wall.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;
		}

		SelectObject(hMdc, hbmp);
		BitBlt(hdc, 225 + x * 32, 140 + y * 32, 32, 32, hMdc, 0, 0, SRCCOPY);
	}
}


上述のコードでは、マップ表示は出来るのですが、主人公の移動のたびに、画面が、ちらついてしまいます。

それで、「ちらつきを無くしたい」と思ったので、BitBltの第一引数をhdcではなく、裏画面のつもりの別ハンドル hCDCを第一引数にして、

コード:

 
BitBlt(hCDC, 225 + x * 32, 140 + y * 32, 32, 32, hMdc, 0, 0, SRCCOPY);


として、さらにfor文のブロック外に、

コード:

SelectObject(hdc, hCDC);
BitBlt(hdc,0, 0, 502, 602, hCDC, 0, 0, SRCCOPY);
と裏画面hCDCから本画面hdcに画像を一括転記するつもりで書きましたが、画面左上にマップチップが1個だけしか表示されず、
意図した表示になりません。

最終的にコードは、下記のようになっています。

コード:

static  HDC hCDC;
hCDC = CreateCompatibleDC(hdc);

static HDC     hMdc;
HBITMAP     hbmp;

//BMP画像をファイルから読み込む
hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);

hMdc = CreateCompatibleDC(hdc);
SelectObject(hCDC, hbmp);

for (int x = 0; x <= 9; ++x)
{
	for (int y = 0; y <= 6; ++y)
	{
		switch (maptable[y][x])
		{
		case (0):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;

		case (1):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_wall.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;
		}

		SelectObject(hMdc, hbmp);
		BitBlt(hCDC, 225 + x * 32, 140 + y * 32, 32, 32, hMdc, 0, 0, SRCCOPY);
	}
}

SelectObject(hdc, hCDC);
BitBlt(hdc,0, 0, 502, 602, hCDC, 0, 0, SRCCOPY);
対策がわからず、困っています。
私は何かBitBltやダブルバッファについて勘違いをしているのでしょうか?

アバター
usao
記事: 1887
登録日時: 11年前

Re: Win32APIでの画像のダブルバッファが分からない

#2

投稿記事 by usao » 5年前

hCDCにSelectObjectで指定されている画像は mapchip_grass.bmp 由来で,サイズがきっと32x32.
32x32しかないものをhdcに転送したならば,チップ1個(32x32サイズ)だけ表示されるのでは.

(本題ではないですが,ループ内で読み込んでいる画像が激しくリークしていそうな予感)

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32APIでの画像のダブルバッファが分からない

#3

投稿記事 by sujiniku » 5年前

コード:

//SelectObject(hdc, hCDC);
のように、2個目のbitbltの前のSelectOutをコメントアウトしてみましたが、問題事例と同様に、マップ1マスぶんしか表示されません。また、hCDCの代わりにhbmpを引数にしてみましたが、同様にマップ1マスぶんしか表示されません。

ダブルバッファを試みてないコードの場合での、For文ブロック内にある1つめのSelectObjectおよび1つめのbitbltまでの場合では

コード:

SelectObject(hMdc, hbmp);
というふうにhbmpと関連づけても最終的にマップ全マスが表示されるのに、
一方で、なぜ2つめのSelectOutを加えた際に同じhbmpを引数にしてもマップ1マスしか表示されないのか、その仕組みが、私に理解できません。
この現象の仕組みが分かれば、解決に近づけるかもしれないと思います。
また、そもそもSelectOutの仕組みについて、私は何か勘違いをしてるかもしれません。

メモリリークの件は把握してるので、この問題の解決後に、リーク対策に取り掛かる予定です。

アバター
usao
記事: 1887
登録日時: 11年前

Re: Win32APIでの画像のダブルバッファが分からない

#4

投稿記事 by usao » 5年前

「バックバッファ(裏画面)に描画して,その内容をフロントバッファに(1回のBitBltで)転送する」をしたいのですよね.
であれば,
バックバッファの画像領域サイズはフロントバッファ側のサイズと同じサイズで用意する必要があると思うのですが.

バックバッファ用hCDCの作り立ての時点:
> hCDC = CreateCompatibleDC(hdc);
では,hCDCで操作できる画像領域というのは1x1のモノクロ画像になっているので,これでは用途に適さないから,
(1)フロントバッファ側と同じサイズのBitmapを作成して
(2)hCDCにSelectObjectでそのBitmapを指定する
必要があるかと.((2)の際,元々の1x1画像へのハンドルが返されるので,そいつの後始末も考慮すべき)

CreateCompatibleDC()にどのくらい時間を要するのかは不明だけど,
「DCを長期間保有し続けるのはよろしくない」という話もあったような気がするので,

(a)プログラム開始直後あたり(ウィンドウリサイズ可能なアプリケーションならリサイズ毎か)でバックバッファ用のBitmapを用意する
(b)表示更新処理の度に,hCDCを作成→バックバッファ用bmpをhCDCにSelectObject→hCDCを介してバックバッファ用Bitmapに描画→フロントバッファに転送→元の1x1画像をhCDCにSelectObject→hCDCを破棄,という流れの処理を行う

という形にすればどうでしょう.

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32APIでの画像のダブルバッファが分からない

#5

投稿記事 by sujiniku » 5年前

下記のコードで、画像全体が表示されるようになりました。
ただし、このコードでは、まだ、画面がちらつきます。また、メモリリークもあるままです。

コード:

static HDC hCDC;
hCDC = CreateCompatibleDC(hdc);

static HDC hMdc;
HBITMAP hbmp;
		
// マップ背景用サイズのビットマップ画像を読み込む
hbmp = (HBITMAP)LoadImage(NULL, TEXT("mudai.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);

SelectObject(hCDC, hbmp);

//マップチップ画像をファイルから読み込む
hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);

hMdc = CreateCompatibleDC(hdc);
	
for (int x = 0; x <= 9; ++x)
				{
	for (int y = 0; y <= 6; ++y)
	{
		switch (maptable[y][x])
		{
		case (0):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;

		case (1):
							hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_wall.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;
		}
		SelectObject(hMdc, hbmp);
		BitBlt(hCDC, 225 + x * 32, 140 + y * 32, 32, 32, hMdc, 0, 0, SRCCOPY);
	}
}

// 主人公のBMP画像をファイルから読み込む
hbmp = (HBITMAP)LoadImage(NULL, TEXT("hero_dot.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);
				
SelectObject(hMdc, hbmp); // これを消すと、主人公ドットが表示されない。

BitBlt(hCDC, 320 + (chx - saisyo_x) * 32, 270 + (chy - saisyo_y) * (32), 170, 180, hMdc, 0, 0, SRCCOPY);
				
BitBlt(hdc, 0, 0, 600, 400, hCDC, 0, 0, SRCCOPY); // 裏画面から本画面に転送のつもり。

DeleteDC(hCDC);
DeleteDC(hMdc);
DeleteObject(hbmp);
私の理解の確認をさせてください。
「SelectObjectでは、画像サイズを採取するので、
適したサイズのビットマップを準備する必要がある」という理解で宜しいでしょうか?

ちらつきの原因は、もしかしたら、このコード以外の他の部分も関係しているかもしれないので、
数日間しばらくデバッグしてから、数日後に再投稿するかもしれません。

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32APIでの画像のダブルバッファが分からない

#6

投稿記事 by sujiniku » 5年前

前の投稿で書き忘れましたが、(前投稿で最初のSelectObjectでhCDCと関連づけさせた)画像 "mudai.bmp" はサイズ640×480の白色画像です。無題.bmp を拡大してファイル名を書き換えただけのものです。ウィンドウサイズを640×480の対応を予定しているので、とりあえず、この白色画像だけ作りました。

naohiro19 [ログアウト]

Re: Win32APIでの画像のダブルバッファが分からない

#7

投稿記事 by naohiro19 [ログアウト] » 5年前

2003年ごろに作ってあったダブルバッファを使ったプログラムを以下においておきます。

コード:

#ifndef _IMAGE_H_
#define _IMAGE_H_

//インクルード
#include <windows.h>

typedef struct tagImage{
         int      w;    //横幅
     int      h;    //縦幅
     HDC      hdc;  //デバイスコンテキスト
     HBITMAP  hbmp; //ビットマップハンドル
}
IMAGE,*LPIMAGE;

//プロトタイプ宣言
int CreateImage(IMAGE* image,HWND hwnd,int w, int h); 
int CreateImageFromFile(IMAGE* image, HWND hwnd,char* name);
int BltImage(IMAGE* dst, int x,int y,IMAGE* scr, RECT scr_r, DWORD flag);
int DeleteImage(IMAGE* image);

#endif

//インクルード
#include <windows.h>
#include "Image.h"

//------------------------------------------------------------
//  CreateImage()
//  Memo:画像の作成する
//------------------------------------------------------------
int CreateImage(
                     IMAGE* image;  //画像を作成するためのポインタ
           HWND   hwnd;   //ウィンドウハンドル
                     int w, int h)  //作成する横幅と縦幅
{
           HDC   hdc;
           hdc = GetDC( hwnd );
             
           //仮想画面とDCの作成
      image->hbmp = CreateCompatibleBitmap( hdc, w, h );
           image->hdc  = CreateCompatibleDC( hdc );

           if( image->hbmp == NULL || image->hdc == NULL ){
                   return 0;
           }
           //関連付け
      SelectObject( image->hdc, image->hbmp );

           image->w = w;
           image->h = h;

           //画面クリア(白)
           BitBlt( image->hdc, 0, 0, w, h, NULL, 0, 0, WHITENESS );
     
           return 0;
}

//------------------------------------------------------------
//       CreateImageFromFile()
//       Memo:画像のロード
//------------------------------------------------------------
int CreateImageFromFile(
                         IMAGE*     imag,  //画像格納用変数ポインタ
             HWND       hwnd,  //ウィンドウハンドル
             char*      name)  //読み込むファイル名
{
          HDC           hdc;
          HINSTANCE     hInst;
          BITMAP        bmp;

          //ファイルからロード
      hInst = (HINSTANCE) GetWindowLong( hwnd,GWL_HINSTANCE );
          image->hbmp = (HBITMAP) LoadImage(hInst, name, IMAGE_BITMAP, 0, 0, 
                                   LR_LOADFROMFILE | LR_CREATEDIBSECTION);

          //DCの作成
      hdc = GetDC( hwnd );
          image->hbmp = CreateCompatible( hwnd );
          ReleaseDC( hwnd, hdc );

          //関連付け
      SelectObject(image->hdc, image->hbmp );

          if(image->hbmp == NULL || image->hdc == NULL ){
                   return 0;
          }

         //ビットマップ情報の取得
     GetObject( image->hbmp sizeof( BITMAP ), &bmp );
         image->w = bmp.bm.Width;
         image->h = bmp.bmHeight;

         return 0;
}

//----------------------------------------------------------
//       BltImage()
//       Memo:描画する処理
//----------------------------------------------------------

int BltImage(
                  IMAGE*     dst,     //転送先イメージ
                  int        x,       //転送先X座標
                  int        y,       //転送先Y座標
                  IMAGE*     scr,     //転送元イメージ
                  RECT       scr_r,   //転送元領域
                  DWORD      flag)    //転送フラグ
{
         //横幅と縦幅の取得
         int w = scr_r.right  - scr_r.left;
         int h = scr_r.bottom - scr_r.top;

         BitBlt( dst->hdc, x, y, w, h, scr->hdc,scr_r.left, scr_r.top, flag );
         
         return 1;
}

//----------------------------------------------------------
//       DeleteImage()
//       Memo:画像を破棄する
//---------------------------------------------------------
int DeleteImage( IMAGE* image ){
            DeleteDC(image-> hdc );
            DeleteObject( image->hbmp );

            image->hdc  = NULL;
            image->hbmp = NULL;

            return 1;
}


アバター
usao
記事: 1887
登録日時: 11年前

Re: Win32APIでの画像のダブルバッファが分からない

#8

投稿記事 by usao » 5年前

> SelectObjectでは、画像サイズを採取するので

「採取する」の意味合いがよくわかりませんが……

SelectObject(hCDC, hbmp);
は,
hCDCを介して(今後)操作する画像領域はhbmpですよ,っていう指定をしている.

以降,Ellipse( hCDC, ...) とかやれば,hbmp(が指すBitmap)に楕円が描かれるし,
BitBlt( hDstDC, ..., hCDC, ... ) とすれば,hbmpの内容を,hDstDC(を介して操作する対象Bitmap)に転送する.

アバター
usao
記事: 1887
登録日時: 11年前

Re: Win32APIでの画像のダブルバッファが分からない

#9

投稿記事 by usao » 5年前

ものすごーく雑なイメージをコードっぽく書くとこんな.

コード:

//デバイスコンテキストの雑なイメージ
struct DC
{
  HBITMAP m_hBmp;
  HPEN m_hPen;
  ...
};

//DCへBitmapを指定する関数
HBITMAP SelectObject( DC *hDC, HBITMAP hBmp )
{
  HBITMAP hOldBMP = hDC->m_hBmp;
  hDC->m_hBmp = hBmp;
  return hOldBMP;  //直前に指定されていたBitmapのハンドルを返す
}

//描画関数
void Ellipe( DC *hDC, ... )
{
  hDC->m_hBmp に対して,hDC->m_hPen を用いて楕円を描画する
}

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32APIでの画像のダブルバッファが分からない

#10

投稿記事 by sujiniku » 5年前

上記の私のコードには無い部分ですが、再描画命令 InvalidateRect の第三引数の意味を、今やっと理解しました。

コード:

InvalidateRect(hWnd, NULL, FALSE);
上記のように第三引数をFALSEにして修正し、ちらつきは無くなりました。

でも、まだメモリリークが無くならず、私は困っています。もし、よろしければ、助けてください。メモリリークの原因を指摘してくだされば、さいわいです。

コード:

// マップの定義。1は壁。0は通行可能な草原。
int maptable[7][10] = {
{ 1,1,1,1,1,1,1,1,1,1 }, //0 x
{ 1,0,0,0,0,0,0,0,0,1 }, //1
{ 1,0,0,0,0,0,0,0,0,1 }, //2
{ 1,0,0,0,0,0,0,0,0,1 }, //3
{ 1,0,0,0,0,0,0,0,0,1 }, //4
{ 1,0,0,0,0,0,0,0,0,1 }, //5
{ 1,1,1,1,1,1,1,1,1,1 }  //6
};


static HDC hCDC;
hCDC = CreateCompatibleDC(hdc);

static HDC hMdc;
HBITMAP hbmp;

// マップ背景用サイズのビットマップ画像を読み込む
hbmp = (HBITMAP)LoadImage(NULL, TEXT("mudai.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);

SelectObject(hCDC, hbmp);

//マップチップ画像をファイルから読み込む
hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);

hMdc = CreateCompatibleDC(hdc);

for (int x = 0; x <= 9; ++x)
{
	for (int y = 0; y <= 6; ++y)
	{
		switch (maptable[y][x])
		{
		case (0):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;

		case (1):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_wall.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;
		}
		SelectObject(hMdc, hbmp);
		BitBlt(hCDC, 225 + x * 32, 140 + y * 32, 32, 32, hMdc, 0, 0, SRCCOPY);
	}
}

// 主人公のBMP画像をファイルから読み込む
hbmp = (HBITMAP)LoadImage(NULL, TEXT("hero_dot.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);

SelectObject(hMdc, hbmp); // これを消すと、主人公ドットが表示されない。

BitBlt(hCDC, 320 + (chx - saisyo_x) * 32, 270 + (chy - saisyo_y) * (32), 170, 180, hMdc, 0, 0, SRCCOPY);
BitBlt(hdc, 0, 0, 600, 400, hCDC, 0, 0, SRCCOPY); // 裏画面から本画面に転送のつもり。

hCDC = NULL;
hMdc = NULL;
hbmp = NULL;

DeleteDC(hCDC);
DeleteDC(hMdc);
DeleteObject(hbmp);
なお、数日前の最初の投稿では省きましたが、実はマップ定義を、hCDCの宣言などの前で行っています。ひょっとしたら、これがリークの原因と関係あるかもと思ったので、今回の投稿では省かず、記載しました。

いちおう、コードブロックの最後でhCDCもhMDCもhbmpも Delete しているつもりなのですが、私は何か削除し忘れているのでしょうか?

アバター
usao
記事: 1887
登録日時: 11年前

Re: Win32APIでの画像のダブルバッファが分からない

#11

投稿記事 by usao » 5年前

2重ループ内だけ見ても70回LoadImageが走るわけですが,こういうことをするなら
その分(70回)のDeleteObjectが必要になりますよね.
そこらへんのところは大丈夫な感じですか?

かずま

Re: Win32APIでの画像のダブルバッファが分からない

#12

投稿記事 by かずま » 5年前

70回も LoadImage を実行するから時間がかかってちらつくのではありませんか?

次のようにすればよいのでは?

コード:

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
	static HBITMAP hbmGrass, hbmWall, hbmHero;
	static int maptable[7][10] = {
		{ 1,1,1,1,1,1,1,1,1,1 }, //0
		{ 1,0,0,0,0,0,0,0,0,1 }, //1
		{ 1,0,0,0,0,0,0,0,0,1 }, //2
		{ 1,0,0,0,0,0,0,0,0,1 }, //3
		{ 1,0,0,0,0,0,0,0,0,1 }, //4
		{ 1,0,0,0,0,0,0,0,0,1 }, //5
		{ 1,1,1,1,1,1,1,1,1,1 }  //6
	};
	int saisyo_x = 0, saisyo_y = 0, chx = 0, chy = 0;
	PAINTSTRUCT ps;
	HDC hdc, hmdc;

    switch (msg) {
    case WM_CREATE:
		hbmGrass = (HBITMAP)LoadImage(NULL, "mapchip_grass.bmp",
				IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
		hbmWall = (HBITMAP)LoadImage(NULL, "mapchip_wall.bmp",
				IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
		hbmHero = (HBITMAP)LoadImage(NULL, "hero_dot.bmp",
				IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
		break;
    case WM_DESTROY:
		DeleteObject(hbmGrass);
		DeleteObject(hbmWall);
		DeleteObject(hbmHero);
		PostQuitMessage(0);
		break;
    case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		hmdc = CreateCompatibleDC(hdc);
		for (int x = 0; x < 10; ++x)
			for (int y = 0; y < 7; ++y) {
				SelectObject(hmdc, maptable[y][x] ? hbmWall : hbmGrass);
				BitBlt(hdc, 225 + x * 32, 140 + y * 32, 32, 32, hmdc, 0, 0, SRCCOPY);
			}
		SelectObject(hmdc, hbmHero);
		BitBlt(hdc, 320 + (chx - saisyo_x) * 32,
			270 + (chy - saisyo_y) * 32, 170, 180, hmdc, 0, 0, SRCCOPY);
		DeleteDC(hmdc);
		EndPaint(hWnd, &ps);
		break;
    default:
		return DefWindowProc(hWnd, msg, wp, lp);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE p, LPSTR s, int nCmdShow)
{
    WNDCLASS wc = { CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, NULL,
		LoadCursor(NULL, IDC_ARROW), (HBRUSH)GetStockObject(WHITE_BRUSH),
		NULL, "hoge" };
    if (!RegisterClass(&wc)) return 0;
    HWND hWnd = CreateWindow("hoge", "RPG", WS_OVERLAPPEDWINDOW,
			80, 60, 640, 480, NULL, NULL, hInst, NULL);
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32APIでの画像のダブルバッファが分からない

#13

投稿記事 by sujiniku » 5年前

とりあえず、For文ブロック内の最後にDeleteObjectを追加してみt、hbmpを消すつもりのコードを入れました。

コード:

for (int x = 0; x <= 9; ++x)
{
	for (int y = 0; y <= 6; ++y)
	{
		switch (maptable[y][x])
		{
		case (0):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;

		case (1):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_wall.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;
		}
		SelectObject(hMdc, hbmp);
		BitBlt(hCDC, 225 + x * 32, 140 + y * 32, 32, 32, hMdc, 0, 0, SRCCOPY);
		DeleteObject(hbmp);
		// DeleteDC(hMdc); // これを入れると、マップが表示されない。
	}
}
でも、まだメモリを消費していきます。

かずまさん、ご指摘、ありがとうございます。
私の現在のコードでのマップ描画の方式はインベーダーゲームみたいにマップ固定で主人公の表示位置が移動する方式ですが、
最終目標的には私はドラクエやFFみたいにマップ画面での主人公の表示位置を中央に固定して、マップのほうをスライドさせる方式にしたいので、
マップ読み込みを移動のたびに毎回する必要があるかもしれないなあ・・・と思ってます。(巨大なダンジョン内では、画面表示される部分だけを、移動のたびに毎回、読み込むことになるかもしれません。)

また、現状では、まだゲームの完成度がそこまで高くなく、ファイル分割をしておらず、さらにオープニング画面や会話画面など他にもさまざまなコードが1つのファイル内にある段階です。
また、私は恥ずかしながら、まだファイル分割の方法を知りません。モノリシック的に、1枚のソースファイルに、ゲーム内全部の処理を書いている状態です。

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32APIでの画像のダブルバッファが分からない

#14

投稿記事 by sujiniku » 5年前

コード:

hbmp = CreateCompatibleBitmap(hdc, 640, 480);
を使うことにより、背景画像を読み込むのを止めてみたら、
つまり

コード:

hbmp = (HBITMAP)LoadImage(NULL, TEXT("mudai.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);
を削除してみたら、リークが大幅に減りました。

こうして、偶然、メモリリークは解決しましたが、理由・仕組みが、まだ私に理解できません。

そのほか、各所に

コード:

DeleteObject(hbmp);
を追加してみたら、とりあえず、メモリリークは発見できなくなりました。100歩以上、キャラクターが歩いても、1メガも使用メモリが増えなくなりました。

最終的に、下記のようなコードになりました。

コード:

static HDC hbackDC; // 裏画面用のハンドル
hbackDC = CreateCompatibleDC(hdc);

static HDC hMdc; // 中間作業用ハンドル
HBITMAP hbmp;

// マップサイズの読み込む
hbmp = CreateCompatibleBitmap(hdc, 640, 480);
SelectObject(hbackDC, hbmp); // hbackDCにマップサイズを読み込ませている

DeleteObject(hbmp);

// マップチップ画像をファイルから読み込む
hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);

hMdc = CreateCompatibleDC(hdc);
DeleteObject(hbmp);

for (int x = 0; x <= 9; ++x)
{
	for (int y = 0; y <= 6; ++y)
	{
		switch (maptable[y][x])
		{
		case (0):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;

		case (1):
			hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_wall.bmp"), IMAGE_BITMAP, 0, 0,
				LR_LOADFROMFILE | LR_CREATEDIBSECTION);
			break;
		}
		SelectObject(hMdc, hbmp);
		BitBlt(hbackDC, 225 + x * 32, 140 + y * 32, 32, 32, hMdc, 0, 0, SRCCOPY);

		DeleteObject(hbmp);
		// DeleteDC(hMdc); // これを入れると、マップが表示されない。
	}
}

// 主人公のBMP画像をファイルから読み込む
hbmp = (HBITMAP)LoadImage(NULL, TEXT("hero_dot.bmp"), IMAGE_BITMAP, 0, 0,
	LR_LOADFROMFILE | LR_CREATEDIBSECTION);
SelectObject(hMdc, hbmp); // これを消すと、主人公ドットが表示されない。
DeleteObject(hbmp);

BitBlt(hbackDC, 320 + (chx - saisyo_x) * 32, 270 + (chy - saisyo_y) * (32), 170, 180, hMdc, 0, 0, SRCCOPY);
BitBlt(hdc, 0, 0, 600, 400, hbackDC, 0, 0, SRCCOPY); // 裏画面から本画面に転送のつもり。

DeleteObject(hbmp);
DeleteDC(hbackDC);
DeleteDC(hMdc);
これでメモリリークの解決する理由・仕組みが、まだ私に理解できませんので、どなたか私に解説してください。

私の思う印象ですが、なんだか、各種のハンドルがSelectObjectを行うたびに、まるでローカル変数的にオブジェクトをハンドル内にコピーしているかのような印象を受けています。そのため、上記のコードでは、SelectObjectを行った直後に、なるべく即座にDeleteObjectで削除しています。

なお、hCDCをhbackDCに改名しました。

アバター
usao
記事: 1887
登録日時: 11年前

Re: Win32APIでの画像のダブルバッファが分からない

#15

投稿記事 by usao » 5年前

DeleteObjectを無闇に入れるのではなく,やるべきタイミングで必要なだけやりましょう.

DCに選択されているオブジェクトは削除できないはずなので,
DeleteObjectで削除するには,先に,DCに別のオブジェクトをSelectObjectして,削除したいオブジェクトの選択解除を行う必要があったと思います.
そのために,一般にはSelectObjectの戻り値を覚えておいて,最後にそれを選択し直す,ということを行います.
(この辺はGDIを使っているコード例を検索とかすれば見つかるはず)

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32APIでの画像のダブルバッファが分からない

#16

投稿記事 by sujiniku » 5年前

ちらつきではないのですが、先日の相談の後、メニュー画面(道具、装備、などのコマンド画面)を作ってみまして、
メニュー画面からキャンセルボタン(Xキー)を押してマップ画面に戻ると、
なぜか、マップの上2段くらいがバグってるというバグに遭遇しました。
マップの上2段に、マップの最下段が横1マスずれて表示されるというバグです。

読者の方は、もし上述のコードで、バグ発生原因が分かりましたら、ぜひ報告してくださると、助かります。

かずま

Re: Win32APIでの画像のダブルバッファが分からない

#17

投稿記事 by かずま » 5年前

メニュー画面を作って、キャンセルボタンを押したら
表示がおかしくなるのなら、そのコードにバグがあるんでしょう。

そのコードを示さずに、バグ発生原因を分って報告してほしいとは、
理不尽な要求だと思いませんか?

#12 で私が示したように、ビルドして実行したら、バグが再現できる
最低限のソースを見せてもらえませんか?

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32APIでの画像のダブルバッファが分からない

#18

投稿記事 by sujiniku » 5年前

最低限のコードではないですが、
GitHubに製作中のゲームのコードがあります。

https://github.com/sujiniku/sujinikuRpg ... pWin32.cpp

最低限のコードを特定するのは、時間が掛かりますので、とりあえずコレで簡便してください。

153行目あたりに、マップ描画用の関数

コード:

 void Draw_map(HDC hdc) 
があります。(行数は掲示板への投稿日のものなので、後日、変更するかもしれません。)

153行目あたりで

コード:

void Draw_map(HDC hdc) {
	static HDC hbackDC; // 裏画面用のハンドル
	hbackDC = CreateCompatibleDC(hdc);

	static HDC hMdc; // 中間作業用ハンドル
	HBITMAP hbmp;

	// マップサイズの読み込む
	hbmp = CreateCompatibleBitmap(hdc, 640+100, 480+100);
	SelectObject(hbackDC, hbmp); // hbackDCにマップサイズを読み込ませている

	DeleteObject(hbmp);

	// マップチップ画像をファイルから読み込む
	hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
		LR_LOADFROMFILE | LR_CREATEDIBSECTION);

	hMdc = CreateCompatibleDC(hdc);
	DeleteObject(hbmp);

	int x_map = 0;
	int y_map = 0;

	for (x_map = 0; x_map <= 9; ++x_map)
	{
		for (y_map = 0; y_map <= 6; ++y_map)
		{
			switch (maptable[y_map][x_map])
			{
			case (0):
				hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_grass.bmp"), IMAGE_BITMAP, 0, 0,
					LR_LOADFROMFILE | LR_CREATEDIBSECTION);
				break;

			case (1):
				hbmp = (HBITMAP)LoadImage(NULL, TEXT("mapchip_wall.bmp"), IMAGE_BITMAP, 0, 0,
					LR_LOADFROMFILE | LR_CREATEDIBSECTION);
				break;
			}
			SelectObject(hMdc, hbmp);
			BitBlt(hbackDC, 225 + x_map * 32, 140 + y_map * 32, 32, 32, hMdc, 0, 0, SRCCOPY);

			DeleteObject(hbmp);
			// DeleteDC(hMdc); // これを入れると、マップが表示されない。
		}
	}

	// 主人公のBMP画像をファイルから読み込む
	
	if(hero1_direction == upward ){
		hbmp = (HBITMAP)LoadImage(NULL, TEXT("hero_dot_up.bmp"), IMAGE_BITMAP, 0, 0,
			LR_LOADFROMFILE | LR_CREATEDIBSECTION);
	}
	
	if (hero1_direction == rightward) {
		hbmp = (HBITMAP)LoadImage(NULL, TEXT("hero_dot_right.bmp"), IMAGE_BITMAP, 0, 0,
			LR_LOADFROMFILE | LR_CREATEDIBSECTION);
	}

	if (hero1_direction == downward) {
		hbmp = (HBITMAP)LoadImage(NULL, TEXT("hero_dot_down.bmp"), IMAGE_BITMAP, 0, 0,
			LR_LOADFROMFILE | LR_CREATEDIBSECTION);
	}

	if (hero1_direction == leftward) {
		hbmp = (HBITMAP)LoadImage(NULL, TEXT("hero_dot_left.bmp"), IMAGE_BITMAP, 0, 0,
			LR_LOADFROMFILE | LR_CREATEDIBSECTION);
	}

	SelectObject(hMdc, hbmp); // これを消すと、主人公ドットが表示されない。
	DeleteObject(hbmp);

	BitBlt(hbackDC, 320 + (chara_x - start_x) * 32, 270 + (chara_y - start_y) * (32), 170, 180, hMdc, 0, 0, SRCCOPY);


	// マップ上キャラのBMP画像をファイルから読み込む

	if (enemy1_alive == 1) {
		hbmp = (HBITMAP)LoadImage(NULL, TEXT("enemy_dot.bmp"), IMAGE_BITMAP, 0, 0,
			LR_LOADFROMFILE | LR_CREATEDIBSECTION);

		SelectObject(hMdc, hbmp); // これを消すと、ドットが表示されない。
		DeleteObject(hbmp);

		BitBlt(hbackDC, 320 + (enemy1_position_x - start_x) * 32, 270 + (enemy1_position_y - start_y) * (32), 170, 180, hMdc, 0, 0, SRCCOPY);
	}


	if (enemy2_alive == 1) {
		hbmp = (HBITMAP)LoadImage(NULL, TEXT("enemy_dot.bmp"), IMAGE_BITMAP, 0, 0,
			LR_LOADFROMFILE | LR_CREATEDIBSECTION);

		SelectObject(hMdc, hbmp); // これを消すと、ドットが表示されない。
		DeleteObject(hbmp);

		BitBlt(hbackDC, 320 + (enemy2_position_x - start_x) * 32, 270 + (enemy2_position_y - start_y) * (32), 170, 180, hMdc, 0, 0, SRCCOPY);
	}


	BitBlt(hdc, 0, 0, 700, 500, hbackDC, 0, 0, SRCCOPY); // 裏画面から本画面に転送のつもり。

	DeleteObject(hbmp);
	DeleteDC(hbackDC);
	DeleteDC(hMdc);

}
と定義しています。(8月上旬ごろの前投稿のあと、モンスターの配置とか、主人公の上下右左の向きのアルゴリズムとかを作ってたので、新たにドット表示が加わっています。)

この関数を696行目あたりで呼び出しています。

696行目あたりで

コード:

if (mode_scene == MODE_MAP ) {
				
				Draw_map(hdc);
}

なお、GitHubのコードは1500行くらいあって膨大になりますので、当掲示板には全部のコードは貼らないでおきます。

かずま

Re: Win32APIでの画像のダブルバッファが分からない

#19

投稿記事 by かずま » 5年前

sujiniku さんが書きました:
5年前
最低限のコードではないですが、
GitHubに製作中のゲームのコードがあります。

https://github.com/sujiniku/sujinikuRpg ... pWin32.cpp

コード:

    77: TCHAR p[MAX_LENGTH] = TEXT("はじめから");

   128: int maptable[7][10] = {
   129:     { 1,1,0,1,1,1,1,1,1,1 }, //0 x
   130:     { 1,0,0,0,0,0,0,0,0,1 }, //1
   131:     { 1,0,0,0,0,0,0,0,0,1 }, //2
   132:     { 1,0,0,0,0,0,0,0,0,1 }, //3
   133:     { 1,0,0,0,0,0,0,0,0,1 }, //4
   134:     { 1,0,0,0,0,0,0,0,0,1 }, //5
   135:     { 1,1,1,1,1,1,0,0,1,1 }  //6
   136: };

   745:             _stprintf_s(p, 300,TEXT("%d"), your_money);
   929:             _stprintf_s(p, 300, TEXT("%d"), hero_hp);
   932:             _stprintf_s(p, 300, TEXT("/ %d"), hero_hp_max);
   945:             _stprintf_s(p, 300, TEXT("%d"), monster_hp);
   987:             _stprintf_s(p, 300, TEXT("%d  EXP"), monster_def_list[encount_monters_id -1].mon_exp);
   994:             _stprintf_s(p, 300, TEXT("%d  G"), monster_def_list[encount_monters_id -1].mon_gold);
  1013:             _stprintf_s(p, 300, TEXT("%d"), hero_hp);
  1016:             _stprintf_s(p, 300, TEXT("/ %d"), hero_hp_max);
MAX_LENGTH は 256 なので、配列 p の要素数は 256 です。
_stprintf_s(p, 300 で、p の要素数は 300 だと嘘をついています。
Visual Studio でデバッグビルドすると、配列 p に格納した文字列の
あとの p[299] までを 0xfefe で埋めます。配列 p は、p[255]までしか
ないので、maptable の先頭の数十バイトが破壊されます。

256しかないのに、300あるよと嘘をつくのはやめましょう。

変数 p をグローバル変数にするのもいかがなものでしょうか。
他にも変なところがたくさんあります。
例えば、rand() の呼び出しの前に常に srand() を呼び出すというのは
変です。

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32APIでの画像のダブルバッファが分からない

#20

投稿記事 by sujiniku » 5年前

配列 p は、p[255]までしか
ないので、maptable の先頭の数十バイトが破壊されます。
私の理解を確認したいと思います。
_stprintf_sの誤った使い方によって引き起こされた一種のオーバフローによって、マップ用データが破壊されたという理解で、よろしいでしょうか?

マップ描画の関数では配列pは用いてないはずなので、つまり、配列pのオーバーフローが、配列maptableのメモリを上書きしてしまったって事でしょうか?

なお _stprintf_s の第二引数をMAX_LENGTHに統一したところ、バグは発生しなくなりました。
(つまり

コード:

_stprintf_s(p, MAX_LENGTH, TEXT("%d"), hero_hp);
のようにした。)
今回のマップ表示のバグ自体は、解決しました。

返信

“C言語何でも質問掲示板” へ戻る