2013年1月14日月曜日

C++ AMP 3 ヒストグラム

グレースケールのヒストグラムを画像ファイルに保存するテスト

ダイオウイカの目が怖かったじゃなイカ
atomic_fetch_add でアトミックに加算でゲソ
restrict(amp) がついてる処理がGPU上で実行されるはずじゃなイカ
デバッグ実行をGPUのみにしてもブレークしないじゃなイカ

main.cpp

#include <wincodec.h>

#include <iostream>
#include <tchar.h>
#include <assert.h>

#include <d3d11.h>
#include <DirectXTex.h>
#include <amp.h>
#include <amp_graphics.h>
#include <amp_math.h>

#pragma comment(lib, "d3d11.lib")


using namespace concurrency;
using namespace concurrency::graphics;
using namespace concurrency::graphics::direct3d;

ID3D11Device*    g_pd3dDevice = nullptr;
ID3D11DeviceContext*  g_pImmediateContext = nullptr ;

ID3D11Texture2D*   g_pInputTexture = nullptr;
texture<unorm4, 2>*   g_pAmpProcessedTexture = nullptr;

// ヒストグラムのサイズ (ビン数)
#define HIST_SIZE 256

void HistKernel(index<2> idx, array_view<int, 1> av, const texture<unorm_4, 2>& input_tex, graphics::writeonly_texture_view<unorm_4, 2> output_tex_view) restrict(amp)
{
 // 入力画像のピクセル値を取得
 float_4 pixel = static_cast<float_4>(input_tex[idx].rgba);

 // RGB値をグレースケールに変更 (0.0 ~ 1.0)
 float Y = pixel.r * 0.2126f + pixel.r * 0.7152f + pixel.b * 0.0722f;

 // 配列のインデックスの算出
 int index = static_cast<int>(Y * (HIST_SIZE - 1));
// int index = 8;

 // アトミックに1加算
 atomic_fetch_add( &av[index], 1 );  // av[index] += 1;

 output_tex_view.set(idx, unorm_4(Y, Y, Y, pixel.a));
}


void RunImageProcessing(const texture<unorm_4, 2>& input_tex, graphics::writeonly_texture_view<unorm_4, 2> output_tex_view, std::vector<int> vHist, graphics::writeonly_texture_view<unorm_4, 2> hist_tex_view)
{
 array_view<int, 1> av(HIST_SIZE, vHist);
// array_view<int, 1> av(HIST_SIZE);  // ERROR
 av.discard_data();

    parallel_for_each(input_tex.accelerator_view, output_tex_view.extent, [=, &input_tex] (index<2> idx) restrict(amp) {
  HistKernel(idx, av, input_tex, output_tex_view);
 });

 av.synchronize();

 // ヒストグラムの最大値とインデックス
 int maxValue = 0;
 int maxIndex = 0;
 int totalPixel = 0;

 parallel_for(0, HIST_SIZE, [&maxValue, &maxIndex, &vHist, &av, &totalPixel](int i) {
  vHist[i] = av[i];
  totalPixel += av[i];
  if(maxValue < av[i]) {
   maxValue = av[i];
   maxIndex = i;
  }
 });

 std::wcout << "input texture width: " << input_tex.extent[1] << std::endl; 
 std::wcout << "input texture height: " << input_tex.extent[0] << std::endl; 
 std::wcout << "input texture pixel num: " << input_tex.extent[0] * input_tex.extent[1] << std::endl; 

 std::wcout << "histogram maxValue: " << maxValue << std::endl; 
 std::wcout << "histogram maxIndex: " << maxIndex << std::endl;
 std::wcout << "totalPixel: " << totalPixel << std::endl;


 // ヒストグラムのグラフ描画用
    parallel_for_each(hist_tex_view.extent, [=] (index<2> idx) restrict(amp) {
  
     const UINT x = idx[1];
  const UINT y = idx[0];

  // 背景色
  float c = 1.0f;

  // ヒストグラム値を 0.0 ~ 1.0 に変換
  float v = av[x] / float(maxValue);

  // Y座標の値を 0.0 ~ 1.0 に変換
  float fy = 1.0f - (y / float(HIST_SIZE - 1));

  // グラフを書く
  if( v > 0 ) {
   if(v >= fy) {
    c = 0.0f;
   }
  }
  hist_tex_view.set(idx, unorm_4(c, c, c, 1.0));
 });
}


// textureをjpeg形式で保存
void SaveImage(LPCWSTR fileName, texture<unorm4, 2>* pTex)
{
 HRESULT hr;

 ID3D11Texture2D* processedTexture = reinterpret_cast<ID3D11Texture2D*>(get_texture<unorm4, 2>(*pTex));

 // processedTextureをoutput_imageにキャプチャーする
 DirectX::ScratchImage output_image;

 hr = DirectX::CaptureTexture(g_pd3dDevice, g_pImmediateContext, reinterpret_cast<ID3D11Resource *>(processedTexture), output_image);
 assert( hr == S_OK );

 // キャプチャーした画像を保存する
 GUID containerFormat = GUID_ContainerFormatJpeg;
 DWORD flags = 0;
 const DirectX::Image* pImage = output_image.GetImages();
 size_t numImage = output_image.GetImageCount();

 hr = DirectX::SaveToWICFile(pImage, numImage, flags, containerFormat, fileName);
 assert( hr == S_OK );

 processedTexture->Release();
}


void TestAMP(_TCHAR* imgFilePath)
{
 HRESULT hr;

 // LoadFromWICFile 用にCOMを初期化
 hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);

 // DirectX11の初期化
    unsigned int createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
// createDeviceFlags |= D3D11_CREATE_DEVICE_SWITCH_TO_REF;
#endif

 D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_HARDWARE;
// D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_REFERENCE;
// D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_SOFTWARE;

    D3D_FEATURE_LEVEL FeatureLevel = D3D_FEATURE_LEVEL_11_0;
 hr = D3D11CreateDevice( nullptr, driverType, nullptr, createDeviceFlags,
    &FeatureLevel, 1, D3D11_SDK_VERSION, &g_pd3dDevice, nullptr, &g_pImmediateContext );
 assert( hr == S_OK );


 // 画像ファイルを読み込む
 DirectX::TexMetadata mdata;
 DirectX::ScratchImage image;

 hr = DirectX::LoadFromWICFile(imgFilePath, DirectX::DDS_FLAGS_NONE, &mdata, image);
 assert( hr == S_OK );

 // ID3D11Texture2Dを作成
 hr = DirectX::CreateTexture( g_pd3dDevice, image.GetImages(), image.GetImageCount(), mdata, reinterpret_cast<ID3D11Resource **>(&g_pInputTexture) );
 assert( hr == S_OK );


 // concurrency::accelerator_viewを作成 実行するとリソースが解放されない何か残っている?
 accelerator_view g_av = concurrency::direct3d::create_accelerator_view(reinterpret_cast<IUnknown*>(g_pd3dDevice));


 // 出力先の作成
 UINT img_width = mdata.width;
    UINT img_height = mdata.height;

    g_pAmpProcessedTexture = new texture<unorm4, 2>(static_cast<int>(img_height), static_cast<int>(img_width), 8U, g_av);

 // writeonly_texture_viewに書き込む
 writeonly_texture_view<unorm4, 2> output_tex_view(*g_pAmpProcessedTexture);

 // ヒストグラム用のテクスチャ
 texture<unorm4, 2>* pAmpHistTexture = new texture<unorm4, 2>(HIST_SIZE, HIST_SIZE, 8U, g_av);
 writeonly_texture_view<unorm4, 2> hist_tex_view(*pAmpHistTexture);


 // 入力データ
 // ID3D11Texture2D g_pInputTextureからConcurrency::graphics::textureを作成
 const texture<unorm4, 2> input_tex = make_texture<unorm4, 2>(g_av, reinterpret_cast<IUnknown*>(g_pInputTexture));

 std::vector<int> vHist_grayscale( HIST_SIZE );


 // 入出力データの用意ができたので、画像処理を行う
 RunImageProcessing(input_tex, output_tex_view, vHist_grayscale, hist_tex_view);


 // 処理結果を保存
 SaveImage(L"output.jpg", g_pAmpProcessedTexture);

 // ヒストグラムのtextureを保存
 SaveImage(L"histogram.jpg", pAmpHistTexture);

 // 解放
 if (g_pInputTexture) g_pInputTexture->Release();
 if (g_pAmpProcessedTexture) delete g_pAmpProcessedTexture;
 delete pAmpHistTexture;
 
 if (g_pImmediateContext) g_pImmediateContext->Release();
    if (g_pd3dDevice) g_pd3dDevice->Release();
 CoUninitialize();
}


int _tmain(int argc, _TCHAR* argv[])
{
 if(argc > 1) {
  TestAMP(argv[1]);
 }
 return 0;
}

2013年1月6日日曜日

C++ AMP 2 モザイク

C++ AMP の AMP は (Accelerated Massive Parallelism) の略
意訳すると、C++で加速される、ちょー大きい並列処理 のこと?
Massive が気になる

4GBのメモリを積んだウルトラハイエンドなグラボがあるらしい

あと、msdnのリファレンスの文章がおかしい、特に C++ AMP mad関数の説明なんかが変
「きちがい関数」www ではない x * y + z を返すだけ

モザイク処理のテスト
出力画像を拡大して確認 → 指定した領域にモザイクがかかっていた

何がどう動いているのかイメージしにくいので、
キーワード tile_static tiled_extent などを後で調査

ドメイン固有言語 なんだけど C++で書けることなのかなー

そういえば、タイルレンダリングのテストもしていた気がする
シェーダーとは、GPU 上で実行される、ドメインに固有の小容量プログラム なんですか?

main.cpp

#include <windows.h>
#include <wincodec.h>

#include <tchar.h>
#include <assert.h>

#include <d3d11.h>
#include <amp.h>
#include <amp_graphics.h>

#include <DirectXTex.h>

#pragma comment(lib, "d3d11.lib")

using namespace concurrency;
using namespace concurrency::graphics;
using namespace concurrency::direct3d;

#define DIMENSION   2

ID3D11Device*     g_pd3dDevice = nullptr;
ID3D11DeviceContext*  g_pImmediateContext = nullptr ;
ID3D11Texture2D*    g_pInputTexture = nullptr;

texture<unorm_4, 2>*  g_pAmpProcessedTexture = nullptr;

// タイルのサイズ
static const UINT sTileSize = 32;
//static const UINT sTileSize = 48; // compile ERROR

// 「C++ AMP でのタイリングの概要」で検索
// extentは範囲
tiled_extent<sTileSize, sTileSize> GetTiledExtent(const extent<DIMENSION>& ext)
{
  // タイルエクステント内の総数は1024以下ならOK
    tiled_extent<sTileSize, sTileSize> text(ext);
    return text.pad();
}

// カーネル関数
void ApplyEffectKernelFunc001(const texture<unorm_4, 2>& input_tex, writeonly_texture_view<unorm_4, 2> output_tex_view, tiled_index<sTileSize, sTileSize> idx) restrict(amp)
{
    // タイル毎の共有メモリ
    tile_static float_4 local_pixels[sTileSize][sTileSize];

    const UINT globalY = idx.global[0];
    const UINT globalX = idx.global[1];
    const UINT localY = idx.local[0];
    const UINT localX = idx.local[1];

  local_pixels[localY][localX] = static_cast<float_4>(input_tex[idx.global].rgba);
//  local_pixels[idx.local] = static_cast<float_4>(input_tex[idx.global].rgba);   // ERROR

  idx.barrier.wait();

  if((globalY >= 100 && globalY < 250) && (globalX > 500 && globalX < 720)) {
    // 特定の領域にモザイクをかける
    output_tex_view.set(idx.global, unorm_4(local_pixels[0][0].r, local_pixels[0][0].g, local_pixels[0][0].b, 1.0));
  }
  else
  {
    // 入力値をコピー
    float_4 pixel = static_cast<float_4>(input_tex[idx.global].rgba);
    output_tex_view.set(idx.global, unorm_4(pixel.r, pixel.g, pixel.b, 1.0));
  }
}


void ApplyEffect(const texture<unorm_4, DIMENSION> & input_tex,  writeonly_texture_view<unorm_4, DIMENSION>& output_tex_view)
{
  // タイルのエクステント
  tiled_extent<sTileSize, sTileSize> computeDomain = GetTiledExtent(input_tex.extent);

  // 並列計算を起動
  parallel_for_each(computeDomain, [=, &input_tex](tiled_index<sTileSize, sTileSize> idx) restrict(amp)
  {
    ApplyEffectKernelFunc001(input_tex, output_tex_view, idx);
  });
}


void TestAMP(_TCHAR* filePath)
{
  HRESULT hr;

  hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);

    unsigned int createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    // DirectX11の初期化
    D3D_FEATURE_LEVEL FeatureLevel = D3D_FEATURE_LEVEL_11_0;
  hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, &FeatureLevel, 1, D3D11_SDK_VERSION, &g_pd3dDevice, nullptr, &g_pImmediateContext );
  assert( hr == S_OK );

  // リソースが解放されない? 実行すると何かが残る
  accelerator_view g_av = create_accelerator_view(reinterpret_cast<IUnknown *>(g_pd3dDevice));

  std::wstring desc = g_av.get_accelerator().get_description();
  bool is_debug = g_av.get_is_debug();
  queuing_mode qmode = g_av.get_queuing_mode();


  // 画像ファイルを読み込む
  DirectX::TexMetadata mdata;
  DirectX::ScratchImage image;

  hr = DirectX::LoadFromWICFile(filePath, DirectX::DDS_FLAGS_NONE, &mdata, image);
  assert( hr == S_OK );

  // ID3D11Texture2Dを作成
  hr = DirectX::CreateTexture( g_pd3dDevice, image.GetImages(), image.GetImageCount(), mdata, reinterpret_cast<ID3D11Resource **>(&g_pInputTexture) );
  assert( hr == S_OK );


  // Concurrency::graphics::textureを作成
  UINT img_width = mdata.width;
    UINT img_height = mdata.height;
#if 1
  // OK
    g_pAmpProcessedTexture = new texture<unorm_4, DIMENSION>(static_cast<int>(img_height), static_cast<int>(img_width), 8U, g_av);
#else
  // NG ApplyEffect内でエラー発生する
//    g_pAmpProcessedTexture = new texture<unorm_4, DIMENSION>(static_cast<int>(img_height), static_cast<int>(img_width), 8U);
#endif

  // 画像データを加工する

  // 出力先
  writeonly_texture_view<unorm_4, DIMENSION> output_tex_view(*g_pAmpProcessedTexture);

  // 入力データ
  // ID3D11Texture2D g_pInputTextureからConcurrency::graphics::textureを作成
  const texture<unorm_4, DIMENSION> input_tex = graphics::direct3d::make_texture<unorm_4, DIMENSION>(g_av, reinterpret_cast<IUnknown *>(g_pInputTexture));

  // 加工処理
  ApplyEffect(input_tex, output_tex_view);

  // 加工結果を取り出す

  // Concurrency::graphics::texture の g_pAmpProcessedTexture から ID3D11Texture2Dを取得
    ID3D11Texture2D* processedTexture = reinterpret_cast<ID3D11Texture2D *>(graphics::direct3d::get_texture<unorm_4, DIMENSION>(*g_pAmpProcessedTexture));

  DirectX::ScratchImage output_image;

  // processedTextureをoutput_imageにキャプチャーする
  hr = DirectX::CaptureTexture(g_pd3dDevice, g_pImmediateContext, reinterpret_cast<ID3D11Resource *>(processedTexture), output_image);
  assert( hr == S_OK );

  // Jpeg形式で画像を保存
  GUID containerFormat = GUID_ContainerFormatJpeg;
  DWORD flags = 0;
  const DirectX::Image* pImage = output_image.GetImages();
  size_t numImage = output_image.GetImageCount();

  hr = DirectX::SaveToWICFile(pImage, numImage, flags, containerFormat, L"output.jpg");
  assert( hr == S_OK );

  // 解放
    processedTexture->Release();

    if (g_pInputTexture) g_pInputTexture->Release();
  if (g_pAmpProcessedTexture) delete g_pAmpProcessedTexture;

    if (g_pImmediateContext) g_pImmediateContext->Release();
    if (g_pd3dDevice) g_pd3dDevice->Release();
  CoUninitialize();
}

int _tmain(int argc, _TCHAR* argv[])
{
  if(argc > 1) {
    TestAMP(argv[1]);
  }
  return 0;
}

2013年1月3日木曜日

C++ AMP 1 グレースケール

C++ AMPで画像処理のテスト
VC 2012 Express版では不可?みたいなので、
テスト用のPCをWindows8 にUpgrade & Visual Studio 2012のPro版をInstall
ようやくテストすることができた。

AMPについて詳しくないので、
「C++ AMP の概要」 でググって読む
なんやらかんやらが、裏でいろいろ動いているはず

C++ AMPの本の付属サンプルを参考に作成したはず
http://ampbook.codeplex.com/

画像の読み込みにDirectXTex ライブラリを使用
ライブラリをDL & ビルドしてリンクに追加
http://directxtex.codeplex.com/

concurrency::indexクラス N次元のインデックス
concurrency::extentクラス N次元の範囲

RunImageProcessing()の parallel_for_each が肝 そういえばラムダ式

concurrency::accelerator_viewが怪しい
concurrency::direct3d::create_accelerator_view() で作成した
リソースが残っていたはず

8K解像度の画像でもサクサク処理できるかテスト中

namespaceを使わないとナガイ & < >が抜けていた?

main.cpp

#include <wincodec.h>

#include <iostream>
#include <tchar.h>
#include <assert.h>

#include <d3d11.h>
#include <DirectXTex.h>
#include <amp.h>
#include <amp_graphics.h>

#pragma comment(lib, "d3d11.lib")

// 今回は使用しない
//using namespace concurrency;
//using namespace concurrency::graphics;
//using namespace concurrency::direct3d;

ID3D11Device*       g_pd3dDevice = nullptr;
ID3D11DeviceContext*    g_pImmediateContext = nullptr ;

ID3D11Texture2D*      g_pInputTexture = nullptr;
Concurrency::graphics::texture<Concurrency::graphics::direct3d::unorm4, 2>*                 g_pAmpProcessedTexture = nullptr;

// 簡易タイマー
class Timer
{
  LARGE_INTEGER start, end;
public:
  void Start() {
    QueryPerformanceCounter(&start);
  }
  void Stop() {
    QueryPerformanceCounter(&end);
  }

  double ElapsedTime() {
    LARGE_INTEGER freq;
    QueryPerformanceFrequency(&freq);
    return (double(end.QuadPart) - double(start.QuadPart)) * 1000.0 / double(freq.QuadPart);
  }
};


// ↓今回のテスト対象 画像処理を行う
void RunImageProcessing(const Concurrency::graphics::texture<Concurrency::graphics::unorm_4, 2> & input_tex,  const Concurrency::graphics::writeonly_texture_view<Concurrency::graphics::unorm_4, 2> output_tex_view)
{
    parallel_for_each(input_tex.accelerator_view, output_tex_view.extent, [=, &input_tex] (Concurrency::index<2> idx) restrict(amp) {

    Concurrency::graphics::float_4 pixel = static_cast<Concurrency::graphics::float_4>(input_tex[idx].rgba);

    // RGB値をグレースケールに変更
    float Y = pixel.r * 0.2126f + pixel.r * 0.7152f + pixel.b * 0.0722f;

    output_tex_view.set(idx, Concurrency::graphics::unorm_4(Y, Y, Y, pixel.a));
  });
}

// AMPのテスト
// 画像ファイルを読み込んだ後に、グレースケールに変換して保存する
void TestAMP(_TCHAR* imgFilePath)
{
  Timer timer;
  HRESULT hr;

  // LoadFromWICFile 用にCOMを初期化
  hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);

  // DirectX11の初期化
    unsigned int createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    D3D_FEATURE_LEVEL FeatureLevel = D3D_FEATURE_LEVEL_11_0;
  hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags,
        &FeatureLevel, 1, D3D11_SDK_VERSION, &g_pd3dDevice, nullptr, &g_pImmediateContext );
  assert( hr == S_OK );


  // 画像ファイルを読み込む
  DirectX::TexMetadata mdata;
  DirectX::ScratchImage image;

  hr = DirectX::LoadFromWICFile(imgFilePath, DirectX::DDS_FLAGS_NONE, &mdata, image);
  assert( hr == S_OK );

  // ID3D11Texture2Dを作成
  hr = DirectX::CreateTexture( g_pd3dDevice, image.GetImages(), image.GetImageCount(), mdata, reinterpret_cast<ID3D11Resource **>(&g_pInputTexture) );
  assert( hr == S_OK );


  // concurrency::accelerator_viewを作成  実行するとリソースが解放されない何か残っている?
  concurrency::accelerator_view g_av = concurrency::direct3d::create_accelerator_view(reinterpret_cast<IUnknown *>(g_pd3dDevice));


  // 出力先の作成
  UINT img_width = mdata.width;
    UINT img_height = mdata.height;

    g_pAmpProcessedTexture = new Concurrency::graphics::texture<Concurrency::graphics::direct3d::unorm4, 2>(static_cast<int>(img_height), static_cast<int>(img_width), 8U, g_av);

  // writeonly_texture_viewに書き込む
  Concurrency::graphics::writeonly_texture_view<Concurrency::graphics::direct3d::unorm4, 2> output_tex_view(*g_pAmpProcessedTexture);

  // 入力データ
  // ID3D11Texture2D g_pInputTextureからConcurrency::graphics::textureを作成
  const Concurrency::graphics::texture<Concurrency::graphics::direct3d::unorm4, 2> input_tex = Concurrency::graphics::direct3d::make_texture<Concurrency::graphics::direct3d::unorm4, 2>(g_av, reinterpret_cast<IUnknown *>(g_pInputTexture));

  timer.Start();

  // 入出力データの用意ができたので、画像処理を行う
  RunImageProcessing(input_tex, output_tex_view);

  timer.Stop();

  std::wcout << "ElapsedTime: " << timer.ElapsedTime() << " (ms)" << std::endl;

  // 処理結果を取り出す

  // Concurrency::graphics::texture の g_pAmpProcessedTexture から ID3D11Texture2Dを取得
    ID3D11Texture2D* processedTexture = reinterpret_cast<ID3D11Texture2D *>(Concurrency::graphics::direct3d::get_texture<Concurrency::graphics::direct3d::unorm4, 2>(*g_pAmpProcessedTexture));

  // processedTextureをoutput_imageにキャプチャーする
  DirectX::ScratchImage output_image;

  hr = DirectX::CaptureTexture(g_pd3dDevice, g_pImmediateContext, reinterpret_cast<ID3D11Resource *>(processedTexture), output_image);
  assert( hr == S_OK );

  // キャプチャーした画像を保存する
  GUID containerFormat = GUID_ContainerFormatJpeg;  // Jpeg
  DWORD flags = 0;
  const DirectX::Image* pImage = output_image.GetImages();
  size_t numImage = output_image.GetImageCount();

  hr = DirectX::SaveToWICFile(pImage, numImage, flags, containerFormat, L"output.jpg");
  assert( hr == S_OK );

  // 解放
    processedTexture->Release();

  if (g_pInputTexture) g_pInputTexture->Release();
  if (g_pAmpProcessedTexture) delete g_pAmpProcessedTexture;
  
  if (g_pImmediateContext) g_pImmediateContext->Release();
  if (g_pd3dDevice) g_pd3dDevice->Release();
  CoUninitialize();
}

int _tmain(int argc, _TCHAR* argv[])
{
  if(argc > 1) {
    TestAMP(argv[1]);
  }
  return 0;
}