이 문서에서는 Windows 환경에서의 DPI 렌더링 과정을 상세하게 설명합니다. 다음과 같은 구조로 내용을 전달하겠습니다: "프로세스 흐름 + 개념 분석 + WinForms 예제 코드".
1. 핵심 개념 이해
===============
Windows 환경에서는 세 가지 종류의 좌표가 존재합니다:
물리 픽셀 (Physical Pixel)
논리 픽셀 (Logical Pixel)
장치 독립 픽셀 (DIP, Device Independent Pixel)
기본 표준은 다음과 같습니다:
96 DPI = 100% 확대
1 DIP = 1 논리 픽셀 = 1/96 인치
화면 확대율이 150%일 경우:
DPI = 144
1 DIP = 1.5 물리 픽셀
2. DPI 렌더링 프로세스
===============
시나리오 설정
컴퓨터 A:
- 해상도: 1920×1080
- 확대율: 100%
- DPI: 96
컴퓨터 B:
- 해상도: 3840×2160
- 확대율: 150%
- DPI: 144
DPI 렌더링 흐름도 (전체 경로)
================
┌───────────────────────────────────────┐
│ 1️⃣ 애플리케이션 시작 │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 2️⃣ Windows는 DPI 인식 수준을 조회 │
│ - Unaware │
│ - System Aware │
│ - PerMonitor │
│ - PerMonitorV2 │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 3️⃣ 현재 모니터의 DPI를 가져옴 │
│ 예: │
│ 100% → 96 DPI │
│ 150% → 144 DPI │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 4️⃣ UI 배치 단계 │
│ │
│ 사용하는 레이아웃에 따라: │
│ - 픽셀 기반 → 96 기준 계산 │
│ - 논리 기반 → DPI 기준 계산 │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 5️⃣ GDI / Direct2D / WPF 렌더링 │
│ │
│ 논리 좌표를 물리 픽셀로 변환 │
│ │
│ 공식: │
│ physical = logical × (DPI / 96) │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 6️⃣ Windows는 가상화 스케일링 필요 여부 확인 │
│ │
│ DPI Unaware인 경우: │
│ → 전체 창을 bitmap으로 확대 │
│ │
│ DPI Aware인 경우: │
│ → 확대 없이 실제 렌더링 │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 7️⃣ 최종 출력: 그래픽 카드 │
└───────────────────────────────────────┘
3. 가장 중요한 분기점: DPI 인식 수준
=================
이 부분은 인터페이스가 왜곡되는지 결정하는 핵심 요소입니다.
DPI Unaware (기존 WinForms 기본값)
프로세스:
프로그램은 96 DPI 기준으로 계산
↓
Windows는 전체 창을 캡처하고
↓
전체적으로 150% 확대
↓
출력
결과:
- 흐릿한 이미지
- 글씨가 불명확
- 레이아웃 오류
- 잘림 현상
이것이 문제의 원인입니다.
System DPI Aware
프로그램 시작 시 주 모니터의 DPI를 읽음
↓
해당 DPI로 렌더링
↓
다른 모니터로 이동 시 재계산하지 않음
문제:
- 멀티 모니터 환경에서 오류 발생
PerMonitorV2 (권장)
창이 새 모니터로 이동
↓
WM_DPICHANGED 메시지 수신
↓
레이아웃 재설정
↓
재렌더링
이것이 현대적인 올바른 방법입니다.
4. WinForms의 내부 렌더링 경로
==================
Control
↓
GDI+
↓
USER32
↓
DWM (Desktop Window Manager)
↓
GPU
DPI Unaware인 경우:
DWM이 비트맵 스케일링을 수행합니다.
5. WinForms에서 올바른 DPI 설정 방법
======================
Program.cs
using System;
using System.Windows.Forms;
internal static class Program
{
[STAThread]
static void Main()
{
// PerMonitorV2 DPI 지원 활성화
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
Form 생성자
public MainForm()
{
InitializeComponent();
// DPI를 기준으로 스케일링
this.AutoScaleMode = AutoScaleMode.Dpi;
}
6. 실제 시스템 메시지 흐름 (PerMonitorV2)
========================
창을 4K 모니터로 드래그할 때:
WM_DPICHANGED
│
├─ 새로운 DPI 값
├─ 제안된 새 창 크기
│
▼
WinForms에서 Layout 트리거
│
▼
Measure 다시 실행
│
▼
Arrange 다시 실행
│
▼
Paint 다시 실행