개발일지/소프트 렌더러

소프트웨어 렌더러 만들기 - 9 (Resize 대응)

hwi.middle 2024. 7. 17. 00:33

여기까지의 작업내용: https://github.com/hwi-middle/HimchanSoftwareRenderer/tree/dcd6f06b4da884d4ea3b921220752ecbfb601979

 

GitHub - hwi-middle/HimchanSoftwareRenderer: C++로 구현한 소프트웨어 렌더러입니다.

C++로 구현한 소프트웨어 렌더러입니다. Contribute to hwi-middle/HimchanSoftwareRenderer development by creating an account on GitHub.

github.com

WIN32_LEAN_AND_MEAN

#define WIN32_LEAN_AND_MEAN

CK 렌더러의 프리컴파일 헤더를 보다보니 이런 부분이 있어서 MSDN을 찾아보았다.

암호화, DDE, RPC, 셸 및 Windows 소켓과 같은 API를 제외하는 WIN32_LEAN_AND_MEAN 정의합니다.

 

그러니까, 암호화나 소켓같은 부분을 제외해주는 매크로라는 것이다. 이를 통해 빌드 시간의 단축을 기대할 수 있는 것이다. 이름이 되게... 직관적이다. Lean & Mean이라니...

#ifndef WIN32_LEAN_AND_MEAN
#include <cderr.h>
#include <dde.h>
#include <ddeml.h>
#include <dlgs.h>
#ifndef _MAC
#include <lzexpand.h>
#include <mmsystem.h>
#include <nb30.h>
#include <rpc.h>
#endif
#include <shellapi.h>
#ifndef _MAC
#include <winperf.h>
#include <winsock.h>
#endif
#ifndef NOCRYPT
#include <wincrypt.h>
#include <winefs.h>
#include <winscard.h>
#endif

#ifndef NOGDI
#ifndef _MAC
#include <winspool.h>
#ifdef INC_OLE1
#include <ole.h>
#else
#include <ole2.h>
#endif /* !INC_OLE1 */
#endif /* !MAC */
#include <commdlg.h>
#endif /* !NOGDI */
#endif /* WIN32_LEAN_AND_MEAN */

 

실제로 Windows.h를 직접 살펴보면 이렇게 선언되어있다. 저 많은 뭉치가 WIN32_LEAN_AND_MEAN 매크로의 #ifndef에 묶여있는거라서 WIN32_LEAN_AND_MEAN가 선언되면 다 생략된다.

 

다만 나의 렌더러가 딱히 빌드 시간을 단축할만한 프로젝트도 아니긴 하다. 하지만... 이런게 또 굉장히 고수같아 보이고... 달리 프로젝트에 side-effect가 있을만한 것도 아니라서 나도 WIN32_LEAN_AND_MEAN라는 매크로를 선언했다 ㅎㅎ

 

Resize 함수 구현

하핫, Resize 대응법은 알고 있다!  WM_SIZE 메시지를 통해 구현할 수 있으니까.

그러면 Application의 Resize 함수부터 구현하자.

void Application::Resize(uint32 InWidth, uint32 InHeight)
{
	Width = InWidth;
	Height = InHeight;
	Renderer->Resize(Width, Height);
}

그런 다음 Renderer의 Resize 함수도 구현해야한다.

void WinRenderer::Resize(uint32 InWidth, uint32 InHeight)
{
	Release();
	Initialize(InWidth, InHeight);
}

일단 DC 같은거 다 Release 해주고 다시 Initialize를 해준다.

이제 Application의 Resize를 호출해주는 것으로 내부에 저장하고 있던 Width와 Height 값을 수정하고 새로운 화면에 맞게 그릴 수 있다.

 

WM_SIZE 메시지 처리

이어서, WM_SIZE 메시지 처리를 진행해보자.

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
	switch (iMessage)
	{
	case WM_SIZE:
	{
		uint32 Width = LOWORD(lParam);
		uint32 Height = HIWORD(lParam);
        // ...
        // 여기서 Resize 어떻게 하지

 

어라, 여기는 WndProc이고 Application의 인스턴스는 WinMain에 있어서 접근할 수가 없다!

CK렌더러를 보니 std::function으로 일종의 콜백을 만들어서 대응하고 있어서 나도 좀 따라해봤다.

std::function<void(uint32 Width, uint32 Height)> g_OnResize;

g_라는 prefix가 있는걸 보면 알 수 있겠지만, global scope에 std::function을 하나 만들었다.

g_OnResize = std::bind(&Application::Resize, &appliction, std::placeholders::_1, std::placeholders::_2);

그런다음 std::bind를 통해 바인딩 해주었다. CK렌더러에서는 람다캡처를 통해서 함수를 호출하고 있었는데, 나는 std::function에 대해 찾아보다가 알게된 std::bind를 사용해보았다.

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    static bool bIsSizeMove = false;

    switch (iMessage)
    {
    case WM_SIZE:
    {
        uint32 Width = LOWORD(lParam);
        uint32 Height = HIWORD(lParam);
        if (g_OnResize)
        {
            g_OnResize(Width, Height);
        }
    }
        //...
    }
    // ...
}

그럼 이렇게 작성할 수 있다.

Resize 중의 렌더링 처리

그런데 문제가 있다. 창을 Resize 하는 중에는 이렇게 화면이 허옇게 뜬다는 것.

std::function<void(void)> g_Tick;

// ...
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
	// ...
	g_Tick = std::bind(&Application::Tick, &appliction);
    // ...
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
	switch (iMessage)
	{
	case WM_SIZE:
	{
		uint32 Width = LOWORD(lParam);
		uint32 Height = HIWORD(lParam);
		if (g_OnResize)
		{
			g_OnResize(Width, Height);
		}

		if (g_Tick)
		{
			g_Tick();
		}
		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return (DefWindowProc(hWnd, iMessage, wParam, lParam));
}

 

그래서 WM_SIZE 메시지를 처리하는 동안에는 WndProc에서 Tick을 호출하도록 해주었다.

그런데 이렇게 하면 Resize 상태에서 마우스를 클릭한 채 창 크기를 조절하지 않고 가만히 있으면 Tick이 호출되지 않는 문제가 있다.

 

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
	switch (iMessage)
	{
	case WM_SIZE:
	{
		uint32 Width = LOWORD(lParam);
		uint32 Height = HIWORD(lParam);
		if (g_OnResize)
		{
			g_OnResize(Width, Height);
		}

		return 0;
	}

	case WM_ENTERSIZEMOVE:
		SetTimer(hWnd, 1, USER_TIMER_MINIMUM, NULL);
		return 0;

	case WM_EXITSIZEMOVE:
		KillTimer(hWnd, 1);
		return 0;

	case WM_TIMER:
		g_Tick();
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return (DefWindowProc(hWnd, iMessage, wParam, lParam));
}

그래서 최종적으로는 이렇게 구현했다. Resize 시작 시 Timer를 등록하고, 종료 시 Timer를 제거한다. WM_TIMER 메시지에서 타이머 핸들을 확인하지 않는 부분은 조만간 수정하겠다.

 

여기서 또 문제는, 이렇게 했더니 Resize 시 깜빡임이 심해졌다. 마침 성능 문제도 있었겠다, 최적화를 진행해보도록 하겠다. 일단 Delta Time을 구현하고 나서 성능 최적화를 진행할 예정이다. 자세한 문제와 해결 과정은 해당 게시물에서 다루도록 하겠다.

 

https://youtu.be/I3T1Tzm0B98?si=QDfDDdrvJ5Bpc_sC

 

그러면 영상을 첨부하고 마무리. 아직 갈 길이 멀다. 키 입력도 구현해야하고...

 

썸네일용 이미지...