ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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의 장점

    1. 언어 통합: 순수 C++ 코드로 Metal 사용 가능
    2. 간소화된 코드: 일관된 C++ 스타일로 작성 가능
    3. 개선된 메모리 관리: NS::SharedPtr와 같은 도구로 참조 카운팅 자동화
    4. 성능 보존: 최소한의 오버헤드로 Metal API 접근
    5. 기존 코드베이스 호환성: 기존 C++ 프로젝트에 쉽게 통합 가능

    Metal-cpp는 C++에서 Metal을 사용하는 가장 효율적인 방법을 제공하여, 개발자가 두 언어의 장점을 동시에 활용할 수 있게 해줍니다. C++ 개발자라면 Metal-cpp를 통해 Apple 플랫폼에서 최고의 그래픽 성능을 쉽게 구현할 수 있습니다.

    댓글

Designed by Tistory.