2019年2月22日金曜日

プログラムの設計をする際に考えていること

ふと思うところがあり,自分がプログラムを設計する際に考えていることを言葉にしてみようと思う.

まず,最初に考えるのは,最終的に用意したいデータは何なのか,ということである.

例えば,最終的に予算の合計が知りたい,だったり,最終的に画像ファイルが生成されていて欲しい,だったり,何らかのキーワードの有無が知りたい,であったりだ.

これがはっきりしていない場合,まずはこれをはっきりさせるために,情報収集を行う.例えば,発注者に話を聞いたり,ゲームの場合,仮組みをして動きや見た目を提示して,想定と合っているかの確認を行う.

次に,手元にあるデータから,最終的に必要なデータまでの加工手順を考える.ここで手順が思いつかない場合,最終的なデータを直接生成できるデータを考える.そのデータを生成できるデータを考えて,と逆算を行っていく.どうしても辿り着けない場合,何らかの情報が欠けていると思われるので,欠けている情報を明確にする.

例えば,画像を良い感じに加工して,と言われても,その良い感じがなんなのか分からないので,熱い感じなのか,寒い感じなのか,明るい感じなのか,暗い感じなのか,といったことを追加で入力してもらう必要があるかもしれない.
(そもそもそんな指示を出すのはどうかという話は置いておく)

また,今後追加で拡張などをして欲しい,といった要望があることも考えて,どういった拡張が考えられるだろう,といったことも考えておく.

ここまで来て,ようやくプログラムの設計に入るのだけれど,ここでいきなりクラスやアーキテクチャなどの設計に入るわけではない.

まずは,途中で必要となる要素の実験を行う.例えば,ファイルが作られた日が必要だとして,対象としている環境すべてで取得可能か確認を行う.もし不可能な環境があれば,他の方法を検討する必要がある.

他にも,想定している機能拡張がある場合にやりやすい方法は何だろう,と考える.なるべくシンプルな機能に分解しておいて,それらを組み合わせて実装するようにしておくと,後々組み合わせるだけで済み,追加部分だけのテストで良いこともある.

いくつかの実験を行い,一通りのパーツが揃ったら,ようやくクラスやアーキテクチャの設計に入る.

この段階で考えるのは,用意したプログラムを他のプログラマが利用するかどうか,ということ.

利用しない場合,ぶっちゃけ多少適当に作ったとしても,他に影響は無いので,サクッと作って素早く提供できる方が良い.

問題になるのは,他のプログラマが利用する場合だ.

下手にgetterやsetterを作ろうものなら,思わぬ使い方をされて,下手に修正できなくなる.

他のプログラマがどういう風に利用するかを考え,想定する使い方をする場合は使いやすく,想定しない使い方をしようとすると,途端に面倒になるような形が望ましい.

これは,ある程度利用するプログラミング言語に関する知識が必要となる.

例えば,C++であれば,あるクラスのオブジェクトはコピーできないようにしておく,getterは値のコピーを返し,オブジェクトが持っている値そのものは,簡単には変更できないようにする,などが考えられる.

C++の場合,アドレスが分かれば無理矢理書き換えもできるが,そこまでやる相手のことを考え始めると,実装速度や処理速度が犠牲になるのでやらない.

他にも,インターフェースである関数名や仮引数名を分かりやすいものにする,などで利用するプログラマが勘違いしたりして変な使い方をしないようにする.

例えば,音量を指定する機能を実装する場合に,それを0から1の浮動小数点で指定するのか,0から100のパーセンテージを表す整数で指定するのか,はたまた実際にスピーカーに渡せる最大値と0の範囲で指定するのか,と色々考えられるが,これがvolume,としか書いていなければ,ドキュメントを信じるか,実装を読み解くしか無い.

ちなみに,同一システム内に上記が混在しているのを見たことがある.

また,統一感,というのも重要になる.例えば,セーブロードの機能が,全体的にsaveとloadという名前で実装されているところに,storeとrestoreを導入する,というのは統一感が取れていない.

統一感を出すには,実装しているシステム中で一般的に使われている単語の用法から外れないように単語を選択していく必要がある.

この辺は,驚きを最小にする,ということを意識するようにしている.

また,実装しているシステムだけでなく,利用しているAPIやプログラミング言語の公式ドキュメント,世に溢れるオープンソースから類似の実装を読み,よく使われている名前を検討する.

よく使われている名前を使っておくと,検索する際にも似たような情報を拾いやすくなる.また,意味の取り違えも減る.

ここまでやって,ようやく多少は間違った使い方をされにくい設計ができる.

後は,考えたことをコードに落とし込んでいくだけである.

書いてみたけど,若干とりとめが無い,ポエムになってしまった感があるなぁ・・・・・・

2017年10月3日火曜日

Direct3D 11の学習 twitterまとめ









2016年12月13日火曜日

Chrome上でJavaScriptからSIMDを利用する方法

さすがstackoverflow.

How can I try out SIMD instructions in Chrome?

ChromeでSIMDを利用するには,ショートカットなどのChrome.exeに--js-flags="--harmony-simd"(ハイフン ハイフン js ハイフン flags = " ハイフン ハイフン harmony ハイフン simd")を指定すれば良いとのこと.

試してみたら,確かにSIMDというオブジェクトが増えていました.

2016年9月1日木曜日

Windows 10で起動したApache HTTPdサーバとPHPを連携させる

PHPのダウンロード


PHPのサイトからPHPをダウンロードします.トップのDownloadをクリックします.



Windows downloadsをクリックします.



Apache HTTPdサーバが32bit版ならx86を,64bit版ならx64のスレッドセーフまたは非スレッドセーフなものを選び,Zipをダウンロードします.ここでは,PHP 7.0.10のx64スレッドセーフ版をダウンロードしています.



ダウンロードしたZipの中身は,環境変数USERPROFILEで取得できるフォルダにphpという名前で解凍したものとします.

php.iniの用意

解凍したphpフォルダに,php.ini-developmentというファイルがあるので,php.iniにファイル名を変更しておきます.

Apache HTTPdの設定ファイル編集


Apache HTTPdとPHPを連携させるために,httpd/conf/httpd.confを編集します.

まず,次のようにPHPを読み込むようにLoadModuleを追加します.
LoadModule php7_module "${USERPROFILE}/php/php7apache2_4.dll"

次に,PHPファイルがphpとして処理されるように設定します.
<IfModule php7_module>
PHPIniDir "${USERPROFILE}/php"
<FilesMatch "\.php">
SetHandler application/x-httpd-php
</FilesMatch>
</IfModule>

最後に,URLとしてフォルダが指定された場合に表示するファイルとして,phpのファイルも使えるようにします.
DirectoryIndex index.html index.php

動作確認


httpd/htdocsフォルダにあるindex.htmlを消して,次の内容のindex.phpを用意します.

<?php phpinfo();?>

これで,localhostにアクセスした際に,PHPの設定が確認できれば成功です.
上手くいかない場合,F5を押すなどして再読込をしてみると,キャッシュが消えて上手くいく場合があります.

2016年8月31日水曜日

Windows 10でApache HTTPdサーバーを実行する

事前条件

Visual Studio 2015 のVisual C++ 再頒布可能パッケージをインストールしておく必要があります.
再頒布可能パッケージは,Microsoftのダウンロードセンターからダウンロードできます.

Apache HTTPdサーバのダウンロード


Apache HTTPdサーバのページの左メニューから,Download!のFrom A Mirrorを選び,Files for Microsoft Windowsを選びます.


Apache HTTPdサーバのWindows向けバイナリを配布しているサイトがいくつかありますが,ここではApache Loungeを選択します.


Apache HTTPdサーバの本体をダウンロードします.この記事を書いた時点での最新版は,2.4.23です.ここでは,64bit版をダウンロードしていますが,32bit版でも手順は変わりません.
ただし,PHPなどと連携する場合,HTTPdサーバが32bit版ならPHPも32bit版,といったように同じbit数のものを選ぶ必要があります.


ダウンロードしたら,解凍し,中にあるApache24というフォルダを取り出します.ここでは,ユーザーのホームフォルダ(環境変数USERPROFILEで指定されているフォルダ)にhttpdという名前で取り出したこととします.



httpdフォルダの中にconfというフォルダがあり,その中にhttpd.confという設定ファイルがあります.Apache HTTPdサーバを起動するには,最低限,以下の項目を設定する必要があります.

  • ServerRoot
    • "${USERPROFILE}/httpd"に書き換えます.
  • DocumentRoot
    • "${USERPROFILE}/httpd/htdocs"に書き換えます.
    • 直下のDirectoryの部分も同様に書き換えます.
  • ServerName
    • 先頭の#を消してコメントを解除します.
    • localhost:80に書き換えます.
これで準備ができたので,Apache HTTPdサーバを起動します.httpdフォルダの中に,binというフォルダがあり,その中にhttpd.exeがあるのでクリックして起動します.

http://localhostにアクセスして,It Works!と表示されれば起動しています.

先にSkypeが起動していると,Apache HTTPdサーバが起動しない場合があります.Skypeを起動している場合,一度Skypeを終了して,Apache HTTPdサーバを起動した後にSkypeを再起動するようにしてみてください.

あとは,httpdフォルダのhtdocsフォルダを基準として,相対パスを指定すれば,そのファイルをHTTPdサーバ経由で取得することができます.例えば,http://localhost/hello.htmlというパスをウェブブラウザに指定すると,htdocsフォルダのhello.htmlを取得することになります.

2016年7月6日水曜日

Vulkan - インスタンスの生成と破棄

ようやくVulkanインスタンスを生成します.

VkApplicationInfoの初期化

Vulkanインスタンスを生成するために,構造体VkInstanceCreateInfoが必要です.
この構造体を初期化するために,構造体VkApplicationInfoを初期化する必要があります.

構造体VkApplicationInfoは,次のような構造になっています.

struct VkApplicationInfo {
    VkStructureType    sType;
    const void*        pNext;
    const char*        pApplicationName;
    uint32_t           applicationVersion;
    const char*        pEngineName;
    uint32_t           engineVersion;
    uint32_t           apiVersion;
};

sTypeには,VK_STRUCTURE_TYPE_APPLICATION_INFOを設定する必要があります.
pNextはnullptrを設定する必要があります.
apiVersionには,VK_API_VERSION_1_0を設定します.今のところ,他のバージョンを設定するとインスタンスが生成できない場合があります.
pApplicatoinName,applicationVersion,pEngineName,engineVersionは適当でも大丈夫そうですが,今後どうなるかは分かりません.

VkInstanceCreateInfoの初期化

VkApplicationInfoを初期化したら,VkInstanceCreateInfoを初期化します.VkInstanceCreateInfoは次のような構造になっています.

struct VkInstanceCreateInfo {
    VkStructureType             sType;
    const void*                 pNext;
    VkInstanceCreateFlags       flags;
    const VkApplicationInfo*    pApplicationInfo;
    uint32_t                    enabledLayerCount;
    const char* const*          ppEnabledLayerNames;
    uint32_t                    enabledExtensionCount;
    const char* const*          ppEnabledExtensionNames;
};

sTypeには,VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFOを設定する必要があります.
pNextはnullptrを設定する必要があります.
flagsは今後のために予約されており,現時点では0を指定します.
pApplicationInfoには,前もって初期化しておいたVkApplicationInfoの変数へのポインタを設定します.
enabledLayerCountには有効にしたいレイヤーの数を,ppEnabledLayerNamesには有効にしたいレイヤー名へのポインタの配列を設定します.
enabledExtensionCount,ppEnabledExtensionNamesもレイヤーと同様に,有効にしたい拡張機能の数と名前へのポインタの配列を設定します.

Vulkanインスタンスの生成

VkInstanceCreateInfoが初期化できたら,次のようなvkCreateInstance()を呼び出してVulkanインスタンスを生成します.

VkResult vkCreateInstance(
    const VkInstanceCreateInfo * pCreateInfo,
    const VkAllocationCallbacks * pAllocator,
    VkInstance * pInstance
);

pCreateInfoには,初期化しておいたVkInstanceCreateInfoの変数へのポインタを渡します.
pAllocatorは,nullptrを指定します.適切な設定をすると,メモリ管理を制御できるのですが,今回は触れません.
pInstanceには,実際に生成されたVulkanインスタンスへのハンドルが設定されるので,VkInstanceの変数へのポインタを渡します.

Vulkanインスタンスの破棄

最後に,次のようなvkDestroyInstance()を呼び出してインスタンスを破棄して終了です.

void vkDestroyInstance(
    VkInstance * pInstance
    const VkAllocationCallbacks * pAllocator
);

実例

以下は,実際にVulkanインスタンスを生成して破棄するだけのコードです.本来は,それぞれのレイヤーがあるかどうか,拡張機能は利用できるかどうか,などを確認すべきですが,ここではそのあたりは省略しています.

#include <iostream>
#include <vector>
#include <vulkan/vulkan.h>

#pragma comment(lib, "vulkan-1")

using namespace std;

int main()
{
    // アプリケーション情報を初期化
    VkApplicationInfo applicationInfo {};
    applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    applicationInfo.pNext = nullptr;
    applicationInfo.pApplicationName = "";
    applicationInfo.applicationVersion = 0;
    applicationInfo.pEngineName = "";
    applicationInfo.engineVersion = 0;
    // VK_VERSION_1_0と間違えないこと
    applicationInfo.apiVersion = VK_API_VERSION_1_0;

    // APIの実引数をダンプしてくれるレイヤーを有効化しておく
    vector<const char *> layerNames {
        "VK_LAYER_LUNARG_api_dump"
    };
    // 今後必要になる拡張機能を有効にしておく
    vector<const char *> extensionNames {
        "VK_KHR_surface",
        "VK_KHR_win32_surface"
    };

    // インスタンス生成情報を初期化
    VkInstanceCreateInfo instanceCreateInfo {};
    instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    instanceCreateInfo.pNext = nullptr;
    instanceCreateInfo.flags = 0;
    instanceCreateInfo.pApplicationInfo = &applicationInfo;
    instanceCreateInfo.enabledLayerCount = layerNames.size();
    instanceCreateInfo.ppEnabledLayerNames = layerNames.data();
    instanceCreateInfo.enabledExtensionCount = extensionNames.size();
    instanceCreateInfo.ppEnabledExtensionNames = extensionNames.data();

    // Vulkanインスタンスを生成
    VkInstance instance = VK_NULL_HANDLE;
    VkResult result = vkCreateInstance(&instanceCreateInfo, nullptr, &instance);
    if(result != VK_SUCCESS)
    {
        cerr << "Vulkanインスタンスの生成に失敗" << endl;
        return -1;
    }
    cout << "Vulkanインスタンスの生成に成功" << endl;

    vkDestroyInstance(instance, nullptr);
}

サンプルコードは,ryutorion/LearningVulkan (GitHubリポジトリ)の003_CreateInstanceにもあります.

Vulkan - インスタンス拡張機能の列挙

拡張機能とは

レイヤーの列挙,でも書いたように,レイヤーはVulkan APIによって発行されるコマンドに割り込みをかけるものです.

一方,拡張機能はVulkanのコマンドなどを追加します.

インスタンス拡張機能の列挙

拡張機能を列挙するための手順は,レイヤーを列挙する際の手順に似ています.インスタンス拡張機能の場合,次のようなvkEnumerateInstanceExtensionProperties()を呼び出します.

VkResult vkEnumerateInstanceExtensionProperties(
    const char * pLayerName,
    uint32_t * pPropertyCount,
    VkExtensionProperties * pProperties
);

仮引数の名前を見ると分かるように,インスタンス拡張機能はレイヤーに紐付いています.ただし,pLayerNameにnullptrを指定することも可能で,その場合はデフォルトで利用可能なインスタンス拡張が列挙されます.

インスタンス拡張機能の情報は,次のような構造体に保存されます.

struct VkExtensionProperties {
    char        extensionName[VK_MAX_EXTENSION_NAME_SIZE];
    uint32_t    specVersion;
};

以下の例では,利用可能なレイヤー名を全て取得し,更にnullptrを追加した上で,インスタンス拡張を列挙して,その情報をダンプしています.

#include <iostream>
#include <vector>
#include <vulkan/vulkan.h>

#pragma comment(lib, "vulkan-1")

using namespace std;

class version
{
public:
    version(uint32_t value) : mValue(value) {}

    uint32_t value() const { return mValue; }
    uint32_t major() const { return VK_VERSION_MAJOR(mValue); }
    uint32_t minor() const { return VK_VERSION_MINOR(mValue); }
    uint32_t patch() const { return VK_VERSION_PATCH(mValue); }

private:
    uint32_t mValue;
};

std::ostream & operator<<(std::ostream & out, const version & v)
{
    out << v.major() << "." << v.minor() << "." << v.patch();
    return out;
}

int main()
{
    VkResult result = VK_SUCCESS;
    vector<VkLayerProperties> layers {};
    do {
        uint32_t count = 0;
        result = vkEnumerateInstanceLayerProperties(&count, nullptr);
        if(result != VK_SUCCESS)
        {
            break;
        }

        layers.resize(count);
        result = vkEnumerateInstanceLayerProperties(&count, layers.data());
    } while(result == VK_INCOMPLETE);

    // レイヤー名の配列を生成
    vector<const char *> layerNames(layers.size() + 1);
    for(auto i = 0; i < layers.size(); ++i)
    {
        layerNames[i] = layers[i].layerName;
    }
    layerNames[layers.size()] = nullptr;

    for(auto layerName : layerNames)
    {
        cout << (layerName ? layerName : "No Layer Name") << endl;

        // レイヤーに対応する拡張の取得
        vector<VkExtensionProperties> extensions;
        do {
            uint32_t count = 0;
            result = vkEnumerateInstanceExtensionProperties(layerName, &count, nullptr);
            if(result != VK_SUCCESS)
            {
                break;
            }

            extensions.resize(count);
            result = vkEnumerateInstanceExtensionProperties(layerName, &count, extensions.data());
        } while(result == VK_INCOMPLETE);

        for(auto & extension : extensions)
        {
            cout << "\tExtension Name        : " << extension.extensionName << endl;
            cout << "\tSpecification Version : " << version(extension.specVersion) << endl;
            cout << endl;
        }
    }
}

GitHubのリポジトリryutorion/LearningVulkanの002_EnumerateInstanceExtensionsに実際のコードがあります.