-
Metal-Cpp : C++ 개발자를 위한 Apple의 GPU 사용법Apple🍎/Metal 2025. 4. 3. 23:03
Metal 둘러보기
셰이더(shader)는 무엇인가?다음은 애플 디벨로퍼 홈페이지에 있는 Metal 의 소개입니다. 일단 Metal 을 사용하면 GPU 프로그래밍을 가능하게 해준다고는 들었는데, 셰이딩 언어는 또 뭘까요? 사실 GP
people-analysis.tistory.com
Metal 소개: Apple 플랫폼의 그래픽 및 컴퓨팅 프레임워크
Metal은 Apple이 개발한 저수준 그래픽 및 컴퓨팅 API로, iOS, macOS, tvOS 등 Apple 플랫폼에서 GPU의 강력한 성능을 활용할 수 있게 해줍니다. 2014년에 처음 소개된 Metal은 OpenGL보다 더 낮은 오버헤드와 더 높은 성능을 제공하기 위해 설계되었습니다.
Metal의 주요 특징은 다음과 같습니다.
- 그래픽 렌더링 및 일반 컴퓨팅(GPGPU) 모두 지원
- 멀티스레딩 및 멀티 GPU 지원
- 최신 Apple 하드웨어에 최적화
- 낮은 CPU 오버헤드
- 셰이더 언어로 Metal Shading Language(MSL) 사용
C++ 개발자가 Metal을 사용하려는 이유
많은 게임 엔진, 과학 시뮬레이션, 그래픽 애플리케이션이 C++로 작성되어 있습니다. C++는 성능 중심 애플리케이션에 이상적인 언어로, 많은 어플리케이션이 기존 C++ 코드베이스를 가지고 있습니다. 이러한 어플리케이션이 Apple 플랫폼에서 작동할 때 최고의 그래픽 성능을 얻으려면 Metal API를 사용해야 합니다.
하지만 Metal은 원래 Objective-C를 위해 설계되었기 때문에, C++ 코드베이스와 Metal을 통합하는 것은 쉽지 않았습니다. 이것이 Apple이 Metal-cpp를 개발한 이유입니다. Metal-cpp는 C++ 개발자가 익숙한 환경에서 Metal을 활용할 수 있도록 도와줍니다.
Objective-C와 C++ 비교 : .m과 .mm 파일 확장자의 차이
Apple 개발 환경에서 파일 확장자는 컴파일러에게 중요한 정보를 제공합니다. Metal을 C++와 함께 사용할 때 이해해야 할 두 가지 중요한 파일 확장자가 있습니다:
.m 파일 (Objective-C)
.m 확장자는 순수한 Objective-C 소스 코드 파일을 나타냅니다.
- 순수 Objective-C 코드만 포함 가능
- C 코드도 포함 가능 (Objective-C는 C의 상위 집합)
- C++ 코드는 포함할 수 없음
- Cocoa, UIKit, Foundation 등 Apple의 Objective-C 프레임워크를 사용하는 코드에 일반적으로 사용
// Example.m - 순수 Objective-C 파일 #import <Foundation/Foundation.h> @interface MyClass : NSObject @end @implementation MyClass - (void)doSomething { NSLog(@"Hello from Objective-C"); } @end
.mm 파일 (Objective-C++)
.mm 확장자는 "Objective-C++"를 의미하며, 두 언어의 혼합을 허용합니다.
- Objective-C 코드와 C++ 코드를 동일한 파일 내에서 혼합 가능
- Apple의 Objective-C 프레임워크와 C++ 라이브러리를 함께 사용 가능
- 컴파일러가 이 파일을 특별히 처리하여 두 언어의 문법, 메모리 모델, 예외 처리 등의 차이를 조정
- Metal-cpp 이전에는 C++와 Metal을 함께 사용하려면 이 확장자가 필수였음
// Example.mm - Objective-C와 C++을 혼합한 파일 #import <Foundation/Foundation.h> #include <vector> #include <string> // C++ 클래스 정의 class CPPProcessor { public: std::string process(const std::string& input) { return "Processed: " + input; } }; // Objective-C 클래스에서 C++ 사용 @interface MixedClass : NSObject @end @implementation MixedClass { CPPProcessor _processor; // C++ 객체를 Objective-C 클래스 내부에 직접 포함 } - (NSString *)processString:(NSString *)input { // Objective-C 문자열을 C++ std::string으로 변환 std::string cppString([input UTF8String]); // C++ 객체의 메서드 호출 std::string result = _processor.process(cppString); // 결과를 다시 Objective-C NSString으로 변환 return [NSString stringWithUTF8String:result.c_str()]; } @end
C++에서 Metal 사용하는 방법 비교하기
Metal-cpp 이전 방식 (Objective-C++ 혼합)
Metal-cpp가 나오기 전에는 C++ 코드베이스에서 Metal을 사용하기 위해 Objective-C++를 통한 혼합 접근 방식이 필요했습니다.
// MyRenderer.mm 파일 (Objective-C++) #include "MyRenderer.h" // C++ 클래스 정의 class MyComputeRenderer { private: // Objective-C 객체를 C++ 클래스에서 사용하기 위해 포인터로 저장 id<MTLDevice> _device; id<MTLComputePipelineState> _computePipeline; public: MyComputeRenderer(id<MTLDevice> device) { // Objective-C 객체를 저장할 때 참조 카운트 수동 관리 필요 _device = device; [_device retain]; // 수동으로 참조 카운트 증가 // Metal 셰이더 라이브러리 생성 - Objective-C 코드 NSError* error = nil; id<MTLLibrary> library = [_device newLibraryWithSource:@"kernel void add(device float* a [[buffer(0)]]) { a[0] += 1.0f; }" options:nil error:&error]; if (!library) { NSLog(@"Failed to create library: %@", error); return; } // 커널 함수 가져오기 - Objective-C와 C++ 혼합 코드 id<MTLFunction> kernelFunction = [library newFunctionWithName:@"add"]; // 파이프라인 생성 - 다시 Objective-C 코드 _computePipeline = [_device newComputePipelineStateWithFunction:kernelFunction error:&error]; // 사용 완료된 Objective-C 객체 해제 [kernelFunction release]; [library release]; } ~MyComputeRenderer() { // 소멸자에서 Objective-C 객체의 참조 카운트 수동 감소 [_computePipeline release]; [_device release]; } void compute() { // 여기서 계산 명령어 실행... } }; // Objective-C 클래스에서 C++ 클래스 사용하는 구현 @implementation MyRenderer { MyComputeRenderer* _cppRenderer; } - (instancetype)initWithDevice:(id<MTLDevice>)device { self = [super init]; if (self) { _cppRenderer = new MyComputeRenderer(device); } return self; } - (void)dealloc { delete _cppRenderer; } - (void)executeCompute { _cppRenderer->compute(); } @end
이 접근 방식의 문제점
- 두 가지 다른 언어 구문을 혼합해야 함 (-> 와 [] 표기법)
- 객체 수명 주기 관리가 복잡함 (수동 retain/release 호출)
- 두 언어의 타입 시스템 간 변환이 필요함
- .mm 파일만 사용해야 함 (순수 C++ 파일에서는 Metal을 사용할 수 없음)
Metal-cpp 사용 방식
Metal-cpp를 사용하면 순수 C++ 코드로 깔끔하게 작성할 수 있습니다.
// MyRenderer.cpp 파일 (순수 C++) #include "MyRenderer.h" #include "Metal/Metal.hpp" // Metal-cpp 헤더 포함 class MyComputeRenderer { private: // 순수 C++ 타입으로 Metal 객체 사용 NS::SharedPtr<MTL::Device> _device; NS::SharedPtr<MTL::ComputePipelineState> _computePipeline; public: MyComputeRenderer(MTL::Device* device) { // SharedPtr로 자동 메모리 관리 _device = NS::RetainPtr(device); // Metal 셰이더 라이브러리 생성 - C++ 문법 NS::Error* error = nullptr; MTL::Library* library = _device->newLibrary( NS::String::string("kernel void add(device float* a [[buffer(0)]]) { a[0] += 1.0f; }", NS::UTF8StringEncoding), nullptr, &error); if (!library) { NS::String* errorStr = error->localizedDescription(); // 에러 처리 return; } // 커널 함수 가져오기 - C++ 문법 MTL::Function* kernelFunction = library->newFunction( NS::String::string("add", NS::UTF8StringEncoding)); // 파이프라인 생성 - C++ 문법 _computePipeline = NS::SharedPtr(_device->newComputePipelineState(kernelFunction, &error)); // SharedPtr 사용으로 수동 해제 불필요 // 로컬 변수는 자동으로 해제됨 } // 소멸자에서 별도 해제 코드 불필요 - SharedPtr이 자동 처리 void compute() { // 계산 명령어 실행... } }; // MyRenderer.h의 C++ 클래스 구현 MyRenderer::MyRenderer(void* devicePtr) { MTL::Device* device = static_cast<MTL::Device*>(devicePtr); _renderer = new MyComputeRenderer(device); } MyRenderer::~MyRenderer() { delete _renderer; } void MyRenderer::executeCompute() { _renderer->compute(); }
Metal-cpp 접근 방식의 이점
- 순수 C++ 구문만 사용 (일관된 ->연산자)
- NS::SharedPtr와 같은 도구를 통한 간편한 메모리 관리
- C++ 타입 시스템과 자연스럽게 통합
- .cpp 파일 사용 가능 (순수 C++ 환경 유지)
- 기존 C++ 코드베이스와 쉽게 통합
Metal-cpp의 작동 방식
Metal-cpp는 C++와 Objective-C Metal 사이의 가벼운 래퍼 역할을 합니다.
- 헤더 전용 라이브러리: 인라인 함수 호출로 구현되어 오버헤드가 최소화됨
- 1:1 매핑: C++ 호출을 Objective-C API에 직접 매핑
- 100% API 커버리지: Metal API의 모든 기능을 C++에서 사용 가능
- Foundation과 CoreAnimation 래핑: Metal이 의존하는 다른 프레임워크도 지원
- 오픈 소스: Apache 2 라이선스 하에 제공되어 쉽게 수정 및 통합 가능
Metal-cpp는 C를 사용하여 Objective-C 런타임에 직접 호출하므로, Objective-C 컴파일러가 메서드를 실행하는 것과 동일한 메커니즘을 사용합니다. 이로 인해 래퍼가 거의 오버헤드를 추가하지 않습니다.
결론
Metal-cpp는 C++ 개발자가 Apple 플랫폼에서 Metal의 강력한 그래픽 및 컴퓨팅 기능을 활용할 수 있는 방법을 크게 개선했습니다. 이전에는 Objective-C++를 통한 복잡한 언어 혼합이 필요했지만, 이제는 순수 C++ 코드로 Metal API를 직접 사용할 수 있습니다.
Metal-cpp의 장점
- 언어 통합: 순수 C++ 코드로 Metal 사용 가능
- 간소화된 코드: 일관된 C++ 스타일로 작성 가능
- 개선된 메모리 관리: NS::SharedPtr와 같은 도구로 참조 카운팅 자동화
- 성능 보존: 최소한의 오버헤드로 Metal API 접근
- 기존 코드베이스 호환성: 기존 C++ 프로젝트에 쉽게 통합 가능
Metal-cpp는 C++에서 Metal을 사용하는 가장 효율적인 방법을 제공하여, 개발자가 두 언어의 장점을 동시에 활용할 수 있게 해줍니다. C++ 개발자라면 Metal-cpp를 통해 Apple 플랫폼에서 최고의 그래픽 성능을 쉽게 구현할 수 있습니다.
'Apple🍎 > Metal' 카테고리의 다른 글
[Particle Simulator] Compute Shader 사용해 gpu 연산하기 (0) 2025.04.01 [Particle Simulator] cpu 연산 - gpu 랜더링 방법 (0) 2025.03.30 Metal 렌더링 파이프라인 (0) 2025.03.29 CPU와 GPU 비교와 GPGPU 프로그래밍의 이해 (0) 2025.03.27 cellular automata: 간단한 모래 시뮬레이터 만들기 (0) 2025.03.24