Window System

열수철·2025년 3월 10일

게임 엔진 개발에서 핵심적인 부분인 '윈도우 시스템'에 대해 알아보려고 합니다. 특히 멀티 플랫폼과 다중 창을 지원하는 윈도우 시스템의 설계와 구현 방법을 간단히 이해해보려 합니다.

윈도우 시스템이란?

윈도우 시스템은 게임이나 그래픽 애플리케이션이 화면에 표시되는 창을 관리하는 시스템입니다. 단순히 창을 띄우는 것 이상으로, 다음과 같은 중요한 역할을 담당합니다:

  • 창 생성 및 관리
  • 사용자 입력 수신(마우스, 키보드 등)
  • 렌더링을 위한 표면(Surface) 제공
  • 다양한 플랫폼(Windows, macOS, Linux 등) 지원

GLFW vs SDL2

많은 게임 엔진이 GLFW 라이브러리를 사용하지만, 다중 창과 멀티 플랫폼 지원에는 몇 가지 한계가 있습니다. 이런 한계를 극복하기 위해 SDL2(Simple DirectMedia Layer 2)를 사용한 접근법을 살펴보겠습니다.

GLFW의 한계:

  • 다중 창 관리가 복잡함
  • 모바일 플랫폼 지원이 제한적
  • 추가 기능(오디오, 컨트롤러 등) 통합이 어려움

SDL2의 장점:

  • 더 넓은 플랫폼 지원(PC, 모바일, 웹 등)
  • 다중 창 관리를 위한 직관적인 API
  • 입력, 오디오, 컨트롤러 등의 추가 기능 내장
  • Vulkan, OpenGL, DirectX와 쉽게 통합

윈도우 시스템 아키텍처: 2단계 접근법

우리의 윈도우 시스템은 두 개의 핵심 클래스로 구성됩니다:

1. Window 클래스 (개별 창 관리)

각 창의 생명주기와 상태를 관리합니다:

class Window {
public:
    Window(const WindowCreateInfo& createInfo);
    ~Window();
    
    // 창 상태 관리
    void setTitle(const std::string& title);
    void setSize(int width, int height);
    void setFullscreen(bool fullscreen);
    
    // Vulkan 통합
    VkSurfaceKHR createSurface(VkInstance instance);
    
    // 이벤트 처리
    void setEventCallback(EventCallback callback);
    bool processEvent(const SDL_Event& event);
    
    // ... 기타 멤버 함수 및 변수 ...
};

Window 클래스는 다음과 같은 책임을 갖습니다:

  • 창 생성 및 속성 관리 (크기, 제목, 전체화면 등)
  • 창 상태 추적 (최소화, 포커스 등)
  • Vulkan 렌더링 표면 제공
  • Window별 이벤트 처리

2. WindowSystem 클래스 (전체 시스템 관리)

모든 창을 관리하고 시스템 수준의 작업을 처리합니다:

class WindowSystem {
public:
    WindowSystem();
    ~WindowSystem();
    
    // 시스템 초기화/정리
    void initialize();
    void shutdown();
    
    // 창 관리
    Window::WindowId createWindow(const WindowCreateInfo& createInfo);
    void destroyWindow(Window::WindowId windowId);
    Window* getWindow(Window::WindowId windowId);
    
    // 이벤트 처리
    void pollEvents();
    bool shouldClose() const;
    
    // 플랫폼 정보
    static std::string getPlatformName();
    static int getDisplayCount();
    
    // ... 기타 멤버 함수 및 변수 ...
};

WindowSystem 클래스는 다음을 담당합니다:

  • SDL 초기화 및 종료
  • 여러 창의 생성과 파괴 관리
  • 중앙 이벤트 루프 처리
  • 플랫폼 정보 제공
  • 다중 모니터 지원

핵심 개념 자세히 살펴보기

1. 창 생성 과정

창을 생성할 때는 다양한 옵션을 설정할 수 있습니다:

// 창 설정 구조체
struct WindowCreateInfo {
    std::string title = "Vulkan Window";
    int width = 800;
    int height = 600;
    bool resizable = false;
    bool fullscreen = false;
    bool borderless = false;
    bool vsync = true;
    int displayIndex = 0;  // 다중 모니터 지원
};

// 창 생성 예시
WindowCreateInfo info;
info.title = "게임 메인 창";
info.width = 1024;
info.height = 768;
info.resizable = true;

Window::WindowId windowId = windowSystem.createWindow(info);

이 과정에서 SDL2는 내부적으로 다음 작업을 수행합니다:
1. 설정된 플래그(resizable, borderless 등)에 맞는 SDL 창 생성
2. 창 ID 할당 (다중 창 식별용)
3. 초기 창 속성 설정

2. 이벤트 처리 흐름

이벤트 처리는 세 단계로 이루어집니다:

  1. 중앙 이벤트 폴링: WindowSystem이 모든 SDL 이벤트를 수집

    void WindowSystem::pollEvents() {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            // 이벤트 처리
        }
    }
  2. 이벤트 분배: 각 이벤트를 관련된 창으로 전달

    // 창 이벤트를 해당 창에 전달
    if (event.type == SDL_WINDOWEVENT) {
        for (auto& pair : m_windows) {
            if (pair.second->processEvent(event)) {
                break;  // 이벤트가 처리됨
            }
        }
    }
  3. 창별 이벤트 처리: 각 창은 자신에게 해당하는 이벤트 처리

    bool Window::processEvent(const SDL_Event& event) {
        // 이 창에 관련된 이벤트인지 확인
        if (event.type == SDL_WINDOWEVENT && 
            event.window.windowID == SDL_GetWindowID(m_window)) {
            
            // 이벤트 처리 및 콜백 호출
            if (m_eventCallback) {
                m_eventCallback(event);
            }
            return true;
        }
        return false;
    }

3. Vulkan과의 통합

각 창은 Vulkan 렌더링을 위한 표면(Surface)을 생성할 수 있습니다:

VkSurfaceKHR Window::createSurface(VkInstance instance) {
    if (m_surface != VK_NULL_HANDLE) {
        return m_surface;  // 이미 생성됨
    }
    
    // SDL을 사용한 Vulkan 표면 생성
    if (SDL_Vulkan_CreateSurface(m_window, instance, &m_surface) != SDL_TRUE) {
        throw std::runtime_error("Failed to create Vulkan surface");
    }
    
    return m_surface;
}

이 표면은 후속 Vulkan 초기화 단계에서 사용됩니다:
1. Vulkan 물리장치 선택
2. 스왑체인 생성
3. 렌더 패스와 프레임버퍼 설정

4. 다중 플랫폼 지원

코드는 컴파일 타임에 플랫폼을 감지하고 적절한 설정을 적용합니다:

std::string WindowSystem::getPlatformName() {
#if defined(_WIN32) || defined(_WIN64)
    return "Windows";
#elif defined(__APPLE__)
    return "macOS";
#elif defined(__linux__)
    return "Linux";
#elif defined(__ANDROID__)
    return "Android";
#elif defined(__EMSCRIPTEN__)
    return "Web";
#else
    return "Unknown";
#endif
}

각 플랫폼별 특성도 자동으로 처리됩니다:

  • Windows: 창 핸들링, 입력 처리
  • macOS: Retina 디스플레이, 메뉴바 통합
  • Linux: X11/Wayland 윈도우 시스템 지원
  • 모바일: 터치 입력, 화면 회전 처리

실제 사용 예시

전체 시스템을 사용하는 기본 코드를 살펴보겠습니다:

int main() {
    // 윈도우 시스템 초기화
    WindowSystem windowSystem;
    windowSystem.initialize();
    
    // 주 창 생성
    WindowCreateInfo mainWindowInfo;
    mainWindowInfo.title = "메인 창";
    mainWindowInfo.width = 1024;
    mainWindowInfo.height = 768;
    Window::WindowId mainWindowId = windowSystem.createWindow(mainWindowInfo);
    
    // 보조 창 생성
    WindowCreateInfo secondaryWindowInfo;
    secondaryWindowInfo.title = "보조 창";
    secondaryWindowInfo.width = 800;
    secondaryWindowInfo.height = 600;
    Window::WindowId secondaryWindowId = windowSystem.createWindow(secondaryWindowInfo);
    
    // Vulkan 초기화 (실제 코드에서 구현)
    VkInstance vulkanInstance = createVulkanInstance();
    
    // 각 창에 대한 Vulkan 표면 생성
    Window* mainWindow = windowSystem.getWindow(mainWindowId);
    VkSurfaceKHR mainSurface = mainWindow->createSurface(vulkanInstance);
    
    // 메인 루프
    while (!windowSystem.shouldClose()) {
        // 이벤트 처리
        windowSystem.pollEvents();
        
        // 렌더링 코드
        // ...
    }
    
    // 정리
    windowSystem.shutdown();
    return 0;
}

확장성과 유연성

이 설계는 다음과 같은 기능으로 쉽게 확장할 수 있습니다:

  1. 입력 시스템 통합

    • 키보드, 마우스, 게임패드 입력 처리
    • 터치스크린 지원
  2. 하이 DPI 지원

    • Retina 디스플레이와 같은 고해상도 화면 지원
    • DPI 기반 스케일링
  3. 전체화면 관리

    • 독점 전체화면
    • 경계 없는 창모드 전체화면
  4. 다중 그래픽 API 지원

    • Vulkan 외에 OpenGL, DirectX도 지원 가능

결론

멀티 플랫폼, 다중 창을 지원하는 윈도우 시스템은 현대 게임 엔진의 핵심 요소입니다. SDL2를 활용한 설계는 다음과 같은 장점을 제공합니다:

  • 유연성: 다양한 플랫폼과 화면 구성 지원
  • 확장성: 기능 추가가 용이한 모듈식 설계
  • 견고성: 플랫폼별 차이점을 추상화하여 일관된 경험 제공
  • 효율성: 최소한의 오버헤드로 필요한 기능 제공

이러한 윈도우 시스템을 구축하면 게임 엔진의 다른 부분(렌더링, 물리, 오디오 등)을 더 쉽게 통합할 수 있으며, 개발자와 사용자 모두에게 더 나은 경험을 제공할 수 있습니다.

다음 게임 엔진 시리즈에서는 Vulkan Instance의 개념과 구현 방법에 대해 알아보겠습니다. 제안이 있으시면 언제든지 댓글로 남겨주세요!

profile
그래픽스, 수학, 물리, 게임 만세

0개의 댓글