2013年2月25日月曜日

variadic templateによるvertex型

variadic templateを利用した3DCGとかで三次元形状データを扱う時によく使う頂点属性よしなに扱う為のvertex型を書いてみた。

vertex型の要件: 
  • vertex型はライブラリーで用意されそのユーザーに提供される
  • ユーザー向けの都合
    • ユーザーがカスタマイズ可能な頂点構造柔軟に定義できる
      • 例: {position:float[4]}とか{position:float[2],color:uint8[4]}とか、位置、色、法線、etc.
      • 要素となる型は基本型かそのRAW配列互換を詰めたメモリー構造
    • コンストラクターで全ての要素にまとめて値を放り込める
    • ユーザーが定義した任意の要素型の順番(≈インデックス)で
      • 要素の参照を取得できる
  • ライブラリー向けの都合
    • それ自体がいわゆるPOD型でそのRAW配列はメモリー上に整然と値が並ぶ
    • ユーザーが定義した任意の要素型の順番(≈インデックス)で
      • 要素型を翻訳時に容易に取り出せる
      • バイトオフセットを翻訳時に容易に取り出せる
今回は考え無い事にした要素:
  • アライメント自動最適化
  • Boost.MPL
  • ユーザー定義型におけるアクセス指定子によるブロックの整理
    (これをやると後方参照が利かない機能周りで面倒なので)
それでこんなんできました(執筆同日21:40頃修正:fix1-1,fix1-2)

#include <cstddef>
#include <cstdint>
#include <memory>

template<class T, class ... TS>
struct vertex final
{
  static constexpr size_t elements = 1 + sizeof...(TS);
  // fix1-1: remove this line
  // static constexpr size_t size = sizeof(T) + vertex<TS...>::size;
private:
  template<size_t N, class T_, class ... TS_>
  struct offset_of_
  {
    using type = typename offset_of_<N - 1, TS_ ...>::type;
    static constexpr size_t add_offset = sizeof(T_) + offset_of_<N - 1, TS_ ...>::add_offset;
  };

  template<class T_, class ... TS_>
  struct offset_of_<0, T_, TS_ ...>
  {
    using type = T_;
    static constexpr size_t add_offset = 0;
  };
public:
  template<size_t N>
  struct offset_of
  {
    using type = typename offset_of_<N, T, TS ...>::type;
    static constexpr size_t value = offset_of_<N, T, TS ...>::add_offset;
  };

  template<size_t N>
  typename offset_of<N>::type & element_of()
  { return *reinterpret_cast<typename offset_of<N>::type*>(&data_[offset_of<N>::value]); }
  
  template<size_t N>
  void element_of(typename offset_of<N>::type&& a)
  { *reinterpret_cast<typename offset_of<N>::type*>(&data_[offset_of<N>::value]) = std::move(a); }
  
  template<size_t N>
  void element_of(const typename offset_of<N>::type& a)
  { *reinterpret_cast<typename offset_of<N>::type*>(&data_[offset_of<N>::value]) = a; }

  // fix1-2: add four line
  static constexpr size_t size
    = offset_of<sizeof...(TS)>::value
    + sizeof(typename offset_of<sizeof...(TS)>::type)
    ;
  
private:
  std::array<uint8_t, size> data_;

public:
  decltype(data_.data())   data()   const { return data_.data(); }
  decltype(data_.begin())  begin()        { return data_.begin(); }
  decltype(data_.end())    end()          { return data_.end(); }
  decltype(data_.cbegin()) cbegin() const { return data_.cbegin(); }
  decltype(data_.cend())   cend()   const { return data_.cend(); }
  
  vertex(){ }
  vertex(const T& v0, const TS& ... vs) { initialize<0, T, TS...>(v0, vs...); }
  
private:
  template<size_t N> void initialize() const { }
  
  template<size_t N, class T_, class ... TS_>
  void initialize(const T_& v, const TS_& ... vs)
  {
    element_of<N>(std::move(v));
    if(sizeof...(TS_))
      initialize<N + 1, TS_...>(vs...);
  }
};

template<class T> struct vertex<T> final
{
  static constexpr size_t elements = 1;
  static constexpr size_t size = sizeof(int);
  vertex() { }
  vertex(const T& v0) : data_(v0) { }
  const uint8_t* const data()   const { return reinterpret_cast<uint8_t*>(&data_); }
  const uint8_t*       begin()        { return data_.data(); }
  const uint8_t*       end()          { return data_.data() + 1; }
  const uint8_t* const cbegin() const { return data_.begin(); }
  const uint8_t*       cend()   const { return data_.end(); }
private:
  T data_;
};


思ったほどカオスにならず、面倒くさいコード量も爆発せずにできた。あってよかったC++11のvariadic template(*´ω`*)

言うまでもなく?Direct3DやOpenGLなどのGPUを使う為のAPIにデータを投げ付ける時に使う用。libWRP-GLEWでもほぼこんな実装を入れる予定。

テスト用のサンプル:

#include <iostream>
#include <iomanip>
#include <array>
#include <typeinfo>
#include <cxxabi.h>
#define DEMANGLE(a) abi::__cxa_demangle(a,0,0,0)

int main()
{
  using namespace std;
  using t = vertex<array<float,2>, array<uint8_t,4>, array<float,2>, double, array<char, 8>>;
  
  cout <<
       "# of elements : " << t::elements << "\n"
       "size in bytes : " << t::size << "\n"
       "offset_of<0>  : " << t::offset_of<0>::value << "\n"
       "offset_of<1>  : " << t::offset_of<1>::value << "\n"
       "offset_of<2>  : " << t::offset_of<2>::value << "\n"
       "offset_of<3>  : " << t::offset_of<3>::value << "\n"
       "offset_of<4>  : " << t::offset_of<4>::value << "\n"
    << endl;
  
  cout
    << DEMANGLE( typeid(t::offset_of<0>::type).name() ) << "\n"
    << DEMANGLE( typeid(t::offset_of<1>::type).name() ) << "\n"
    << DEMANGLE( typeid(t::offset_of<2>::type).name() ) << "\n"
    << DEMANGLE( typeid(t::offset_of<3>::type).name() ) << "\n"
    << DEMANGLE( typeid(t::offset_of<4>::type).name() ) << "\n"
    << endl;

  t x;
  x.element_of<0>( {{ 3.1, 4.1 }} );
  x.element_of<1>( {{ 59, 26, 53, 58 }} );
  x.element_of<2>( {{ 9.7, 9. }} );
  x.element_of<3>( 3.2384 );
  x.element_of<4>( {{ '6','2','6','4','3','3','8','3'}} );

  {
    cout << "x data : 0x" << setfill('0')<< setw(2) << hex;
    for(auto v : x)
      cout << setfill('0') << setw(2) << int(v);
    cout << endl;
  }

  t y
  ( {{1.1,2.2}}
  , {{3,4,5,6}}
  , {{7.7,8.8}}
  , 9.9
  , {{'a','b','c','d','e','f','g','h'}}
  );
  
  {
    cout << "y data : 0x" << hex;
    for(auto v : y)
      cout << setfill('0') << setw(2) << int(v);
    cout << endl;
  }

  cout << std::endl;

  cout
    <<     y.element_of<0>()[0]  << "\n"
    <<     y.element_of<0>()[1]  << "\n"
    << int(y.element_of<1>()[0]) << "\n"
    << int(y.element_of<1>()[1]) << "\n"
    << int(y.element_of<1>()[2]) << "\n"
    << int(y.element_of<1>()[3]) << "\n"
    <<     y.element_of<2>()[0]  << "\n"
    <<     y.element_of<2>()[1]  << "\n"
    <<     y.element_of<3>()     << "\n"
    << std::string(y.element_of<4>().begin(), y.element_of<4>().end()) << endl;

}

実行結果(GCC-4.7.2/Linux Mint14):

# of elements : 5
size in bytes : 36
offset_of<0>  : 0
offset_of<1>  : 8
offset_of<2>  : 12
offset_of<3>  : 20
offset_of<4>  : 28

std::array<float, 2ul>
std::array<unsigned char, 4ul>
std::array<float, 2ul>
double
std::array<char, 8ul>

x data : 0x66664640333383403b1a353a33331b410000104163ee5a423ee809403632363433333833
y data : 0xcdcc8c3fcdcc0c40030405066666f640cdcc0c41cdcccccccccc23406162636465666768

1.1
2.2
3
4
5
6
7.7
8.8
9.9
abcdefgh

追記 執筆同日21:40頃の修正 fix1-2,fix1-2について:

using V = vertex<T1,T2,T3>とかしてある際のV::size正しいサイズを返していないバグに気付き修正しました。vertexの使用想定としてT1等の型はstd::arrayを前提としています。

fix前の状態ではT3のバイトサイズを正常に取得できずT1+T2+4byteがsizeの定数値として翻訳され、実際に必要な領域よりも小さな領域しか確保せず最終要素への書き込み操作によりバッファーオーバーフローが発生する可能性がありました。fix1-1,fix1-2によりこのバグは修正されています。

このvertex型は自身の定義時(もちろん翻訳時)にconstexprとvariadic templateによりsizeを求め、そのsizeの分だけ自信にuint8_t配列のバッファー領域を確保しています。事後であれば外部からもsizeof(V)でバイトサイズは取得できますし、テンプレートクラスの翻訳より後で使う部分ならsizeof(*this)でも同様に取れますが、テンプレート解決時に定数式で必要なバイトサイズをvariadic template要素を基に確定する必要があるのでsizeなんてものがstatic constexprで定義してあります。

0 件のコメント:

コメントを投稿