• D言語によるWebアプリの実装 その3

    2011年10月26日 19時54分
    前回の続き

    HTMLファイルを表示しましたが、見た目が残念なので
    CSSを当ててそれなりの見た目にしたいと思います。


    今回のソース


    CSSファイルの出力

    CSSファイルはアプリケーションでいじる必要はなく、
    単純にWebサーバが出力できるようにすればよいです。

    /usr/local/bbs/resources/stylesheets以下に、CSSファイルを配置します
    # mkdir -p /usr/local/bbs/resources/stylesheets
    # cp default.css /usr/local/bbs/resources/stylesheets/


    ApacheがCSSのディレクトリを参照できるようhttpd.confにAliasを設定します
    Alias /bbs/stylesheets /usr/local/bbs/resources/stylesheets


    さらにAliasはWebアプリを通したくないので、除外設定も追記します
    RewriteCond %{REQUEST_URI} !^/bbs/stylesheets.+$
    RewriteRule ^/bbs/.*$ /var/www/fcgi-bin/bbs.fcgi [L]



    テンプレートのパス記述

    あとはHTMLテンプレートにCSSの読み込みを書き込めば完了です。
    <link rel="stylesheet" type="text/css" href="/bbs/stylesheets/default.css" />



    完了なのですが、これだと内容が少ない上に、
    プログラム全然関係ないので少し機能を追加します。


    追加機能

    HTMLテンプレートに、「/bbs/stylesheets/default.css」と直接書いてしまうと、
    Webアプリの設置場所を他のURLに変更する時に書き直さなければなりません。

    例えば「/keijiban/」に配置したい時や、
    「/bbs1/」「/bbs2/」のように複数配置したい時などです。


    今はまだ変更が1か所だけなので、さほど手間でもないですが、
    修正箇所が増えてくると面倒になってきます。

    相対パスで書くという手もありますが、
    全てのテンプレートで書き方を統一した方がシンプルなので、
    絶対パスで書きたいです。


    そこで、テンプレートにはアプリ直下のURLのみを記述し、
    プログラムからパスを追加する形にしたいと思います。

    テンプレートに「/stylesheets/default.css」と書いておくと、
    プログラムが出力時に「/bbs/stylesheets/default.css」としてくれるようにします。


    テンプレートの記述

    テンプレートに処理を入れるには、対象のタグにs:idでIDを指定します。
    <link s:id="link" rel="stylesheet" type="text/css" href="/stylesheets/default.css" />


    また、今回は処理対象の属性名も指定してみようと思います。
    s:attributeでURLを変更する属性を指定します。
    <link s:id="link" s:attribute="href" rel="stylesheet" type="text/css" href="/stylesheets/default.css" />


    s:idはテンプレートエンジン標準属性ですが、
    s:attributeは今回の処理を記述するための専用属性です。


    プログラムの記述

    テンプレート1つに対してViewクラスを1つ作るようにプログラムを作成します。
    「ThreadList.html」テンプレートに対し、jp.ku6.bbs.view.ThreadListView.ThreadListViewクラスを作成しました。

    ただ、今回作成する機能は、全画面で使いたいので、
    共通クラスを作成し、それを継承する形にしました。

    共通クラスはjp.ku6.bbs.view.BaseView.BaseViewクラスです。


    テンプレートオブジェクトの生成とHTMLレスポンスの出力については前回同様です。
    ThreadListActionに書かれていた処理を持ってきました。

    追加されたのが次の部分です。
    1// s:id="link"が付いたタグに対する処理を割り当て
    2contents_.assign("link", &linkProcessor);


    この記述は、テンプレート出力時にs:id="link"が書かれたタグの情報を
    指定のdelegateに渡すものです。

    指定しているdelegateは次のメンバ関数です。
    1void linkProcessor(SweetsPrinter printer, SweetsElement element, const(SweetsNode[]) children)


    引数は次の通りです
    • printer → 処理結果データの出力先
    • element → s:idを付けられたタグそのもの
    • children → s:idを付けられたタグの内側のデータです。

    この関数内で、URLが変更されたlinkタグを出力します。


    まずはURLを変更する属性を調べます。
    対象の属性は、テンプレート内でs:attribute属性を使って指定しましたので、これを参照します。
    1const(string)* target = "s:attribute" in element.attributes;



    対象の属性名が取得出来たら、それを利用して同じように対象の属性値を取得します
    1const(string)* v = *target in element.attributes;



    属性値にアプリケーションのURLを追加し、新しい属性値として作成します。
    1string[string] attributes;
    2attributes[*target] = APPLICATION_ROOT_URI ~ *v;


    新しい属性値を追加してタグを表示します。
    1element.printStartTagWithAdditionalAttributes(printer, attributes, true);


    linkタグではありえませんが、子要素を持っていた場合のために、子要素も含めて出力する処理も書いておきます。
    1element.printStartTagWithAdditionalAttributes(printer, attributes, false);
    2foreach (child; children)
    3    child.print(printer);
    4element.printEndTag(printer);




    実行

    テンプレートファイルと実行ファイルを更新すれば、CSSが適用されます。



    次回へ続く
  • D言語によるWebアプリの実装 その2

    2011年10月25日 12時54分
    前回の続き

    前回はプログラムにHTMLを直接埋め込んでいましたが、
    記述量が増えるとあっさり破綻するので、ファイルを読み込んで出力する形にします。

    単純にstd.file.read等で読み出せばとりあえず事足りるのですが、
    HTMLテンプレートエンジンを作ってありますので、こちらを使います。
    後々テンプレートとして使用しますし。


    今回のソース


    初期化処理

    jp.ku6.bbs.action.ThreadListActionのコンストラクタに、
    ファイルパスを指定でファイルを読み込む処理を追加します。
    1// HTMLファイルからテンプレートオブジェクトを作成
    2auto tpl = FileAutoReloadableSweetsTemplate.instance(TEMPLATE_DIRECTORY ~ "/ThreadList.html");


    今回はテンプレートとして使わないのであまり関係ありませんが、
    内部的にはファイルを読み込み、テンプレートとして解析し、
    処理用のデータ構造を構築しています。

    TEMPLATE_DIRECTORYはconstants.dで定義しています。
    テンプレートファイル置き場のディレクトリです。



    取得したテンプレートデータはただのデータの塊なので、
    それを操作するオブジェクトに渡してあげます。
    1// テンプレートオブジェクトをテンプレート処理オブジェクトに渡す
    2sweets_ = new AutoUpdatableSweets(tpl);


    このオブジェクトを通してテンプレートに対する処理を設定します。


    レスポンス処理

    レスポンスを返す処理は、前回HTMLを出力していた処理を、
    読み込んだファイルを出力するよう変更するだけです。
    1// テンプレートをそのまま出力
    2sweets_.print(&response.append);



    若干書き方が見慣れないと思いますので補足します。

    Sweets.printの定義は次のようになっています。
    1alias void delegate(const(char)[]) SweetsPrinter;
    2void print(SweetsPrinter printer);


    const(char)[]を受け取るdelegateを渡し、
    そのdelegateに対して出力を行うようになっています。

    Sweets.printがstringを返さないのは、
    確保済みのメモリ領域に対して出力出来るようにしたかった為です。



    実行

    テンプレートファイルを読み込みますので、まずはこれを配置します。

    TEMPLATE_DIRECTORYで定義されているように、
    /usr/local/bbs/templatesの下に、ThreadList.htmlを配置します。


    実行ファイルは前回同様dmdでビルドし、配置中のものを上書きします。


    ブラウザでアクセスすると、ThreadList.htmlの内容が表示されます。


    次回へ続く
  • D言語によるWebアプリの実装 その1

    2011年10月25日 00時20分
    最近あまりプログラムを書いていないのでネタがありません。
    とはいえ、何日も更新が無いというのはなんとかしたいものです。


    そこで、DによるWebアプリ実装についての記事を
    細かく刻んで小出しし、乗り切ってみたいと思います。

    ちなみに何故Webアプリかと言うと、
    何回か実装していてわりと内容がまとまっているからです。
    (実はこのブログもDによるWebアプリの実装になっています。)


    この記事ではシンプルな掲示板の作成を目指したいと思います。



    一口にWebアプリといっても色々と実装方法がありますが、
    今回はFastCGIを使用して実装します。
    それと、FastCGI用の自家製フレームワークがありますので併せて使用します。


    FastCGIを選んだのは、せっかくDで書くなら実行速度が速いといいなぁという理由です。


    環境構築

    FastCGIを使いますので、次の2つが必要になります。
    1. FastCGIライブラリ
    2. FastCGI実行環境

    また、Dを使いますのでdmdも必要です。


    環境構築方法は話の本筋ではありませんが、
    「Hello work!!」出すだけだと内容が薄いので、
    CentOS 6.0(x64)上に構築する場合を例として簡単に書いておきます。

    開発環境として、手間がかからず動けば良いという観点の方法ですので、
    実際に構築する場合は色々調整してください。


    FastCGIライブラリのインストール

    CentOS 6.0のMinimalインストールは本当にMinimalだったので
    ビルドツール等を色々入れます。
    # yum -y install gcc
    # yum -y install make
    # yum -y install patch
    # yum -y install wget


    FastCGIサイトからソースをダウンロードして解凍します。
    # wget http://www.fastcgi.com/dist/fcgi.tar.gz
    # tar xzvf fcgi-2.4.0.tar.gz


    GCCのバージョンアップのためかと思いますが、
    EOFマクロがエラーを出すようなので、パッチを当てます。
    (stdio.hのincludeを足すだけです。)
    # cd fcgi-2.4.0/libfcgi/
    # patch -p0 < ../../fcgi-2.4.0-gcc4.patch


    パッチの内容は次の通りです。
    --- fcgio_old.cpp   2002-02-25 05:12:22.000000000 +0900
    +++ fcgio.cpp   2011-10-21 16:26:18.039072131 +0900
    @@ -22,6 +22,7 @@
     #define DLLAPI  __declspec(dllexport)
     #endif
    
    +#include <stdio.h>
     #include <limits.h>
     #include "fcgio.h"


    後はビルドしてインストールします
    # cd ..
    # ./configure
    # make
    # make check
    # make install



    FastCGI実行環境のインストール

    WebサーバはApacheを使います
    # yum -y install httpd


    mod_fcgidのビルドのためにApache開発環境をインストール
    # yum -y install httpd-devel


    mod_fcgidをダウンロードして解凍
    # wget http://ftp.jaist.ac.jp/pub/apache//httpd/mod_fcgid/mod_fcgid-2.3.6.tar.gz
    # tar xzvf mod_fcgid-2.3.6.tar.gz


    mod_fcgidをビルドしてインストール
    # cd mod_fcgid-2.3.6
    # ./configure.apxs
    # make
    # make install



    dmdのインストール

    unzipも無ければ入れないといけません。
    # yum -y install unzip


    dmdのダウンロードと解凍と配置
    # wget http://ftp.digitalmars.com/dmd.2.055.zip
    # unzip dmd.2.055.zip
    # mv dmd2 /usr/local/


    dmdにシンボリックリンクを張る
    # cd /usr/local/bin/
    # ln -s /usr/local/dmd2/linux/bin64/dmd


    設定ファイルを設置
    # cp /usr/local/dmd2/linux/bin64/dmd.conf /etc/


    設定ファイルはフルパスで書く派です
    [Environment]
    
    DFLAGS=-I/usr/local/dmd2/src/phobos -I/usr/local/dmd2/src/druntime/import -L-L/usr/local/dmd2/linux/lib64 -L-L/usr/local/dmd2/linux/lib32 -L--no-warn-search-mismatch -L--export-dynamic -L-lrt



    アプリケーションのビルド

    アプリケーションのソースをダウンロードし、dmdでビルドします
    # dmd @switches


    出来た実行ファイルを設置します
    # mkdir /var/www/fcgi-bin/
    # mv bbs.fcgi /var/www/fcgi-bin/



    Apacheの設定

    mod_fcgidの設定を追加します。
    FcgidProcessTableFile /var/run/fcgid_shm
    FcgidIPCDir /tmp/fcgid_sock/
    FcgidMaxProcessesPerClass 1

    その他オプションについてはmod_fcgid公式を参照してください。


    fcgiファイルを置くディレクトリの設定を追加します。
    <Directory "/var/www/fcgi-bin/">
        AllowOverride None
        Options +ExecCGI
        Order allow,deny
        Allow from all
        SetHandler fcgid-script
    </Directory>



    /bbs/以下のアクセスが作成したfcgiファイルに行くよう設定します。
    RewriteEngine on
    RewriteRule ^/bbs/.*$ /var/www/fcgi-bin/bbs.fcgi [L]




    Apacheの起動

    mod_fcgidはSELinuxに弾かれるようなのでSELinuxは切ります。
    # echo 0 > /selinux/enforce


    また、CentOSのデフォルトiptablesは80ポートが塞がっているのでとりあえずクリアします。
    # iptables -F


    そうしましたらApacheを起動します。
    # service httpd start



    動作確認

    該当URLにアクセスします。
    http://hostname/bbs/ThreadList.html



    Hello work!!と表示されれば成功です。


    ソースについて

    modules以下にライブラリが入っていますが、これの内容ついては細かく触れません。使うだけです。
    アプリケーションのソースはsources以下にあります。


    エントリポイントはjp.ku6.bbs.mainモジュールにあります。
    記述量はそれほど無く、コメントも入れているのでなんとなく内容はわかるかと思います。
    1void main(string[] arguments)
    2{
    3    // フレームワークオブジェクトの生成
    4    auto raptan = new Raptan(8192, 8192, "/tmp");
    5
    6    // 拡張子を判断して自動でContent-Typeを出力する設定
    7    auto contentTypeFilter = new ContentTypeFilter;
    8    contentTypeFilter.add("html""text/html; charset=UTF-8");
    9    raptan.addFilter(contentTypeFilter);
    10
    11    // リクエストメソッドフィルタリング設定
    12    raptan.addFilter(new RequestMethodFilter(Request.Method.GET | Request.Method.POST));
    13
    14    // パスに対して処理を設定
    15    auto threadListAction = new ThreadListAction;
    16    raptan.setAction(APPLICATION_ROOT_URI ~ "/ThreadList.html", &threadListAction.execute);
    17
    18    // リクエストの受付を開始
    19    raptan.start();
    20
    21    // アプリケーションの終了を待機
    22    raptan.join();
    23}



    ポイントとしては、jp.ku6.raptan.core.RaptanがFastCGI用のフレームワークになっています。

    RaptanオブジェクトにURLのパスと処理内容のdelegateをセットで登録すると、
    そのURLにアクセスしたときに対応するdelegateが呼び出されます。
    1// パスに対して処理を設定
    2auto threadListAction = new ThreadListAction;
    3raptan.setAction(APPLICATION_ROOT_URI ~ "/ThreadList.html", &threadListAction.execute);


    セットしている処理内容はjp.ku6.bbs.action.ThreadListActionにあります。
    HTTP Responseオブジェクトに対してHTMLデータを出力しています。
    1string execute(Request request, Response response)
    2{
    3    // まずはHTMLテキストを出力するだけ
    4    response.append("<html><head><title>Hello work!!</title><body>Hello work!!</body></html>");
    5    return null;
    6}



    次回へ続く。
  • Direct3D 11のDynamic Shader Linkageを試す

    2011年08月25日 20時53分
    Direct3D 11で~とかしれっと書き続けて来ましたが、
    11の機能はほとんど使ってなくてインチキくさいので今回は11の機能についてです。


    プログラマブルシェーダは自由度が高く色々なことが出来ますので、
    処理がどんどん増えていきます。

    縦にコードが長くなるのはそれほど困らないのですが、
    場合分けで横に増えていくと困りものです。

    例えば、ポリゴンをそのまま表示する場合とテクスチャを張る場合。
    ライトを当てる場合と当てない場合。
    これだけでもう2 × 2 = 4のシェーダが必要になります。

    他にも描画要素はたくさんあるので、
    掛け算でシェーダ数が増えて行ってとんでもない数になります。



    「100万パワー+100万パワーで200万パワー!」
    「いつもの2倍のジャンプが加わって200×2の400万パワー!」
    「そしていつもの3倍の回転を加えれば400×3の・・・!
    バッファローマン!お前を上回る1200万パワーだーーーーっ!」




    みたいなもんです。

    if文を使った条件分岐でそれなりに対応は出来そうですが、難しい時もあるでしょう。

    そういった状況のためにDirect3D 11からはDynamic Shader Linkageが追加されました。
    OOPでおなじみのinterfaceとclassの概念をHLSLで使うことが出来ます。



    先ほども出た、ポリゴンそのままとテクスチャ有りの場合分けを例にします。


    今回のソース

    HSLS側の準備

    どちらの場合でも色データがとれればいいので、interfaceは次のように定義しました。
    1interface BaseColor
    2{
    3    float4    GetColor(float2 uv);
    4};


    このinterfaceに対するclassとして次のように実装しました。
    1class RawPolygonColor : BaseColor
    2{
    3    float4    GetColor(float2 uv)
    4    {
    5        // 固定で白を返す
    6        return    float4(1, 1, 1, alpha);
    7    }
    8};
    9
    10class TexturedColor : BaseColor
    11{
    12    float4    GetColor(float2 uv)
    13    {
    14        // 与えられたuv座標でテクスチャの色を返す
    15        float4 color = tex.Sample(wrapSampler, uv);
    16        color.a *- alpha;
    17        return color;
    18    }
    19};


    interfaceのインスタンス変数も用意します。
    1BaseColor baseColor;


    インスタンス変数はこのように使えます。
    1float4 oc = baseColor.GetColor(input.uv);


    Direct3D側の準備


    Direct3Dから、HLSLのインスタンス変数に実装クラスのインスタンスをセットします。


    ID3D11Device::CreateClassLinkageを使って、HLSL上interfaceのDirect3Dインターフェース(ややこしい)を作成します。
    1device_.CreateClassLinkage(&pixelShaderClassLinkage_)



    作成したインターフェースはID3D11Device::CreateXXXShaderに渡して関連付けます。
    1device_.CreatePixelShader(compiled.GetBufferPointer(), compiled.GetBufferSize(), pixelShaderClassLinkage_, &pixelShader_)


    この後、リフレクションを使って、interfaceが何番目のスロットに対応するかチェックするのですが、
    今回はinterfaceのインスタンスが1個のため確実に0番になるので省略します。


    classのインスタンスはID3D11ClassLinkage::CreateClassInstanceを使って、HLSL上のクラス名から取得します。
    1pixelShaderClassLinkage_.CreateClassInstance("RawPolygonColor", 0, 0, 0, 0, &pixelShaderRawPolygonColorClass_)
    2pixelShaderClassLinkage_.CreateClassInstance("TexturedColor", 0, 0, 0, 0, &pixelShaderTexturedColorClass_)

    (class内にメンバ変数がある場合は、GetClassInstanceを使います)



    あとは表示条件に従ってinterfaceのインスタンス変数にclassインスタンスのどちらかをセットして使います。
    1immediateContext_.PSSetShader(pixelShader_, &pixelShaderRawPolygonColorClass_, 1);
  • Direct3D 11でトゥーンレンダリング

    2011年07月21日 23時51分
    MMDといえばトゥーンレンダリングだろうと思いますので実装します。

    例によって本家と全く同じ処理というわけではありませんのでご注意ください。


    MMDはトゥーン用テクスチャを10個持っています。
    そして、モデルデータにはそのインデックスが入っています。

    PMDファイルの内部構造についてはこちらのサイトが詳しいです。
    MMDのモデルデータ(PMD)形式 めも4 - 通りすがりの記憶

    拡張仕様でデフォルトのテクスチャ以外も使えるようですが、
    読み込み位置が遠いので今回は対応しません。


    よって、テクスチャを10個読み込んで、
    インデックスデータを読み込めば下準備完了です。



    トゥーンレンダリング自体も、単純な仕様を選べば難しいものではありません。

    今回はディレクショナルライトと、法線ベクトルの内積を取り、
    180度(光が強く当たる) ~ 90度以下(光が当たらない)の値を取ります。(-1 ~ 1の値になります)

    この値を元に、テクスチャ座標の0(光が強く当たる) ~ 1(光が当たらない)として使います。

    通常の拡散反射の計算ですと角度によってなめらかに光の強さが変わりますが、
    テクスチャから色を取る事によって段階的に光の強さが変わります。


    こんな感じのシェーダを書きました
    1float force = 1;
    2if (minDepth >= input.lpos.z) // 深度バッファシャドウの判定
    3{
    4    // 法線と光線の角度を内積として取得する
    5    force = dot(normalize(input.normal), normalize(directionalLightDirection));
    6
    7    // テクスチャ座標に変換する
    8    force = saturate(force + 1);
    9}
    10
    11// 与えられた値を元にtoonテクスチャから色を拾い、拡散反射光として扱う
    12float3 diffuse = toonTex.Sample(clampSampler, float2(0, force));



    実行するとこうなります。顔の影などが、ある点から表示されているのがわかります。
    実行結果