그래픽 관련 공부를 해야 할 계기가 생겼고, 이것도 하나의 기회라고 생각해서 블로그에 정리하면서 자세히 공부해보려고 합니다.
예전에 AR 쪽 할 때 Metal을 본 것 같은데 기억이 안 나서 아예 처음부터 다시 정리해볼게요!
Metal을 이해하려면 먼저 "왜 Metal이 필요했는가?"를 알아야 합니다. 그러려면 컴퓨터가 그래픽을 어떻게 처리하는지부터 차근차근 알아봐야겠죠?
컴퓨터 아키텍처의 기초
CPU: 컴퓨터의 두뇌
- ALU (산술논리장치): 수학적 연산과 논리 연산을 수행
- 제어장치: 명령어를 해석하고 실행 순서를 결정
- 레지스터: 고속으로 임시 데이터를 저장
- 캐시메모리: 자주 사용되는 데이터와 명령어를 저장
- CPU는 순차적 처리에 최적화되어 있어서 복잡한 논리나 분기문을 잘 처리하지만, 같은 연산을 반복해야 하는 작업에는 비효율적입니다.
GPU: 원래 그래픽 처리를 위해 설계되었으나, 현재는 병렬 컴퓨팅의 핵심 요소로 확장
- 병렬처리 능력: 동일한 연산을 수많은 데이터에 동시 적용
- 그래픽 렌더링: 3D 모델링, 텍스처 매핑, 쉐이딩 처리
- 범용컴퓨팅: 기계학습, 과학적 계산, 암호화 등
IPhone에서 적용
현시점 가장 최신폰인 iPhone 16 pro버전에서는 A18 Pro칩을 사용하고 있다.
CPU 구성
- 2개의 성능코어(P-Core): 무거운 작업(복잡한 계산, 게임)을 빠르게 처리
- 4개의 효율코어(E-Core): 일상적인 작업(이메일, 메시지)을 배터리 효율적으로 처리
GPU 구성
6개의 코어가 SIMD 방식으로 작동하며, 각 코어에는 수백~수천 개의 작은 처리 유닛들이 병렬로 작업합니다.
iOS에서는 모든 UI 요소가 GPU를 통해 렌더링되고, 더 나아가 이미지 처리(필터, 조정, 변환), 기계학습, AR까지 모두 GPU를 활용합니다.
SISD vs SIMD: 처리 방식의 차이
위에서 GPU관련 공부하면 SIMD가 나온다. 이는 하나의 명령어로 여러개의 데이터를 한번에 처리하는 병렬 방법이다
SISD (Single Instruction, Single Data)
- 작동 방식: 하나의 명령어가 하나의 데이터에 순차적으로 적용
- 장점: 설계가 단순하고, 제어 흐름이 명확하며, 예측 가능성이 높음
- 단점: 병렬 처리가 불가능해서 대규모 데이터 처리 시 비효율적
- 주요 사용처: 전통적인 CPU 코어, 순차적 처리가 필요한 작업
SIMD (Single Instruction, Multiple Data)
- 작동 방식: 하나의 명령어가 여러 데이터 요소에 동시 적용
- 장점: 높은 처리량, 향상된 전력 효율성, 그래픽 작업에 최적화
- 단점: 조건부 실행 시 성능 저하, 복잡한 프로그래밍 모델
- 주요 사용처: GPU, 벡터 프로세서, 그래픽 렌더링, 신호 처리
- 픽셀마다 동일한 셰이딩 알고리즘을 적용하는데, 이 계산들이 독립적으로 수행 가능하고 수백만 개를 동시에 처리해야 하기 때문에 GPU는 SIMD 방식을 사용하는 거죠!
OpenGL에서 Metal로의 변화
OpenGL과 OpenGL ES의 한계
OpenGL은 플랫폼 독립적인 API로, 다양한 운영체제나 하드웨어에 상관없이 GPU를 활용한 그래픽 렌더링을 가능하게 하는 도구였습니다.
구성요소:
- 정점(Vertex): 3D 공간의 점
- 선: 정점 간의 연결
- 다각형(Polygon): 삼각형 등으로 복잡한 표면 형성
- 셰이더(Shader): 정점 셰이더와 프래그먼트 셰이더로 그래픽의 동작과 외관을 정의
OpenGL ES는 모바일 및 임베디드 시스템용 경량 버전이었지만, 여전히 높은 추상화 구조 때문에 하드웨어와의 직접 통신에서 오버헤드가 발생했습니다.
특히 Apple A 시리즈 칩과 같은 고성능 GPU의 잠재력을 완전히 활용하지 못했고, 고사양 게임이나 복잡한 그래픽 작업에서 성능 병목 현상이 나타났어요
Metal의 등장
이런 문제들을 해결하기 위해 Apple이 2014년에 Metal을 출시했습니다. 벌써 10년이 넘었네요!
Metal의 핵심 목표:
- CPU-GPU 통신 오버헤드 최소화: 하드웨어에 더 직접적으로 접근
- 렌더링 속도 향상: 기존 OpenGL ES 대비 평균 50% 이상 성능 개선
- 배터리 효율성 개선
Metal의 주요 장점:
- 낮은 오버헤드: 하드웨어에 직접 접근하여 CPU-GPU 간 통신 최적화
- 통합 API: OpenGL의 그래픽과 OpenCL의 계산을 하나의 API로 결합
- 렌더링 지연시간 감소: OpenGL ES 대비 평균 50% 이상 감소
Metal 핵심 구조
1. MTLDevice (GPU 추상화)
↓
2. MTLCommandQueue (명령 대기열)
↓
3. MTLCommandBuffer (명령 묶음)
↓
4. MTLCommandEncoder (명령 인코딩)
1. MTLDevice
시스템의 GPU를 나타내며, OS가 앱에 제공합니다.
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("Metal을 지원하지 않는 기기입니다")
}
2. MTLCommandQueue
GPU로 보낼 명령들을 관리하는 대기열입니다.
guard let commandQueue = device.makeCommandQueue() else {
fatalError("CommandQueue 생성 실패")
}
3. MTLCommandBuffer
GPU로 전송될 명령들의 묶음입니다. 하나의 프레임 렌더링에 필요한 모든 명령을 담습니다.
guard let commandBuffer = commandQueue.makeCommandBuffer() else {
fatalError("CommandBuffer 생성 실패")
}
4. MTLCommandEncoder
실제 명령을 해당 명령 버퍼에 인코딩하는 역할을 합니다.
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
셰이더: GPU에서 실행되는 작은 프로그램들
셰이더는 GPU에서 실행되는 작은 프로그램입니다.
우리가 Xcode에 작성한 코드가 CPU에서 실행되는 것과 달리, 셰이더는 GPU에서 실행되며 그래픽 데이터 처리에 특화되어 있어요.
셰이더의 주요 역할
- 위치 계산: 3D 객체의 각 점이 화면의 어디에 위치할지 계산
- 색상 결정: 각 픽셀의 색상을 계산 (조명, 재질, 텍스처 등을 고려)
- 특수 효과 적용: 그림자, 반사, 블러, 왜곡 등 다양한 시각 효과 구현
셰이더의 종류 3가지
1. 버텍스 셰이더 (Vertex Shader)
3D 모델의 각 정점 위치를 계산합니다.
vertex VertexOutput vertexShader(
const device Vertex* vertices [[buffer(0)]],
uint vertexID [[vertex_id]]
) {
// 3D 좌표계의 정점을 화면 좌표계로 변환하는 코드
}
- 입력: 모델의 원본 정점 좌표, 변환 행렬 등
- 출력: 화면 좌표계로 변환된 정점 위치 및 추가 데이터
2. 프래그먼트 셰이더 (Fragment Shader)
: 각 픽셀의 최종 색상을 결정
fragment float4 fragmentShader(
VertexOutput in [[stage_in]],
texture2d<float> texture [[texture(0)]]
) {
// 픽셀 색상을 계산하는 코드
}
- 입력: 정점 간 보간된 데이터, 텍스처, 조명 정보 등
- 출력: 픽셀의 RGBA 색상 값
3. 컴퓨트 셰이더 (Compute Shader)
: 일반적인 병렬 계산 수행
이미지 처리, 물리 시뮬레이션, 인공지능 연산 등
kernel void computeShader(
texture2d<float, access::read> inputTexture [[texture(0)]],
texture2d<float, access::write> outputTexture [[texture(1)]],
uint2 position [[thread_position_in_grid]]
) {
// 병렬 계산을 수행하는 코드
}
Metal에서는 MSL(Metal Shading Language)이라는 C++ 기반 언어로 셰이더를 작성합니다.
// 버텍스 셰이더 - 정점 위치 계산
vertex VertexOutput basicVertex(
const device Vertex* vertices [[buffer(0)]],
uint vertexID [[vertex_id]]
) {
VertexOutput output;
// 정점 위치를 그대로 사용
output.position = float4(vertices[vertexID].position, 1.0);
// 텍스처 좌표 전달
output.texCoord = vertices[vertexID].texCoord;
return output;
}
// 프래그먼트 셰이더 - 색상 변경
fragment float4 tintFragment(
VertexOutput in [[stage_in]],
texture2d<float> colorTexture [[texture(0)]],
constant float4& tintColor [[buffer(0)]]
) {
constexpr sampler textureSampler(filter::linear);
// 텍스처에서 원본 색상 가져오기
float4 originalColor = colorTexture.sample(textureSampler, in.texCoord);
// 원본 색상과 틴트 색상 혼합
return originalColor * tintColor;
}
우리는 이 셰이더를 통해 블러, 필터 효과를 완성할껏이다. 인스타그램 처럼 그건 아마 또 다음글에 이어질꺼 같다...후 길어진다. 어렵다. 아직 갈길이 멀다.
'면접준비' 카테고리의 다른 글
근본으로돌아가자(7)-String,Array으로 시작해서 Sequence까지 (0) | 2025.04.04 |
---|---|
Metal(2)-셰이더 코드 작성까지 (0) | 2025.04.02 |
Autolayout 모든 것: 사이클부터 제약조건까지 (0) | 2025.03.26 |
Apple의 보안 (0) | 2025.03.15 |
근본으로 돌아가자(6) Image (3) | 2025.03.05 |