^(코딩캣)^ = @"코딩"하는 고양이;
썸네일 이미지
[단막 Windows NT 서버] 딥웹 .onion 익명 사이트 개설(호스팅)하기 #1
단막 Windows NT 서버 딥웹 .onion 익명 사이트 개설(호스팅)하기 (Tor를 사용한 .onion 주소 할당 방법) 일반적인 검색엔진(구글, 네이버, 다음 등)으로 검색되지 않는 웹 사이트들이 존재하는 통신망을 딥웹(Deep Web), 심층 웹, 깊은 웹 등이라 부른다. Tor 네트워크는 딥 웹의 한 종류이며 전용 브라우저인 Tor Browser를 사용하여 접속할 경우 여러 프록시 서버들을 거치며 그 내용 또한 암호화되어 전송되기 때문에 익명으로 인터넷을 이용할 수 있고, warning.or.kr처럼 특정 국가에서 차단해 놓은 사이트들을 접근할 수도 있다. Tor 네트워크에 존재하는 웹 사이트들은 통상적인 도메인이 아닌 .onion으로 끝나는 해쉬hash 형태의 주소를 가지는데, 이번 포스팅에..
Operating System/Windows NT
2019. 2. 26. 21:48

[단막 Windows NT 서버] 딥웹 .onion 익명 사이트 개설(호스팅)하기 #1

Operating System/Windows NT
2019. 2. 26. 21:48

단막 Windows NT 서버


 

딥웹 .onion 익명 사이트 개설(호스팅)하기 (Tor를 사용한 .onion 주소 할당 방법)


일반적인 검색엔진(구글, 네이버, 다음 등)으로 검색되지 않는 웹 사이트들이 존재하는 통신망을 딥웹(Deep Web), 심층 웹, 깊은 웹 등이라 부른다.

Tor 네트워크는 딥 웹의 한 종류이며 전용 브라우저인 Tor Browser를 사용하여 접속할 경우 여러 프록시 서버들을 거치며 그 내용 또한 암호화되어 전송되기 때문에 익명으로 인터넷을 이용할 수 있고, warning.or.kr처럼 특정 국가에서 차단해 놓은 사이트들을 접근할 수도 있다. Tor 네트워크에 존재하는 웹 사이트들은 통상적인 도메인이 아닌 .onion으로 끝나는 해쉬hash 형태의 주소를 가지는데, 이번 포스팅에서는 Windows 상에서 간단하게 웹 서버를 구축하고 .onion 주소를 할당받는 방법을 소개하겠다.

 

1 단계. 웹 서버 구성하기


포트가 충돌할 우려가 있으니 기존 사이트를 운영하던 서버에 Deep Web 사이트를 설정하는 것보다는 Deep Web 사이트만을 위한 서버를 따로 마련하는 것이 권장된다. 그리고 이 항목은 Apache나 IIS 등의 서버를 설치하고 구성한 경우라면 건너뛰어도 된다. 여기에서는 XAMPP(Apache + MySQL + PHP + Perl)을 이용해 서버를 구성해보겠다.

XAMPP 공식 홈 페이지(https://www.apachefriends.org/index.html)에 접속하여 최신 버전의 XAMPP를 다운로드 받는다. Windows XP 사용자는 최신버전이 지원되지 않으므로 1.8.x 이하 버전을 설치하여야 한다.

UAC 때문에 원활히 작동되지 않을 수 있으므로 Program Files 폴더는 피해서 설치할 것을 권장한다.
[Next >] 버튼을 누른다.
구성 요소를 선택한다. 특별히 고칠 것이 없으므로 [Next >] 버튼을 누른다.
설치 경로는 그대로 두고 [Next >] 버튼을 누른다.
[Next >] 버튼을 누른다.
설치가 진행되고 있다. 시간이 다소 걸리므로 완료될 때까지 기다린다.
제어판을 실행해야 하므로 선택 후 [Next >] 버튼을 누른다.
언어 선택하고...

 

Onion 사이트의 페이지는 80번 포트를 통해 Tor 네트워크로 나갈 것이다. 이는 Apache가 내보내는 포트의 기본값과 같으므로 충돌을 방지하기 위해 Apache가 사이트를 내보내는 포트를 다른 번호로 바꿔줘야 하다. 다음과 같이 XAMPP 제어판이 실행되었다면, Apache에서 [Config] 버튼 -> [Apache (httpd.conf)]를 클릭한다.

XAMPP 제어판
[Apache (httpd.conf)]를 클릭
80번 포트에서 8080 포트로 수정
방화벽 예외에 추가

 

다음과 같이 XAMPP 제어판에서 8080번 포트로 웹 사이트가 게시되고 있음을 확인할 수 있다.

8080번 포트로 게시되고 있음

 

xampp\htdocs 폴더에 웹 사이트로 게시되는 파일들을 저장한다. 기본 제공되는 테스트 파일들은 모두 제거한 후 다음과 같은 내용의 html 파일을 작성하여 index.html로 저장한다.

<!doctype html>
<html lang="en">
    <head>
        <title>Hello, Onion!</title>
        <meta charset="utf-8" />
    </head>
    <body>
        <p>Hello, Onion!</p>
        <p>^(코딩캣)^ = @"코딩"하는 <strong>고양이</strong></p>
    </body>
</html>

 

기본 제공된 파일을 제거한다
웹 브라우저로 localhost 접속됨을 확인

 

2 단계. Tor로 Onion 주소 얻어 Deep Web 사이트 개설하기


구 버전의 Tor에서는 Tor Browser라는 Tor 네트워크 브라우저와 Vidalia라는 Tor 네트워크 제어판이 분리되었는데, 현재 나오는 버전의 Tor는 Tor Browser로 일원화되어있다. 따라서 Tor Browser만 종료하지 않고 실행해 두면 항상 Tor 네트워크 접속 상태가 유지될 것이다. 서버의 유동 IP/고정 IP 여부도 염려할 필요가 없다. 외부에서 접속할 때는 IP가 아닌 onion 주소를 사용하며, IP 주소와 onion 주소의 대응은 Tor 네트워크에서 알아서 해 줄 것이다.

먼저 Tor Project 홈 페이지(https://www.torproject.org/)에 접속하여 Tor Browser를 다운로드 받는다.

설치 경로를 지정한다.
설치 완료.

 

Tor Browser가 설치된 경로(\Tor Browser\Browser\TorBrowser\Data\Tor)에 들어가면 torrc라는 0 바이트짜리 파일이 보일 것이다. Tor Browser를 처음으로 실행한 후 이 파일을 다시 찾을 것이다.

torrc가 0바이트이다.

 

설치된 경로(\Tor Browser\Start Tor Browser.lnk)를 실행한다.

[Connect] 버튼을 눌러 기본값으로 Tor 네트워크에 접속한다.
Tor 네트워크에 접속되었다면 다시 닫는다.

 

3 단계. 포트 설정하기


앞서 확인했던 경로(\Tor Browser\Browser\TorBrowser\Data\Tor)를 다시 열면 0 바이트였던 torrc 파일의 크기가 늘어난 것을 확인할 수 있다. 텍스트 편집기로 이를 열어서 내용을 확인한다.

크기가 증가한 torrc 파일을 연다.

 

아래의 두 줄을 새로 추가한다. 첫 번째 줄은 xampp로 게시되는 로컬 경로를 Tor 네트워크에게 알려주는 것이고, 두 번째 줄은 서버의 아이피와 xampp로 게시되는 일반 웹의 포트번호(여기서는 8080)를 딥 웹으로는 80번 포트로 재게시 하는 것이다.

HiddenServiceDir 사이트의로컬경로
HiddenServicePort 80 서버의아이피:서버의포트번호

 

torrc 파일을 수정한다.

 

Tor Browser를 다시 실행한다. 이 때부터는 htdocs의 로컬 경로가 하나의 Deep Web 사이트로서 Tor 네트워크에 내보내지고 있으므로 닫으면 외부에서 접속이 안 된다.

 

4 단계. onion 주소 확인하기


torrc 파일에서 HiddenServiceDir 항목으로 지정한 로컬 경로로 들어가보면 몇 개의 파일이 생성된 것을 볼 수 있다. 이 중 hostname 파일을 열어보면 이 사이트의 onion 주소를 확인할 수 있다.

서버 경로에 생성된 몇 개의 파일
hostname에 기록된 현 서버의 onion 주소

 

Tor Browser를 실행하고 약 1분이 지난 후 이 주소를 접속하면 일반 웹 브라우저에서 http://localhost:8080로 보았던 것과 동일한 웹 페이지를 볼 수 있다. 즉, 외부 사용자도 이 onion 주소를 통해 같은 페이지를 보고 있다는 뜻이다. 이렇게 해서 하나의 Deep Web 사이트 구성이 완료되었다.

 

Tor Browser를 통해 접속한 웹 사이트의 모습

 

마무리


이번 포스팅에서는 Tor Browser와 xampp를 활용하여 딥 웹 사이트를 운영하는 서버를 구축하여 보았다. 다음 포스팅에서는 커스텀 onion 주소를 할당받는 방법에 대해 설명하겠다.

카테고리 “Operating System/Windows NT”
more...
[단막 DirectX 9.0 활용] DirectX 9.0를 WM_PAINT 이벤트에 적용하기
단막 DirectX 9.0 활용 DirectX 9.0를 WM_PAINT 이벤트에 적용하기 WM_PAINT(MFC: OnPaint) 이벤트는 창의 내용을 그릴 때 호출되는 이벤트이다. 이 이벤트에 DirectX 9.0을 적용하여 보겠다. IDirect3DDevice9::TestCooperativeLevel 로스트 상태는, DirectX 객체와 실제 그래픽 어댑터 사이의 연결이 끊어진 상태를 뜻한다. DirectX 객체가 생성된 시점과 이 객체를 이용하여 실제로 그리는 시각 시점의 상대적으로 긴 시간이 있는 경우 이 사이에 여러가지 이유(예: 로그오프, 절전모드 등)로 객체와 장치간 연결이 끊어질 수 있는데 이는 IDirect3DDevice9::TestCooperativeLevel함수로 체크할 수 있다. 만..
API/DirectX
2019. 2. 26. 19:30

[단막 DirectX 9.0 활용] DirectX 9.0를 WM_PAINT 이벤트에 적용하기

API/DirectX
2019. 2. 26. 19:30

단막 DirectX 9.0 활용


 

DirectX 9.0를 WM_PAINT 이벤트에 적용하기


WM_PAINT(MFC: OnPaint) 이벤트는 창의 내용을 그릴 때 호출되는 이벤트이다. 이 이벤트에 DirectX 9.0을 적용하여 보겠다.

 

IDirect3DDevice9::TestCooperativeLevel


로스트 상태는, DirectX 객체와 실제 그래픽 어댑터 사이의 연결이 끊어진 상태를 뜻한다. DirectX 객체가 생성된 시점과 이 객체를 이용하여 실제로 그리는 시각 시점의 상대적으로 긴 시간이 있는 경우 이 사이에 여러가지 이유(예: 로그오프, 절전모드 등)로 객체와 장치간 연결이 끊어질 수 있는데 이는 IDirect3DDevice9::TestCooperativeLevel함수로 체크할 수 있다.

만일 DirectX와 장치간 연결이 정상이 아닐 경우 그 원인에 따라 D3DERR_DRIVERINTERNALERROR, D3DERR_DEVICELOST, D3DERR_DEVICENOTRESET 중 하나를 반환하는데, 이 때는 IDirect3DDevice9::CreateDevice에 사용되었던 D3DPRESENT_PARAMETERS 구조체를 다시 사용하여 IDirect3DDevice9::Reset 함수를 실행하여 장치와 객체의 연결을 리셋한다.

/* C++ Source */
IDirect3DDevice * pDirect3DDevice;
D3DPRESENT_PARAMETERS d3dPresentParameters;
HRESULT hResult;
// ...
hResult = pDirect3DDevice->TestCooperativeLevel();
switch (hResult)
{
case D3DERR_DRIVERINTERNALERROR:
    pDirect3DDevice->Reset(&d3dPresentParameters); // D3DPRESENT_PARAMETERS *
    // Reset 실패 시 각종 조치
    break;
case D3DERR_DEVICELOST:
    // Reset 실패 시 각종 조치
    pDirect3DDevice->Reset(&d3dPresentParameters); // D3DPRESENT_PARAMETERS *
    break;
case D3DERR_DEVICENOTRESET:
    // Reset 실패 시 각종 조치
    pDirect3DDevice->Reset(&d3dPresentParameters); // D3DPRESENT_PARAMETERS *
    break;
}

 

IDirect3DDevice9::Clear

장치의 로스트 상태 확인 및 조치를 완료하였다면 지정된 위치의 버퍼를 특정 색상으로 지운다.

/* C++ Source */
IDirect3DDevice * pDirect3DDevice;
D3DPRESENT_PARAMETERS d3dPresentParameters;
DWORD dwRects;
D3DRECT * pRects;

HRESULT hResult;
// ...
hResult = pDirect3DDevice->Clear
(
    0,                                // DWORD Count : pRects 배열의 원소 수
    NULL,                            // const D3DRECT * pRects
    D3DCLEAR_TARGET,                 //
    D3DCOLOR_XRGB(0x00, 0xFF, 0xFF), // D3DCOLOR Color : 리셋 후 버퍼의 색상
    0.0f,                            // float Z
    0                                // DWORD Stencil
);

if (FAILED(hResult))
{
    // Clear 실패 시 각종 조치
}

 

IDirect3DDevice9::BeginScene, IDirect3DDevice9::EndScene, IDirect3DDevice9::Present


그리기 단위인 '장면scene'을 시작하고, 끝내는 함수이고 IDirect3DDevice9::Present를 통해 버퍼 스왑을 한다.

/* C++ Source */
IDirect3DDevice * pDirect3DDevice;
D3DPRESENT_PARAMETERS d3dPresentParameters;
HRESULT hResult;
// ...
pDirect3DDevice->BeginScene();
if (FAILED(hResult))
{
    // BeginScene 실패 시 각종 조치
}

// 각종 그리기 연산

pDirect3DDevice->EndScene();
if (FAILED(hResult))
{
    // EndScene 실패 시 각종 조치
}

pDirect3DDevice->Present
(
    NULL, // const RECT * pSourceRect
    NULL, // const RECT * pDestRect
    NULL, // HWND hDestWindowOverride
    NULL  // const RGNDATA *pDirtyRegion
);

if (FAILED(hResult))
{
    // Present 실패 시 각종 조치
}

 

카테고리 “API/DirectX”
more...
썸네일 이미지
[단막 DirectX 9.0 활용] DirectX 9.0 객체의 생성과 해제
단막 DirectX 9.0 활용 DirectX 9.0 객체의 생성과 해제 DirectX 9.0을 시작하기 위해 객체의 생성을 하고 창을 닫을 때의 해제는 다음과 같이 한다. DirectX 객체는 COMComponent Object Model으로 제공되므로 C++ 언어를 사용한다. Direct3DCreate9, IDirect3D9::Release DirectX 9.0의 객체 생성은 Direct3DCreate9 함수를 실행한다. 성공하면 LPDIRECT3D9(struct IDirect3D9 *) 형식의 객체를 반환하고, 실패하면 NULL을 반환한다. /* C++ Source */ #pragma comment(lib, "d3d9.lib") // DirectX 9.0 라이브러리를 링크 #include // Wi..
API/DirectX
2019. 2. 12. 20:42

[단막 DirectX 9.0 활용] DirectX 9.0 객체의 생성과 해제

API/DirectX
2019. 2. 12. 20:42

단막 DirectX 9.0 활용


 

DirectX 9.0 객체의 생성과 해제


DirectX 9.0을 시작하기 위해 객체의 생성을 하고 창을 닫을 때의 해제는 다음과 같이 한다. DirectX 객체는 COMComponent Object Model으로 제공되므로 C++ 언어를 사용한다.

 

Direct3DCreate9, IDirect3D9::Release


DirectX 9.0의 객체 생성은 Direct3DCreate9 함수를 실행한다. 성공하면 LPDIRECT3D9(struct IDirect3D9 *) 형식의 객체를 반환하고, 실패하면 NULL을 반환한다.

/* C++ Source */
#pragma comment(lib, "d3d9.lib") // DirectX 9.0 라이브러리를 링크
#include <Windows.h> // Windows API에서 제공되는 기본 자료형들을 불러오기
#include <d3d9.h> // DirectX 9.0 API를 불러오기

LPDIRECT3D9 lpDirect3D = (LPDIRECT3D9)NULL; // DirectX 9.0 객체

/* 창을 생성하거나 그래픽을 리셋하는 단계에서 */

lpDirect3D = Direct3DCreate9(D3D_SDK_VERSION); // DirectX 9.0 버전으로 객체를 생성한다.
if (!lpDirect3D) 
{
    // 생성에 실패한 경우 실행 작업
}

/* 생성에 성공한 경우 실행 작업 */

 

DirectX 9.0 객체의 해제는 IDirect3D9::Release를 호출한다.

/* C++ Source */
#pragma comment(lib, "d3d9.lib") // DirectX 9.0 라이브러리를 링크
#include <Windows.h> // Windows API에서 제공되는 기본 자료형들을 불러오기
#include <d3d9.h> // DirectX 9.0 API를 불러오기

LPDIRECT3D9 lpDirect3D = (LPDIRECT3D9)NULL; // DirectX 9.0 객체

/* 창을 닫거나 그래픽을 종료하는 단계에서 */

lpDirect3D->Release(); // 객체 해제

/* 이후 작업 */

 

실제 코드에 적용


다음은 MFC에서 DirectX 객체를 생성하고 해제하는 예이다. CTestFrameCWndFrame에서 상속된 클래스이고 m_pDirect3D는 사전에 클래스 헤더에 선언된 멤버 변수이다.

/* MFC Source */
int CTestFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    HRESULT hResult = NULL;
    int ret = 0;
    
    TRACE(TEXT("[SUCCESS] CTestFrame::OnCreate called.\n"));
    
    ret = CFrameWnd::OnCreate(lpCreateStruct);
    if (ret != 0)
    {
        TRACE(TEXT("[FAILURE] CTestFrame::OnCreate.\n"));
        return ret;
    }
    
    this->m_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
    if (this->m_pDirect3D == NULL)
    {
        TRACE(TEXT("[FAILURE] Direct3DCreate9.\n"));
        return ret;
    }
    
    return ret;
}

void CTestFrame::OnDestroy()
{
    TRACE(TEXT("[SUCCESS] CTestFrame::OnDestroy called.\n"));
    
    if (this->m_pDirect3D != NULL)
    {
        this->m_pDirect3D->Release();
    }
    
    CFrameWnd::OnDestroy();
}

 

MFC 프로그램의 실행 결과 (아직 빈 창)
OnCreate와 OnDestroy이 실행된 흔적

 

어댑터 종류 열거하기


DirectX를 사용하기 위해 컴퓨터에서 지원 가능한 그래픽 어댑터의 종류를 모두 가져오려면 CreateDXGIFactory 함수로 생성된 IDXGIFactory형 객체를 사용한다.

 

CreateDXGIFactory


/* C++ Source */
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "dxgi.lib")

#include <d3d9.h>
#include <dxgi.h>

IDXGIFactory * pDXGIFactory = NULL;
IDXGIAdapter * pDXGIAdapter = NULL;
SIZE_T nDXGIAdapter = 0;

/* 창을 생성하거나 그래픽을 리셋하는 단계에서 */
hResult = CreateDXGIFactory
(
    __uuidof(IDXGIFactory),                  // REFIID riid
    reinterpret_cast<void **>(&pDXGIFactory) // void ** ppFactory
);

// 해당 인덱스 번호에 해당하는 어댑터가 없다면 DXGI_ERROR_NOT_FOUND를 반환하기 시작한다.
// 0번 어댑터부터 검색을 시작한다.
nDXGIAdapter = 0;
while (pDXGIFactory->EnumAdapter(nDXGIAdapter, &pDXGIAdapter) != DXGI_ERROR_NOT_FOUND)
{
nDXGIAdapter++; // 다음 번호의 어댑터를 확인한다.

}

 

실제 코드에 적용


그래픽 어댑터의 종류를 열거하는 위 코드를 응용하여 디버그 문자열로 그래픽 어댑터의 이름을 출력해보겠다.

/* MFC Source */
int CTestFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    HRESULT hResult = NULL;
    int ret = 0;
    
    TRACE(TEXT("[SUCCESS] CTestFrame::OnCreate called.\n"));
    
    ret = CFrameWnd::OnCreate(lpCreateStruct);
    if (ret != 0)
    {
        TRACE(TEXT("[FAILURE] OnCreate::OnCreate.\n"));
        return ret;
    }
    
    // DirectX 객체를 생성
    this->m_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
    if (this->m_pDirect3D == NULL)
    {
        TRACE(TEXT("[FAILURE] Direct3DCreate9.\n"));
        return ret;
    }
    
    // 어댑터를 열거할 수 있는 IDXGIFactory 객체 생성
    IDXGIFactory * pDXGIFactory = NULL;
    hResult = CreateDXGIFactory
    (
        __uuidof(IDXGIFactory),                  // REFIID riid
        reinterpret_cast<void **>(&pDXGIFactory) // void ** ppFactory
    );
    if (FAILED(hResult))
    {
        TRACE(TEXT("[FAILURE] CreateDXGIFactory.\n"));
        return ret;
    }
    
    // 열거된 어댑터를 하나씩 배열에 보관
    CArray<IDXGIAdapter *> dxgiAdapters;
    IDXGIAdapter * pDXGIAdapter = NULL;
    UINT i = 0;
    while (TRUE)
    {
        hResult = pDXGIFactory->EnumAdapters
        (
            i++,          // UINT Adapter
            &pDXGIAdapter // IDXGIAdapter ** ppAdapter
        );
        
        if (hResult == DXGI_ERROR_NOT_FOUND) break;
        
        dxgiAdapters.Add(pDXGIAdapter);
        pDXGIAdapter = NULL;
    }
    
    // 배열에 보관된 어댑터의 이름을 디버그 문자열로 출력
    for (INT_PTR i = 0; i < dxgiAdapters.GetCount(); i++)
    {
        DXGI_ADAPTER_DESC dxgiAdapterDesc;
        TCHAR szBuffer[256];
        
        dxgiAdapters[i]->GetDesc(&dxgiAdapterDesc);
        TRACE(TEXT("[%2d] DXGI_ADAPTER_DESC.Description = %s\n"), i, dxgiAdapterDesc.Description);
    }
    
    // 사용이 끝난 어댑터 객체와 Factory 객체 해제
    for (INT_PTR i = 0; i < dxgiAdapters.GetCount(); i++)
    {
        dxgiAdapters[i]->Release();
    }
    dxgiAdapters.RemoveAll();
    pDXGIFactory->Release();
    
    return ret;
}

 

본 컴퓨터에 장착된 그래픽 어댑터의 종류가 열거되는 것을 확인할 수 있다. 다만 [장치 관리자]에는 없는 Microsoft Basic Render Driver가 마지막에 있을 수도 있다.

컴퓨터에 장착된 그래픽 어댑터의 종류가 열거되는 결과

 

그래픽 어댑터의 색상 포맷의 지원 여부 확인


ARGB, RGB, HSV 등등의 색상 포맷을 사용하고자 할 때 그래픽 어댑터가 이를 지원하는지 여부를 확인한다. 여기서 XRGB는 RGB와 같은 것을 의미한다.

 

IDirect3D9::EnumAdapterModes


 

/* C++ Source */
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "dxgi.lib")

#include <d3d9.h>
#include <dxgi.h>

LPDIRECT3D9 lpDirect3D = (LPDIRECT3D9)NULL; // DirectX 9.0 객체
UINT uAdapterMode = 0;

/* 창 또는 그래픽을 리셋하는 단계에서 */

uAdapterMode = lpDirect3D->GetAdapterModeCount
(
    D3DADAPTER_DEFAULT, // UINT Adapter : 시스템 기본 어댑터를 사용하겠다
    D3DFMT_X8R8G8B8     // D3DFORMAT Format : 픽셀당 32비트 색상 포맷을 쓴다. 단, 실제 사용되는 비트는 R, G, B 24비트이다. 
);

 

실제 코드에 적용


그래픽 어댑터에서 해당 색상 포맷으로 몇 개의 모드를 지원하는지를 확인해 보겠다.

/* MFC Source */
int CTestFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    HRESULT hResult = NULL;
    int ret = 0;
    
    TRACE(TEXT("[SUCCESS] CTestFrame::OnCreate called.\n"));
    
    ret = CFrameWnd::OnCreate(lpCreateStruct);
    if (ret != 0)
    {
        TRACE(TEXT("[FAILURE] OnCreate::OnCreate.\n"));
        return ret;
    }
    
    this->m_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
    if (this->m_pDirect3D == NULL)
    {
        TRACE(TEXT("[FAILURE] Direct3DCreate9.\n"));
        return ret;
    }
    
    IDXGIFactory * pDXGIFactory = NULL;
    hResult = CreateDXGIFactory
    (
        __uuidof(IDXGIFactory),                 // REFIID riid
        reinterpret_cast<void **>(&pDXGIFactory) // void ** ppFactory
    );
    if (FAILED(hResult))
    {
        TRACE(TEXT("[FAILURE] CreateDXGIFactory.\n"));
        return ret;
    }
    
    UINT uAdapterMode = this->m_pDirect3D->GetAdapterModeCount
    (
        D3DADAPTER_DEFAULT, // UINT Adapter
        D3DFMT_X8R8G8B8     // D3DFORMAT Format
    );
    TRACE(TEXT("[SUCCESS] GetAdapterModeCount = %u\n"), uAdapterMode);
    
    return ret;
}

 

본 컴퓨터의 경우 RGB(X8R8G8B8) 색상 포맷으로 43가지의 어댑터 모드를 지원한다.

 

IDirect3D9::EnumAdapterModes


IDirect3D9::EnumAdapterModes 함수는 그래픽 어댑터가 지원 가능한 모드를 상세히 열거하는 함수이다. 지원 모드는 D3DDISPLAYMODE 구조체를 통해 반환된다.

/* C++ Source */
UINT uAdapterMode = this->m_pDirect3D->GetAdapterModeCount
(
    D3DADAPTER_DEFAULT, // UINT Adapter
    D3DFMT_X8R8G8B8     // D3DFORMAT Format
);

for (UINT i = 0; i < uAdapterMode; i++)
{
    D3DDISPLAYMODE d3dDisplayMode;
    hResult = this->m_pDirect3D->EnumAdapterModes
    (
        D3DADAPTER_DEFAULT, // UINT Adapter
        D3DFMT_X8R8G8B8,    // D3DFORMAT Format
        i,                  // UINT Mode : 어댑터에 대해 얻고자 하는 모드의 번호
        &d3dDisplayMode     // D3DDISPLAYMODE * pMode : 반환 정보
    );
}

 

실제 코드에 적용


기본 어댑터(D3DADAPTER_DEFAULT)의 RGB 컬러(D3DFMT_X8R8G8B8)로 몇 가지의 어댑터 모드가 지원 가능한지 조회해 보겠다. 실행 결과는 컴퓨터마다 상이하다.

/* MFC Source */
int CTestFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    HRESULT hResult = NULL;
    int ret = 0;
    
    TRACE(TEXT("[SUCCESS] CTestFrame::OnCreate called.\n"));
    
    ret = CFrameWnd::OnCreate(lpCreateStruct);
    if (ret != 0)
    {
        TRACE(TEXT("[FAILURE] OnCreate::OnCreate.\n"));
        return ret;
    }
    
    this->m_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
    if (this->m_pDirect3D == NULL)
    {
        TRACE(TEXT("[FAILURE] Direct3DCreate9.\n"));
        return ret;
    }
    
    IDXGIFactory * pDXGIFactory = NULL;
    hResult = CreateDXGIFactory
    (
        __uuidof(IDXGIFactory),                  // REFIID riid
        reinterpret_cast(&pDXGIFactory) // void ** ppFactory
    );
    if (FAILED(hResult))
    {
        TRACE(TEXT("[FAILURE] CreateDXGIFactory.\n"));
        return ret;
    }
    
    UINT uAdapterMode = this->m_pDirect3D->GetAdapterModeCount
    (
        D3DADAPTER_DEFAULT, // UINT Adapter
        D3DFMT_X8R8G8B8     // D3DFORMAT Format
    );
    TRACE(TEXT("[SUCCESS] GetAdapterModeCount = %u\n"), uAdapterMode);
    
    for (UINT i = 0; i < uAdapterMode; i++)
    {
        D3DDISPLAYMODE d3dDisplayMode;
        hResult = this->m_pDirect3D->EnumAdapterModes
        (
            D3DADAPTER_DEFAULT, // UINT Adapter
            D3DFMT_X8R8G8B8,    // D3DFORMAT Format
            i,                  // UINT Mode : 어댑터에 대해 얻고자 하는 모드의 번호
            &d3dDisplayMode     // D3DDISPLAYMODE * pMode : 반환 정보
        );
        if (SUCCEEDED(hResult))
        {
            TRACE(TEXT("[%2u] EnumAdapterModes\n"), i);
            TRACE(TEXT("     D3DDISPLAYMODE.Format = %x\n"), d3dDisplayMode.Format);
            TRACE(TEXT("     D3DDISPLAYMODE.RefreshRate = %u\n"), d3dDisplayMode.RefreshRate);
            TRACE(TEXT("     D3DDISPLAYMODE.Width = %u\n"), d3dDisplayMode.Width);
            TRACE(TEXT("     D3DDISPLAYMODE.Height = %u\n"), d3dDisplayMode.Height);
        }
    }
    
    return ret;
}

 

RGB 모드로 지원 가능한 43개의 어댑터 모드에 대해 프레임 속도와 해상도가 열거되고 있다.

 

어댑터 장치의 성능 확인하기


장치의 성능 중에서 특히 자주하게 확인할 것은 하드웨어로 버텍스 처리가 가능한지 여부이다. 하드웨어에서 버텍스 프로세싱이 가능하면 그대로 이용하고, 그렇지 않을 경우 소프트웨어(CPU)로 버텍스를 연산해야 한다. 이 경우 체감 속도가 다소 느릴 수 있다.

 

IDirect3D9::GetDeviceCaps


IDirect3D9::GetDeviceCaps 함수가 반환하는 구조체인 D3DCAPS9DevCaps 멤버를 통해 확인이 가능하며 D3DDEVCAPS_HWTRANSFORMANDLIGHT 비트의 설정 여부로 하드웨어의 버텍스 처리 성능을 판단할 수 있다.

/* C++ Source */
D3DCAPS9 d3dCaps;
ZeroMemory(&d3dCaps, sizeof(D3DCAPS9));
hResult = this->m_pDirect3D->GetDeviceCaps
(
    D3DADAPTER_DEFAULT, // UINT Adapter
    D3DDEVTYPE_HAL,     // D3DDEVTYPE DeviceType
    &d3dCaps            // D3DCAPS9 * pCaps
);
if (FAILED(hResult))
{
    // 장치 정보 얻기에 실패시 조치
}
if ((d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0)
{
    // 하드웨어로 버텍스 처리가 가능하다.
}
else
{
    // 소프트웨어로 버텍스 처리가 가능하다. (속도 느림)
}

 

실제 코드에 적용


실제 코드에 적용하여 보겠다.

/* MFC Source */
int CTestFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    HRESULT hResult = NULL;
    int ret = 0;
    
    TRACE(TEXT("[SUCCESS] CTestFrame::OnCreate called.\n"));
    
    ret = CFrameWnd::OnCreate(lpCreateStruct);
    if (ret != 0)
    {
        TRACE(TEXT("[FAILURE] OnCreate::OnCreate.\n"));
        return ret;
    }
    
    this->m_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
    if (this->m_pDirect3D == NULL)
    {
        TRACE(TEXT("[FAILURE] Direct3DCreate9.\n"));
        return ret;
    }
    
    D3DCAPS9 d3dCaps;
    ZeroMemory(&d3dCaps, sizeof(D3DCAPS9));
    hResult = this->m_pDirect3D->GetDeviceCaps
    (
        D3DADAPTER_DEFAULT, // UINT Adapter
        D3DDEVTYPE_HAL,     // D3DDEVTYPE DeviceType
        &d3dCaps            // D3DCAPS9 * pCaps
    );
    if (FAILED(hResult))
    {
        TRACE(TEXT("[FAILURE] IDirect3D9::GetDeviceCaps = %u"), hResult);
        return ret;
    }
    if ((d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0)
    {
        TRACE(TEXT("[DEVCAPS] D3DCAPS9.DevCaps has D3DDEVCAPS_HWTRANSFORMANDLIGHT\n"));
    }
    else
    {
        TRACE(TEXT("[DEVCAPS] D3DCAPS9.DevCaps not D3DDEVCAPS_HWTRANSFORMANDLIGHT\n"));
    }
    
    return ret;
}
실행 결과 하드웨어 버텍스 처리가 가능하면 위와 같이 출력될 것이다.

 

IDirect3DDevice9 객체 생성


그래픽 어댑터를 호출할 수 있는 장치 인터페이스인 IDirect3DDevice9 객체를 생성한다. 앞서 확인한 각종 하드웨어 사항과 본인이 선정한 색상 포맷, 기타 필요한 사항들을 조합하여 하나의 컨텍스트를 만든 다음 이를 IDirect3D9::CreateDevice함수에 D3DPRESENT_PARAMETERS 구조체로 전달한다.

IDirect3D9::CreateDevice


IDirect3D9::CreateDevice의 사용 방법은 다음과 같다.

/* C++ Source */
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "dxgi.lib")

#include <d3d9.h>
#include <dxgi.h>

LPDIRECT3D9 lpDirect3D9 = NULL;
LPDIRECT3DDEVICE9 lpDirect3DDevice9 = NULL;
HRESULT hResult;

/* 그래픽을 리셋하는 부분에서 */

hResult = lpDirect3D9->CreateDevice
(
    D3DADAPTER_DEFAULT, // UINT Adapter
    D3DDEVTYPE_HAL,     // D3DDEVTYPE DeviceType
    &d3dCaps            // D3DCAPS9 * pCaps
);

 

실제 코드에 적용


본 함수를 시험하기 전에 새 멤버변수를 클래스의 헤더에 선언하였다.

static const DWORD CTestFrame::GRAPHIC_WIDTH = 800;
윈도우의 폭이면서 그래픽 버퍼의 폭이다. 앞서 그래픽 어댑터에서 지원 가능한 것으로 확인된 800 픽셀을 상수로 지정한 것이다.
static const DWORD CTestFrame::GRAPHIC_HEIGHT = 600;
윈도우의 높이면서 그래픽 버퍼의 높이이다. 앞서 그래픽 어댑터에서 지원 가능한 것으로 확인된 600 픽셀을 상수로 지정한 것이다.
IDirect3DDevice * m_pDirect3DDevice = NULL
새로 생성될 장치 객체이다.

 

/* MFC Source */
int CTestFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    HRESULT hResult = NULL;
    int ret = 0;
    
    TRACE(TEXT("[SUCCESS] CTestFrame::OnCreate called.\n"));
    
    ret = CFrameWnd::OnCreate(lpCreateStruct);
    if (ret != 0)
    {
        TRACE(TEXT("[FAILURE] OnCreate::OnCreate.\n"));
        return ret;
    }
    
    this->m_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
    if (this->m_pDirect3D == NULL)
    {
        TRACE(TEXT("[FAILURE] Direct3DCreate9.\n"));
        return ret;
    }
    
    // 윈도우 크기를 조정한다.
    RECT rect;
    this->GetWindowRect(&rect);
    this->SetWindowPos(NULL, rect.left, rect.top, CTestFrame::GRAPHIC_WIDTH, CTestFrame::GRAPHIC_HEIGHT, SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW);
    
    D3DCAPS9 d3dCaps;
    D3DPRESENT_PARAMETERS d3dPresentParameters;
    ZeroMemory(&d3dCaps, sizeof(D3DCAPS9));
    ZeroMemory(&d3dPresentParameters, sizeof(D3DPRESENT_PARAMETERS));
    hResult = this->m_pDirect3D->GetDeviceCaps
    (
        D3DADAPTER_DEFAULT, // UINT Adapter
        D3DDEVTYPE_HAL,     // D3DDEVTYPE DeviceType
        &d3dCaps            // D3DCAPS9 * pCaps
    );
    if (FAILED(hResult))
    {
        TRACE(TEXT("[FAILURE] IDirect3D9::GetDeviceCaps = %u"), hResult);
        return ret;
    }
    if ((d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0)
    {
        TRACE(TEXT("[DEVCAPS] D3DCAPS9.DevCaps has D3DDEVCAPS_HWTRANSFORMANDLIGHT\n"));
        
        // 그래픽 어댑터가 하드웨어 버텍스 처리를 지원하는 경우
        ZeroMemory(&d3dPresentParameters, sizeof(D3DPRESENT_PARAMETERS));
        d3dPresentParameters.BackBufferWidth = CTestFrame::GRAPHIC_WIDTH;
        d3dPresentParameters.BackBufferHeight = CTestFrame::GRAPHIC_HEIGHT;
        d3dPresentParameters.BackBufferFormat = D3DFMT_X8R8G8B8;
        d3dPresentParameters.BackBufferCount = 1;
        d3dPresentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
        d3dPresentParameters.hDeviceWindow = this->m_hWnd; // 윈도우 핸들
        d3dPresentParameters.Windowed = TRUE;  // 어플리케이션을 창 모드로 연다.
        d3dPresentParameters.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
        
        hResult = m_pDirect3D->CreateDevice
        (
            D3DADAPTER_DEFAULT,
            D3DDEVTYPE_HAL,
            this->m_hWnd,
            D3DCREATE_HARDWARE_VERTEXPROCESSING, // 하드웨어 버텍스 처리
            &d3dPresentParameters,
            &this->m_pDirect3DDevice
        );
        if (FAILED(hResult))
        {
            TRACE(TEXT("[FAILURE] IDirect3D9::CreateDevice = %x\n"), hResult);
            return ret;
        }
        TRACE(TEXT("[SUCCESS] IDirect3D9::CreateDevice\n"));
    }
    else
    {
        TRACE(TEXT("[DEVCAPS] D3DCAPS9.DevCaps not D3DDEVCAPS_HWTRANSFORMANDLIGHT\n"));
        
        // 그래픽 어댑터가 하드웨어 버텍스 처리를 지원하지 않는 경우
        ZeroMemory(&d3dPresentParameters, sizeof(D3DPRESENT_PARAMETERS));
        d3dPresentParameters.BackBufferWidth = CTestFrame::GRAPHIC_WIDTH;
        d3dPresentParameters.BackBufferHeight = CTestFrame::GRAPHIC_HEIGHT;
        d3dPresentParameters.BackBufferFormat = D3DFMT_X8R8G8B8;
        d3dPresentParameters.BackBufferCount = 1;
        d3dPresentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
        d3dPresentParameters.hDeviceWindow = this->m_hWnd; // 윈도우 핸들
        d3dPresentParameters.Windowed = TRUE; // 어플리케이션을 창 모드로 연다.
        d3dPresentParameters.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
        
        // 장치 객체를 생성한다.
        hResult = m_pDirect3D->CreateDevice
        (
            D3DADAPTER_DEFAULT,
            D3DDEVTYPE_HAL,
            this->m_hWnd,
            D3DCREATE_SOFTWARE_VERTEXPROCESSING, // 소프트웨어로 버텍스 연산한다. (느림)
            &d3dPresentParameters,
            &this->m_pDirect3DDevice
        );
        if (FAILED(hResult))
        {
            TRACE(TEXT("[FAILURE] IDirect3D9::CreateDevice = %x\n"), hResult);
            return ret;
        }
        TRACE(TEXT("[SUCCESS] IDirect3D9::CreateDevice\n"));
    }
    
    return ret;>>
}
크기가 조정된 DirectX 윈도우
장치 객체 생성에 성공한 흔적

 

카테고리 “API/DirectX”
more...
[단막 C++ 문법] C++ 배열 반복문 사용 방법
C++ 배열 반복문 사용 방법 C++에서 배열 반복문을 사용하는 방법은 다음과 같이 정리할 수 있다. 1. 전통적인 배열 반복문 전통적인 배열 반복문은 역시 인덱스 변수를 선언하고 그 변수를 증감하면서 배열의 원소에 순차적으로 접근하는 방식이다. /* C++ source */ #include // std::array #include // std::cout using namespace std; int main(int argc, char * argv[]) { std::array integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 }; for (size_t i = 0; i < integers.size(); i++) { cout
Language/C & C++
2019. 2. 7. 16:06

[단막 C++ 문법] C++ 배열 반복문 사용 방법

Language/C & C++
2019. 2. 7. 16:06

C++ 배열 반복문 사용 방법


C++에서 배열 반복문을 사용하는 방법은 다음과 같이 정리할 수 있다.

 

1. 전통적인 배열 반복문


전통적인 배열 반복문은 역시 인덱스 변수를 선언하고 그 변수를 증감하면서 배열의 원소에 순차적으로 접근하는 방식이다.

/* C++ source */
#include <array>     // std::array
#include <iostream>  // std::cout

using namespace std;

int main(int argc, char * argv[])
{
    std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
    
    for (size_t i = 0; i < integers.size(); i++)
    {
        cout<<integers[i]<<'\t';
    }
    return 0;
}

 

2. 반복자 사용하기


STL Container 클래스내의 반복자(iterator)를 자동변수로 선언하여 배열의 원소에 순차적으로 접근할 수도 있다. 여기에서 반복자의 자료형은 std::array<int, 10>::iterator이지만 작성의 편의를 위해 auto 키워드로 대체하여 반복자를 선언하는 것이 보통이다.

/* C++ source */
#include <array>     // std::array
#include <iostream>  // std::cout

using namespace std;

int main(int argc, char * argv[])
{
    std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
    
    for (std::array<int, 10>::iterator = integers.begin(); integer != integers.end(); integer++)
    {
        cout<<*integers<<'\t';
    }
    
    return 0;
}

 

3. std::for_each 사용하기


std::for_each는 앞서 작성한 반복자 선언을 좀 더 키워드화한 것으로 algorithm 헤더 파일에 다음과 같이 선언되어 있다.

/* C++ source */
template <class InputIterator, class Function>
Function for_each (InputIterator first, InputIterator last, Function fn);

 

여기서 fn 매개변수는 람다식이 될 수도 있고 함수 포인터가 될 수도 있다. 다음은 함수 포인터를 사용한 예이다.

/* C++ source */
#include <algorithm> // std::for_each
#include <array>     // std::array
#include <iostream>  // std::cout

using namespace std;

void myloop(int);

int main(int argc, char * argv[])
{
    std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
    
    std::for_each
    (
        integers.begin(), // InputIterator first
        integers.end(), // InputIterator last
        myloop // Function fn
    );
    
    return 0;
}

void myloop(int integer)
{
    cout<<integer<<'\t';
}

 

다음은 람다식을 사용한 예이다.

/* C++ source */
#include <algorithm> // std::for_each
#include <array>     // std::array
#include <iostream>  // std::cout

using namespace std;

int main(int argc, char * argv[])
{
    std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
    
    std::for_each
    (
        integers.begin(), // InputIterator first
        integers.end(), // InputIterator last
        [](int integer) { cout<<integer<<'\t'; } // Function fn
    );
    
    return 0;
}

 

4. Range based for 구문 사용하기


Range based for가 바로 파이썬, 베이직 등 타 스크립트 언어에서 지원하는 foreach 구문이다. C++에서 foreach 구문은 다음과 같은 형태를 갖는다.

/* C++ source */
#include <array>     // std::array
#include <iostream>  // std::cout

using namespace std;

int main(int argc, char * argv[])
{
    std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
    
    for (int & integer : integers)
    {
        cout<<integer<<'\t';
    }
    
    return 0;
}

 

카테고리 “Language/C & C++”
more...
썸네일 이미지
OpenCL 기본 실행 예 (1)
OpenCL 기본 실행 예 (1) OpenCL은 실행을 위한 세팅 과정이 너무 많다. 따라서 본 포스팅을 통해 OpenCL을 실행하는 예시 코드를 정리해 둔다. 0 단계. 사용 가능한 플랫폼 개수 확인하기. OpenCL을 사용할 수 있는 플랫폼(CPU와 GPU 등 디바이스 조합)이 총 몇 개인지를 확인하기 위해 clGetPlatformIDs 함수를 사용한다. 2 번째 매개변수인 cl_platform_id * platforms에 NULL을 대입하면 플랫폼의 총 개수가 반환된다. 이 때 플랫폼의 개수는 1 번째 매개변수인 cl_uint num_entries 이하로 반환한다. 그러므로 최대의 개수를 얻고자 하면 1 번째 매개변수에 -1을 대입하면 된다. 이 매개변수의 자료형은 부호 없는 정수(cl_uint)이..
API/OpenCL
2019. 2. 2. 22:25

OpenCL 기본 실행 예 (1)

API/OpenCL
2019. 2. 2. 22:25

OpenCL 기본 실행 예 (1)


OpenCL은 실행을 위한 세팅 과정이 너무 많다. 따라서 본 포스팅을 통해 OpenCL을 실행하는 예시 코드를 정리해 둔다.

 

0 단계. 사용 가능한 플랫폼 개수 확인하기.


OpenCL을 사용할 수 있는 플랫폼(CPU와 GPU 등 디바이스 조합)이 총 몇 개인지를 확인하기 위해 clGetPlatformIDs 함수를 사용한다. 2 번째 매개변수인 cl_platform_id * platformsNULL을 대입하면 플랫폼의 총 개수가 반환된다. 이 때 플랫폼의 개수는 1 번째 매개변수인 cl_uint num_entries 이하로 반환한다. 그러므로 최대의 개수를 얻고자 하면 1 번째 매개변수에 -1을 대입하면 된다. 이 매개변수의 자료형은 부호 없는 정수(cl_uint)이기 때문에 -10xFFFF, 0xFFFFFFFF, 0xFFFFFFFFFFFFFFFF 중에 하나이고 부호 없는 정수의 최댓값과 같다.

사용 가능한 플랫폼의 수는 3 번째 매개변수인 cl_uint * num_platforms으로 반환된다. 함수 자체가 반환하는 값은 함수의 실행 결과로서, 정상 작동했다면 CL_SUCCESS를 반환한다.

/* C source */
// 새로 추가된 변수
cl_int errNo;
cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

/* ... */

// 0 단계. 시스템으로부터 OpenCL이 사용 가능한 플랫폼의 수를 확인한다.
errNo = clGetPlatformIDs
(
    (cl_uint)(-1),  // cl_uint num_entries
     NULL,          // cl_platform_id * platforms
    &nPlatformId    // cl_uint * num_platforms
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clGetPlatformIDs = %d\n", errNo);
    // 실패 시 작업
}
else if (nPlatformId <= 0)
{
    printf("[FAILURE] clGetPlatformIDs: nPlatformId = %u", nPlatformId);
    // 실패 시 작업
}
else
{
    printf("[SUCCEED] clGetPlatformIDs: nPlatformId = %u\n", nPlatformId);
}

/* ... */

 

사용 가능한 플랫폼의 수(여기서는 1개)를 확인할 수 있다.

 

1 단계. 사용 가능한 플랫폼 목록 가져오기.


앞서 0 단계에서 플랫폼 수를 확인하였다면, 메모리를 동적할당하여 플랫폼 목록을 가져온다. 마찬가지로 clGetPlatformIDs를 사용한다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

/* ... */

// nPlatformId개의 구조체를 동적할당한다.
assert(lpPlatformId = (cl_platform_id *)calloc(nPlatformId, sizeof(cl_platform_id)));

// 1 단계. 플랫폼 목록을 가져온다.
errNo = clGetPlatformIDs
(
    nPlatformId,  // cl_uint num_entries
    lpPlatformId, // cl_platform_id * platforms
    NULL          // cl_uint * num_platforms
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clGetPlatformIDs = %d\n", errNo);
    // 실패 시 작업
}
else
{
    printf("[SUCCEED] clGetPlatformIDs: lpPlatformId = %p\n", lpPlatformId);
}

/* ... */

free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

사용 가능한 플랫폼 목록을 가져온다.

 

2 단계. 컨텍스트 생성


컨텍스트context는 OpenCL에서 사용 가능한 CPU + GPU + 메모리 객체 등등을 아우르는 개념이다. OpenCL은 컨텍스트 속에서 작동하는 구조로 되어 있으므로 이를 얻는 것이 가장 중요하다.

본 포스팅에서는 컨텍스트를 얻기 위해서 clCreateContextFromType 함수를 사용하겠다.

 

2-1 단계. 컨텍스트 프로퍼티 구성


컨텍스트 프로퍼티context property clCreateContextFromType에서 요구하는 매개변수로서 다음과 같은 구조의 배열이다.

{ CL_CONTEXT_PLATFORM, 플랫폼 1, CL_CONTEXT_PLATFORM, 플랫폼 2, ..., 0 }

 

마지막 원소는 항상 0이어야 한다.

다음은 앞서 얻은 플랫폼 배열을 통해 컨텍스트 프로퍼티 배열을 구성하는 예이다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

// 새로 추가된 변수
size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

/* ... */

assert((nContextProperties = nPlatformId * 2 + 1) > 1);
assert(lpContextProperties = (cl_context_properties *)calloc(nContextProperties, sizeof(cl_context_properties)));
for (i = 0, j = 0; i < nContextProperties;)
{
    if (i < nContextProperties - 1)
    {
        // 마지막 원소가 아니라면...
        // 플랫폼에 대해 질의한다.
        lpContextProperties[i++] = (cl_context_properties)CL_CONTEXT_PLATFORM;
        // 질의하고 싶은 플랫폼을 지정한다.
        lpContextProperties[i++] = (cl_context_properties)lpPlatformId[j++];
    }
    else
    {
        // 마지막 원소는 항상 zero
        lpContextProperties[i] = (cl_context_properties)0;
        break;
    }
}

/* ... */

free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

2-2 단계. 컨텍스트 객체 생성


앞서 구성한 컨텍스트 프로퍼티 배열을 가지고 OpenCL 컨텍스트 객체를 생성한다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

// 새로 추가된 변수
size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

/* ... */

context = clCreateContextFromType
(
    lpContextProperties, // cl_context_properties * properties
    CL_DEVICE_TYPE_ALL,  // cl_device_type device_type
    NULL,                // void  (*pfn_notify) (const char *errinfo, const void  *private_info, size_t  cb, void  *user_data)
    NULL,                // void * user_data
    &errNo               // cl_int * errcode_ret
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clCreateContextFromType = %d\n", errNo);
    free(lpContextProperties); // 컨텍스트 얻기에 사용된 배열 해제
    // 실패 시 작업
}
else
{
    printf("[SUCCEED] clCreateContextFromType: context = %p\n", context);
}

/* ... */

clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

컨텍스트 객체 생성에 성공한 화면

 

3 단계. 컨텍스트에 포함된 장치들 얻기


컨텍스트에는 CPU, GPU를 포함한 가속 장치들이 포함되어 있다. 현재 컨텍스트에 몇 개의 장치들이 있고, 각각의 장치가 무엇인지 확인하기 위해 다음과 같이 코드를 작성한다.

 

3-1. 장치 정보를 얻기 위한 버퍼의 크기 구하기


컨텍스트에 포함된 장치들의 목록을 얻기 위해 총 몇 바이트의 버퍼가 필요한지 확인하는 과정이다. clGetContextInfo를 사용하며 매개변수에 param_value_size = 0, param_value = NULL을 하고 param_value_size_ret =&변수를 하면 변수를 통해 메모리의 크기가 반환된다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

// 새로 추가된 변수
size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;

/* ... */

errNo = clGetContextInfo
(
    context,            // cl_context context
    CL_CONTEXT_DEVICES, // cl_context_info param_name
    0,                  // size_t param_value_size
    NULL,               // void * param_value
    &cbDeviceBuffer     // size_t * param_value_size_ret
);
if (errNo != CL_SUCCESS)
{
    fprintf(stderr, "[FAILURE] clGetContextInfo = %d\n", errNo);
    // 실패 시 작업
}
else if (cbDeviceBuffer <= 0)
{
    fprintf(stderr, "[FAILURE] deviceBufferSize = %zu\n", cbDeviceBuffer);
    // 실패 시 작업
}
else
{
    fprintf(stderr, "[SUCCESS] clGetContextInfo: cbDeviceBuffer = %zu\n", cbDeviceBuffer);
}

/* ... */

clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 얻기에 사용된 배열 해제
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

3-2. 컨텍스트에 있는 장치들의 정보 가져오기


장치 목록들을 얻기 위해 몇 바이트가 필요한지를 위와 같이 clGetContextInfo 함수로 확인하였다. 다시 호출하여 장치 목록을 복사해온다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

// 새로 추가된 변수
size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;

/* ... */

assert(nDeviceId = (cl_uint)(cbDeviceBuffer / sizeof(cl_device_id)));
assert(lpDeviceId = (cl_device_id *)calloc(nDeviceId, sizeof(cl_device_id)));
errNo = clGetContextInfo
(
    context,            // cl_context context
    CL_CONTEXT_DEVICES, // cl_context_info param_name
    cbDeviceBuffer,     // size_t param_value_size
    lpDeviceId,         // void * param_value
    NULL                // size_t * param_value_size_ret
);
if (errNo != CL_SUCCESS)
{
    fprintf(stderr, "[FAILURE] clGetContextInfo = %d\n", errNo);
    // 실패 시 작업
}
else
{
    printf("[SUCCESS] clGetContextInfo: nDeviceId = %zu\n", nDeviceId);
}

/* ... */

free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

이 PC에서는 컨텍스트에 2개의 장치가 있음을 확인 가능하다.

 

3-3. 컨텍스트에 있는 장치들에 대한 커맨드 큐 객체 생성


커맨드 큐command queue는 연산장치가 수행할 연산들이 대기하는 장소이다. clCreateCommandQueue 함수로 컨텍스트에 있는 각 장치들의 커맨트 큐 객체를 생성한다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;

// 새로 추가된 변수
size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;

/* ... */

assert((nCommandQueue = nDeviceId) > 0);
assert(lpCommandQueue = (cl_command_queue *)calloc(nCommandQueue, sizeof(cl_command_queue)));
for (i = 0; i < nCommandQueue; i++)
{
    lpCommandQueue[i] = clCreateCommandQueue
    (
        context,       //cl_context context
        lpDeviceId[i], // cl_device_id device
        0,           // cl_command_queue_properties properties
        &errNo         // cl_int * errcode_ret
    );
    if (errNo != CL_SUCCESS)
    {
        printf("[FAILURE] clCreateCommandQueue = %d\n", errNo);
        // 실패 시 작업
    }
    else if (lpCommandQueue[i] == NULL)
    {
        printf("[FAILURE] commandQuene == %p @ %zu\n", lpCommandQueue[i], i);
        // 실패 시 작업
    }
    else
    {
        printf("[SUCCESS] clCreateCommandQueue: commandQueue = %p @ %zu\n", lpCommandQueue[i], i);
    }
}

/* ... */

free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

커맨드 큐 객체 생성 결과

 

4 단계. OpenCL 소스 파일을 읽어서 컴파일


OpenCL 소스는 기본적으로 텍스트 상태로 배포된다. 사용자의 다양한 아키텍처에 맞추어 즉석으로 컴파일하기 때문이다. 이 때 사용되는 함수는 clCreateProgramWithSource, clBuildProgram이다.

 

4-1 단계. OpenCL 소스 작성 후 읽기


OpenCL 소스를 작성한다. 파일 이름은 hello.cl로 정한다.

/* OpenCL source */
// ptr1 배열의 각 원소와 ptr2 배열의 각 원소를 더하여 out으로 리턴한다.
__kernel void hello(__global float * out, __global const float * ptr1, __global const float * ptr2)
{
    int global_id = 0; // 배열의 몇 번째 원소를 참조할 것인지를 나타내는 인덱스 변수이다.
    
    global_id = get_global_id(0);
    out[global_id] = (ptr1[global_id] + ptr2[global_id]);
    
    return;
}

 

위 소스 파일을 C로 읽어들인다.

/* C source */
FILE * fp = NULL;
size_t cbSource = 0;
char * lpSource = NULL;

if (fp = fopen("./hello.cl", "rt")) // 파일을 읽기 모드로 연다
{
    fseek(fp, 0, SEEK_END); // 파일이 몇 바이트인지를 체크한다.
    cbSource = ftell(fp) + 1;
    lpSource = (char *)calloc(cbSource, sizeof(char));
    fseek(fp, 0, SEEK_SET); // 파일을 처음부터 끝까지 읽는다.
    fread(lpSource, sizeof(char), cbSource, fp);
}
fclose(fp);

 

4-2 단계. OpenCL 소스로부터 프로그램 객체를 생성


위의 단계에서 읽어들인 소스 파일을 컴파일한다. 컴파일에 앞서 소스 문자열로부터 프로그램 객체를 생성해야 한다. 소스 문자열로부터 프로그램 객체를 생성하는 함수는 clCreateProgramWithSource이다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;

size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;

// 새로 추가된 변수
cl_program program;

/* ... */

program = clCreateProgramWithSource
(
    context,                       // cl_context context
    1,                             // cl_uint count
    (const char **)&sourceContent, // const char **strings
    &sourceContentSize,            // const size_t *lengths
    &errNum                        // cl_int *errcode_ret
);
if (errNum != CL_SUCCESS)
{
    printf("[FAILURE] clCreateProgramWithSource == %d\n", errNum);
    // 실패 시 작업
}
else
{
    printf("[SUCCESS] clCreateProgramWithSource\n");
}

/* ... */

clReleaseProgram(program); // 프로그램 객체 해제
free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

프로그램 객체의 생성 결과

 

4-3 단계. OpenCL 소스 컴파일


앞서 생성한 프로그램 객체를 컴파일한다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;

size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;

cl_program program;

/* ... */

errNo = clBuildProgram
(
    program, // cl_program program : 컴파일하고자 하는 프로그램 객체
    0,       // cl_uint num_devices : 컴파일 대상이 될 장치의 개수 (0개 = 컨텍스트에 있는 기본 장치들)
    NULL,    // const cl_device_id * device_list : 컴파일 대상이 될 장치 (NULL = 컨텍스트에 있는 기본 장치들)
    NULL,    // const char * options : 빌드 옵션
    NULL,    // void (*pfn_notify)(cl_program, void * user_data) : 콜백 함수
    NULL     // void * user_data : 콜백함수에 전달할 사용자 데이터
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clBuildProgram == %d\n", errNo);
    // 실패 시 작업
}
else
{
    printf("[SUCCESS] clBuildProgram\n");
}

/* ... */

clReleaseProgram(program); // 프로그램 객체 해제
free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

OpenCL 소스 컴파일 결과

 

5 단계. OpenCL에서 선언한 함수 실행하기


커널kernel은 OpenCL 소스에서 선언한 각 함수들의 진입점에 해당하는 개념이다. 앞서 컴파일한 OpenCL 소스에서 특정 함수의 이름을 통해 그 커널을 가져와 호출한다. 함수가 매개변수를 필요로 한다면 메모리 객체를 별도로 구성해야 한다.

 

5-1 단계. 커널 얻기


hello.cl 소스 파일에서 선언한 한 함수 hello를 호출해보도록 하겠다. 이를 위해 커널 객체를 생성한다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;

size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;

cl_program program;

// 새로 추가된 변수
cl_kernel kernel;

/* ... */

kernel = clCreateKernel
(
    program, // cl_program program
    "hello", // const char * kernel_name
    &errNo   // cl_int * errcode_ret
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clCreateKernel = %d\n", errNo);
    // 실패 시 작업
}
else if (kernel == NULL)
{
    printf("[FAILURE] clCreateKernel: kernel = %p\n", kernel);
    // 실패 시 작업
}
else
{
    printf("[SUCCESS] clCreateKernel\n");
}

/* ... */

clReleaseKernel(kernel); // 커널 객체 해제
clReleaseProgram(program); // 프로그램 객체 해제
free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

5-2 단계. 매개변수 구성하기


OpenCL 소스에서 선언한 hello 함수에서는 float * 형식의 3개의 매개변수(out, ptr1, ptr2)가 필요하다. 이를 구성해보겠다.

먼저 다음과 같이 테스트 배열을 마련한다. 하나는 1부터 50까지이고 다른 하나는 51부터 100까지의 각 50개의 실수로 구성된 배열이다.

/* C source */
float out[50] = { 0.0f, };
float ptr1[50] = { 0.0f, };
float ptr2[50] = { 0.0f, };
size_t i = 0;
for (i = 0; i < 50; i++) 
{
    ptr1[i] = (1.0f + (float)i);
    ptr2[i] = (51.0f + (float)i);
}

 

C의 배열을 OpenCL 소스에서 작성한 함수로 보내기 위해서는 메모리 객체를 생성한다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;

size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;

cl_program program;

cl_kernel kernel;

// 새로 추가된 변수
cl_mem memOut;
cl_mem memPtr1;
cl_mem memPtr2;

/* ... */

memOut = clCreateBuffer
(
    context,                                  // cl_context context : 배열을 사용할 컨텍스트
    CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, // cl_mem_flags flags : 각종 속성
    sizeof(out),                              // size_t size : 배열의 크기 (단위 = 바이트)
    out,                                      // void * host_ptr : 배열
    &errNo                                    // cl_int * errcode_ret : 오류 코드
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clCreateBuffer = %d", errNo);
    // 실패 시 작업
}
else if (memOut == NULL)
{
    printf("[FAILURE] clCreateBuffer: memOut = %p", memOut);
    // 실패 시 작업
}
else
{
    printf("[SUCCESS] clCreateBuffer\n");
}

memOut = clCreateBuffer
(
    context,                                  // cl_context context
    CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,  // cl_mem_flags flags
    sizeof(ptr1),                             // size_t size
    ptr1,                                     // void * host_ptr
    &errNo                                    // cl_int * errcode_ret
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clCreateBuffer = %d", errNo);
    // 실패 시 작업
}
else if (memOut == NULL)
{
    printf("[FAILURE] clCreateBuffer: memOut = %p", memOut);
    // 실패 시 작업
}
else
{
    printf("[SUCCESS] clCreateBuffer\n");
}

memOut = clCreateBuffer
(
    context,                                  // cl_context context
    CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,  // cl_mem_flags flags
    sizeof(ptr2),                             // size_t size
    ptr2,                                     // void * host_ptr
    &errNo                                    // cl_int * errcode_ret
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clCreateBuffer = %d", errNo);
    // 실패 시 작업
}
else if (memOut == NULL)
{
    printf("[FAILURE] clCreateBuffer: memOut = %p", memOut);
    // 실패 시 작업
}
else
{
    printf("[SUCCESS] clCreateBuffer\n");
}

/* ... */

clReleaseMemObject(memOut); // float out[50]에 대한 메모리 객체 해제
clReleaseMemObject(memPtr1); // float ptr1[50]에 대한 메모리 객체 해제
clReleaseMemObject(memPtr2); // float ptr2[50]에 대한 메모리 객체 해제
clReleaseKernel(kernel); // 커널 객체 해제
clReleaseProgram(program); // 프로그램 객체 해제
free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

메모리 객체의 생성 결과

 

5-3 단계. 커널에 매개변수 전달하기


함수 진입점을 '커널'의 형태로 얻었고, 함수에 전달할 매개변수도 '메모리'의 형태로 구성하였다. 이제 커널과 매개변수를 연계해보겠다. OpenCL 소스에서 (out, ptr1, ptr2)의 순서로 선언하였으므로 이 순서대로 메모리 객체를 커널에 연계한다. 이 때 쓰이는 함수는 clSetKernelArg이다.

/* C source */

cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;

size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;

cl_program program;

cl_kernel kernel;

// 새로 추가된 변수
cl_mem memOut;
cl_mem memPtr1;
cl_mem memPtr2;

/* ... */

errNo = clSetKernelArg
(
    kernel,         // cl_kernel kernel : 실행하고 싶은 커널
    0,              // cl_uint arg_index : 커널에 선언된 매개변수 순서 (0번째 = out)
    sizeof(cl_mem), // size_t arg_size : 메모리 객체의 크기
    &memOut         // const void * arg_value : 버퍼
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clSetKernelArg = %d\n", errNo);
}
else
{
    printf("[SUCCESS] clSetKernelArg\n");
}
    
errNo = clSetKernelArg
(
    kernel,         // cl_kernel kernel
    1,              // cl_uint arg_index : 1번째 = ptr1
    sizeof(cl_mem), // size_t arg_size
    &memPtr1        // const void * arg_value
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clSetKernelArg = %d\n", errNo);
}
else
{
    printf("[SUCCESS] clSetKernelArg\n");
}
    
errNo = clSetKernelArg
(
    kernel,         // cl_kernel kernel
    2,              // cl_uint arg_index : 2번째 = ptr2
    sizeof(cl_mem), // size_t arg_size
    &memPtr2        // const void * arg_value
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clSetKernelArg = %d\n", errNo);
}
else
{
    printf("[SUCCESS] clSetKernelArg\n");
}

/* ... */

clReleaseMemObject(memOut); // float out[50]에 대한 메모리 객체 해제
clReleaseMemObject(memPtr1); // float ptr1[50]에 대한 메모리 객체 해제
clReleaseMemObject(memPtr2); // float ptr2[50]에 대한 메모리 객체 해제
clReleaseKernel(kernel); // 커널 객체 해제
clReleaseProgram(program); // 프로그램 객체 해제
free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

커널에 매개변수 지정한 결과

 

5-4 단계. 커널을 호출하기


이제 커널을 실행한다. 커맨드 큐에 커널을 추가하는 것으로 커널은 실행 준비 상태가 된다. 앞서 장치의 수 만큼 여러개의 커맨드 큐를 얻었는데 0번째 커맨드 큐로 커널을 추가해 보겠다. 이 때 사용되는 함수는 clEnqueueNDRangeKernel이다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;

size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;

cl_program program;

cl_kernel kernel;

cl_mem memOut;
cl_mem memPtr1;
cl_mem memPtr2;

// 새로 추가된 변수
// 커널이 수행할 총 작업량을 어떻게 분배할 것인지 그 크기를 지정한다. 이 분배는 최적화 방식에 따라 달라질 수 있다.
size_t globalWorkSize[1] = { 50 }; // 각 배열이 가지고 있는 50개의 원소
size_t localWorkSize[1] = { 1 }; // 하나의 디바이스에 몰아서 처리

/* ... */

errNo = clEnqueueNDRangeKernel
(
    lpCommandQueue[0], // cl_command_queue command_queue
    kernel,            // cl_kernel kernel
    1,                 // cl_uint work_dim
    NULL,              // const size_t * global_work_offset
    globalWorkSize,    // const size_t * global_work_size
    localWorkSize,     // const size_t * local_work_size
    0,                 // cl_uint num_events_in_wait_list
    NULL,              // const cl_event * event_wait_list
    NULL               // cl_event * event
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clEnqueueNDRangeKernel = %d\n", errNo);
    // 실패 시 작업
}
else
{
    printf("[SUCCESS] clEnqueueNDRangeKernel\n");
}

/* ... */

clReleaseMemObject(memOut); // float out[50]에 대한 메모리 객체 해제
clReleaseMemObject(memPtr1); // float ptr1[50]에 대한 메모리 객체 해제
clReleaseMemObject(memPtr2); // float ptr2[50]에 대한 메모리 객체 해제
clReleaseKernel(kernel); // 커널 객체 해제
clReleaseProgram(program); // 프로그램 객체 해제
free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

커널을 큐에 추가한 결과

 

6 단계. 커널의 실행 결과 읽기


커널에서는 out 매개 변수를 통해 값을 함수 밖으로 전달한다. 이 매개변수는 C 소스에서 cl_mem 형식으로 전달된다. cl_mem으로부터 배열을 읽어올 때 사용되는 함수는 clEnqueueReadBuffer이다.

/* C source */
cl_int errNo;

cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;

size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;

size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;

size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;

cl_program program;

cl_kernel kernel;

float out[50] = { 0.0f, };
float ptr1[50] = { 0.0f, };
float ptr2[50] = { 0.0f, };
cl_mem memOut;
cl_mem memPtr1;
cl_mem memPtr2;

size_t globalWorkSize[1] = { 50 }; 
size_t localWorkSize[1] = { 1 }; 

/* ... */

errNo = clEnqueueReadBuffer
(
    lpCommandQueue[0], // cl_command_queue command_queue
    memOut,            // cl_mem buffer
    CL_TRUE,           // cl_bool blocking_read
    0,                 // size_t offset
    sizeof(out),       // size_t cb
    out,               // void * ptr
    0,                 // cl_uint num_events_in_wait_list
    NULL,              // const cl_event * event_wait_list
    NULL               // cl_event * event
);
if (errNo != CL_SUCCESS)
{
    printf("[FAILURE] clEnqueueReadBuffer = %d\n", errNo);
    // 실패 시 작업
}
else
{
    printf("[SUCCESS] clEnqueueReadBuffer\n");
}

/* ... */

clReleaseMemObject(memOut); // float out[50]에 대한 메모리 객체 해제
clReleaseMemObject(memPtr1); // float ptr1[50]에 대한 메모리 객체 해제
clReleaseMemObject(memPtr2); // float ptr2[50]에 대한 메모리 객체 해제
clReleaseKernel(kernel); // 커널 객체 해제
clReleaseProgram(program); // 프로그램 객체 해제
free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.

 

메모리 객체로부터 배열을 읽은 결과

 

6-1. 결과 확인하기


OpenCL 커널의 연산 결과는 clEnqueueReadBuffer를 통해 float out[50];로 복사되었다. 출력해보면 OpenCL 소스에서 작성하였던 ptr1[x] * 100.0f + ptr2[x]의 결과가 출력됨을 확인할 수 있다.

/* C source */
/* ... */
for (i = 0; i < 50; i++)
{
    printf("%6.1f ", out[i]);
    if ((i + 1) % 8 == 0) printf("\n");
}
    
printf("\n");
/* ... */

 

OpenCL 소스 코드의 실행 결과

 

“2019/02” (5건)