風邪で全身筋肉痛??

やあ子供たち。夏風邪を引いて全身の筋肉が最初から最後まで痛かったという体験をしたので書いておくよ。
1日目
仕事をしていると、全身の筋肉に一様な弱い痛みを感じる。仕事し過ぎたのだろうかと普段通り自転車で帰宅。帰宅後も何やら全身が筋肉痛で、何か心当たりはないか考えるも、とくに思い当たらず。しかし、寝る頃になると、風邪特有の悪寒や、肌のざらざら感を覚える、熱が38.7度まで上がる。筋肉の痛みは風邪によるものだったかと、ま納得というか、ひとまずは理解し、寝る、、寝れない。。熱い。痛い。。
2日目
。。発熱と筋肉の痛みとで、あまりにも寝苦しかった夜が明ける。朝、最寄りの医院に行き、風邪との診断。まそうだろう。筋肉の痛みは熱によるものでしょうと。抗生剤や解熱鎮痛剤など、いわゆる風邪の薬をもらう。喉もかなり痛いので、なるほど風邪なのだ、この筋肉の痛みも風邪によるものなのだと、まだこの時点ではそう考えた。薬を飲んでから10時間ほどは、熱も下がり痛みも消え、比較的楽に過ごす。しかし、それを過ぎた夜中の3時頃、熱はまた上がり、筋肉の痛みに耐えられず、解熱鎮痛剤を飲む。
3日目
するとまた10時間ほどは比較的楽に過ごす。しかしながら、午後3時頃、やはり痛みに耐えきれず、解熱鎮痛剤を服用。筋肉の痛みが和らぐ。ここにくると、風邪というより、発熱はまだいいのだが、何より張ったままどうやらますます痛みを増していく筋肉についてまじまじと考え始める。ただの風邪ではないのでは。。筋肉は熱を持ったまま、張った状態で、とにかく全身の筋肉が痛いので、起き上がろうとすると、生まれ変わるのではないかとおもうくらい、あらゆる筋肉が痛む。手をつけば、指の筋肉が痛み、太腿部も脹脛もすべて痛い。筋肉は力を入れずとも、圧力を感じただけで痛い。例えば、布団と床の間の段差が当たっているだけで痛い。指の筋肉が痛むので、缶ジュースの蓋を指一本で開けられない。一度少し持ち上げないと開かない方式の幼児侵入防止扉を開けるのも、腕や指の激痛を伴う。
4日目
ほとんど朝から寝ながら過ごすも、全くよくならないため、18時頃また解熱鎮痛剤を服用。ネットで調べると、免疫系が、筋肉を分解して免疫力を高めるという事例もあるという話を読む。筋肉の痛みは、とにかく安静にしていなさいという体からのメッセージなのだとか。あらゆることをしようという気力が湧かない。筋肉だけが原因で、人間気持ちまでが随分と落ち込むものだ。きっと、今よりもずっと年をとるとこういう感覚になるのかもしれない。
5日目
朝、これまでで最も筋肉が痛い。筋肉を痛くしている粒のようなものが体内にあるのだとするとそれの密度がこれまでの倍になったような感覚。それでも無理に朝食をとる。食後に市の救急相談窓口に電話。筋肉がやたら痛くなる風邪はあまり例がないらしく、期待していたような話は聞けず。緊急性もないとの返答。昼色を無理に食べたころ、何かが変わっていることに気付く。筋肉はこれまで通り痛いのだが、そのもっと奥底から、しっかりと動けるような何かが始動した感じがした。そのあとは、午後は屋外で子供の面倒もみることができるなど、少なくとも外見だけは健常者として復帰できた。
6日目
自転車で普通に出社。でもキーボードが打てない、自が書けない。足や腕の大きな動きに問題はないが、指先の動作がきつい。車は運転できても、バイクは絶対に乗れない。会社のお医者さんにも相談してみた。ギランバート症候群を連想された様だがちょっと違うよねと。何だろうねでも良くなってるのなら何よりだねと。
7日目
普通に出社しキーボードが打てるようになった。字はゆっくりとなら、自分の字が書けることに気付く。でも急ぐとだめだ。ミミズの字になってしまう。速く書けない。
8日目
普通に出社。キーボードはほぼ問題なし。まだ若干倦怠感は残るが。字もまあまあ普通に書けるようにはなってきた。

とまあ、こんなところだ。おじさんはネットに書いてあった、免疫系が筋肉を分解し過ぎたケースで、筋肉がずたずたに今回はされてしまったという、まそういう風邪のケースも自分にはあったというわけだ、という理解なんだけど、とにかくお医者も認識してないようだったので、同じような症状に見舞われた人は、どんどんこうして記録に残していってほしい。そして仲間を増やせば、もう少しピンときた医療対応というか説明もきけるようになるのではないかと思う。
今回の事があって、健常時の筋肉が如何にリッチで強力で、まるで高性能なアクチュエータを全身にまとっているかの様な気持ちになった。普通に健康って本当に有難いことなんだね。
チャオ!

C#での printf、ないしは cout

やあ子供たち。新しいことにチャレンジするといろいろ勉強になるから、今までやったことのないことにもガンガンチャレンジしていくことをお勧めするぞ。
今日はC#に関するメモだよ。こんな超基本的なことでも出来るとわかっているのと、出来るかな?と思っているのとでは、ま精神的にも肉体的にも大分状況が変わってくるから大変だね。忘れないようにしよう。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.IO;

namespace HellloSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            // まず、C#にはC/C++におけるprintfみたいな書き方ができる。
            Console.WriteLine("PI is {0}", Math.PI);
            Console.WriteLine("e is {0}", Math.E);
            // さらに、同じ書き方で、sprintfみたいなこともできる。
            {
                StringWriter sw = new StringWriter();
                sw.WriteLine("PI is {0}", Math.PI);
                sw.WriteLine("e is {0}", Math.E);
                Console.WriteLine(sw.ToString());
            }
            // なので、例えば書き出すファイル名の末尾に日付時刻を
            // つけたい場合などに使える。
            {
                StringWriter sw = new StringWriter();
                sw.Write("data_{0}.txt", 
                              DateTime.Now.ToString("yyyyMMdd_HHmmss"));
                String filename = sw.ToString();
                Console.WriteLine(filename);
            }
            // まー実は以下のようにやってもいけるわけだが。
            // この書き方は cout 的だね。
            {
                String stab = "data_";
                stab += DateTime.Now.ToString("yyyyMMdd_HHmmss");
                stab += ".txt";
                Console.WriteLine(stab);
            }
        }
    }
}

ちなみに、DateTime.Now.ToStringに食わせる時刻部分の"HH"は、大文字なのでこれは24時間表記をしなさい、という意味だよ、ここが小文字の"hh"だと、
12時間表記になって㏂なのか㏘なのかわからなくなってしまうので気を付けよう。

vs2017 community edition をインストールしてみたよ

やあ子供たちご無沙汰したな。今回おじさんはVS2017のCommunityEditionを個人的に導入してみたのでそのメモだよ。

●まずはインストールフォルダの構成がちょっと変わったのではという話。
C:\Program Files (x86)
の下にある、歴代VSのインストールフォルダは、"Microsoft Visual Studio"の後ろに、2桁バージョン表記(西暦表記とはちょっとずれてるあの紛らわしい表記)がついていたわけだけど、今回は何やら単に"Microsoft Visual Studio"という名前のフォルダができている。日付から判断してもどうやらこれがVS2017のようだ。

●次に、VS150COMNTOOLS環境変数どこいったという話。
ところで昨日ダウンロードしてきたインストーラなわけだが、インストールが完了したのに、VS150COMNTOOLS 環境変数が見当たらないことに気付いたよ。ネットを調べてみても同じような質問がたくさんされていた。そしてその中に見つけた無難そうなソリューションが、以下の手順だ。
"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools"\VsDevCmd.bat
を実行する。
ま確かにこれをやれば、VS150COMNTOOLS 環境変数がしっかり定義された。
がしかし、このBATファイル、別のBATファイルの中からそのまま呼ぶとその後の処理が実行されず、BAT実行がそこで終了してしまうようだよ。なので、BATファイルの中から呼び出す場合は、以下のように、-no_ext オプションをつけて呼ぶか、
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools"\VsDevCmd.bat -no_ext
あるいはこれはおじさんが勝手に見つけたのだけど、以下のより最低限な機能っぽいBATを呼ぶか、
call "C:\Program Files (x86)\Microsoft Visual Studio"\2017\Community\Common7\Tools\vsdevcmd\core\vsdevcmd_start.bat
まそういうふうにすれば、VS150COMNTOOLS 環境変数がしっかり定義された状態で、以降の行に書いた処理も実行されるという感じでした。。
肝心のdevenv.exe自体は、
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE
の下にあるので、ま上記環境変数以下の構成的には、これに関しては従来通りのようだ。(なお、上記パスの、「Community」となっているあたりのフォルダ構成はエディションによって変わってくるのだと思う。)
●プロジェクトファイル(.vcprojx)は、少なくともvs2015との互換性はかなり高そう
VS2015で作ったプロジェクトをVS2017で読み込んでも、Windows10のターゲットバージョンと、ツールキットのターゲットバージョンとを再確認されるだけで、プロジェクトファイルの変換といったような従来では必ず求められた操作は一切行われない。.vcprojxの内容をVS2017で開く前後で比較してみても、上述のターゲットバージョンの書き換えくらいしか、差異が確認できない。これなら、一度VS2017で作成・保存したプロジェクトを、VS2015で開くことが可能かも知れない。

また何かびっくりがあれば追記する予定。
チャオ!

C++SimpleProfilerを作ったよ

やあ子供たち。寒いけど春はもうすぐそこまで来ているぞ。でも今はスノボシーズン真っ只中なので、若者はプログラミングなんかしてないでスノボに行った方がいいぞ、雪山にな。
さて今日は簡単なプロファイリング用便利クラスを紹介するぞ。その名もSimpleProfilerだ。ちょっとだけ長めになってしまったがまずは以下がコードだよ。有用だと思った人は自由に使ってみてくれ。

//
// SimpleProfiler.h      
//    (C) 2017 nursmaul
//    url: http://d.hatena.ne.jp/nurs/20170228
//
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <process.h>
#include <time.h>

#include <iostream>
#include <sstream>
#include <vector>
#include <functional>
#include <algorithm>
#include <stack>
using namespace std;

class SimpleProfiler
{
	class MarkBase
	{
	public:
		MarkBase(const string& str, const clock_t cl)
			:_title(str), _clock(cl) {}
		virtual void dummyFunc() {}
		string _title;
		clock_t _clock;
	};
	class Scope : public MarkBase
	{
	public:
		Scope(const string& str, const clock_t cl, const bool is_pop=false)
			:MarkBase(str, cl),_is_pop(is_pop){}
		bool _is_pop;
		float _result;
	};
public:
	SimpleProfiler(void) {
		_tak = [](clock_t a, clock_t b) ->float
		{return (b - a) / (float)CLOCKS_PER_SEC; };
	}
	void MarkHere(const string& str) {
		_mark_vec.push_back(new MarkBase(str, clock()));
		return;
	}
	string Dump(void)
	{
		ostringstream oss;
		oss << "[SimpleProfiler] ****** Dump Starts ******" << endl;
		for (int i = 0; i < _mark_vec.size(); ++i)
		{
			auto im = _mark_vec[i];
			if (auto p = dynamic_cast<Scope*>(im))
			{
				if( !p->_is_pop)
					oss << "[SimpleProfiler] *-->>--" << p->_title <<":"<<p->_result << "(s)" << endl;
				else
					oss << "[SimpleProfiler] *--<<--" << endl;
			}
			else
			{
				if (i == _mark_vec.size() - 1)
					oss << "[SimpleProfiler] * " << im->_title << endl;
				else
					oss << "[SimpleProfiler] * " << im->_title << "[" << i << "-" << i + 1 << "]:"
					<< _tak(im->_clock, _mark_vec[i + 1]->_clock) << "(s)" << endl;
			}
		}
		oss << "[SimpleProfiler] ******* Dump Ends *******" << endl;
		return oss.str();
	}
	void PushScope(const string& str)
	{
		_scope_stack.push(_mark_vec.size());
		_mark_vec.push_back(new Scope(str, clock()));
	}
	void PopScope(void)
	{
		int id = _scope_stack.top();
		_scope_stack.pop();
		auto pushed = dynamic_cast< Scope*>(_mark_vec[id]);
		pushed->_result = _tak(pushed->_clock, clock());
		_mark_vec.push_back(new Scope("--", clock(), true));
	}
	stack< int > _scope_stack;
	vector< MarkBase* > _mark_vec;
	function< float(clock_t a, clock_t b) > _tak;
};

さて、使い方は以下のようになるよ。今回はちょっとした計算課題をシングルスレッドで計算させた場合と、マルチスレッドで計算させた場合との計算時間の測定を題材にしてみたよ。

unsigned int __stdcall func(void* pv)
{
	float sum = 0;
	for (int i = 0; i < 300000000; ++i)
	{
		sum += 1 / (float)(i + 1);
	}
	cout << sum << endl;
	return 0;
}
int main(int argc, char* argv[])
{
	SimpleProfiler sp;
	const int num_thread = 4;
	sp.PushScope("Today's Test");
	sp.PushScope("single thread test");
	{
		for (int i = 0; i < num_thread; ++i)
		{
			char s[234];
			sprintf(s, " %d th test.", i);
			sp.MarkHere(s);
			func(nullptr);
		}
	}
	sp.PopScope();
	sp.PushScope("multi thread test");
	{
		vector< HANDLE > handles(num_thread);
		sp.MarkHere(" _beginthreadex call");
		for (auto& ih : handles)
		{
			DWORD dw;
			ih = (HANDLE)_beginthreadex(0, 0, func, nullptr, 0, (unsigned int*)&dw);
		}// i
		sp.MarkHere(" ResumeThread() call");
		for_each(handles.begin(), handles.end(), [](HANDLE& h) { ResumeThread(h); });
		sp.MarkHere(" WaitForMultipleObjects() call");
		WaitForMultipleObjects(num_thread, handles.data(), TRUE, INFINITE);
		sp.MarkHere(" CloseHandle() call");
		for_each(handles.begin(), handles.end(), [](HANDLE& h) { CloseHandle(h); });
	}
	sp.PopScope();
	sp.PopScope();
	cout << sp.Dump();
	cout << "its done" << endl;

	getchar();
	return 0;
}

SimpleProfilerの使用可能なメソッドはとても簡単で、4つだけだ。

  • SimpleProfiler(コンストラクタ)
  • MarkHere 時間を計測したい点で呼び出します。
  • PushScope/PopScope 時間を計測したい、特定の区間(スコープ)を定義、スコープは複数定義可能で互いに入れ子構造にすることも出来る。
  • Dump 各マーク間、およびスコープ内でかかった時間の計測結果を時系列に表示。
15.4037
15.4037
15.4037
15.4037
15.4037
15.4037
15.4037
[SimpleProfiler] ****** Dump Starts ******
[SimpleProfiler] *-->>--Today's Test:12.369(s)
[SimpleProfiler] *-->>--single thread test:10.651(s)
[SimpleProfiler] *  0 th test.[2-3]:2.566(s)
[SimpleProfiler] *  1 th test.[3-4]:2.696(s)
[SimpleProfiler] *  2 th test.[4-5]:2.694(s)
[SimpleProfiler] *  3 th test.[5-6]:2.695(s)
[SimpleProfiler] *--<<--
[SimpleProfiler] *-->>--multi thread test:1.718(s)
[SimpleProfiler] *  _beginthreadex call[8-9]:0(s)
[SimpleProfiler] *  ResumeThread() call[9-10]:0(s)
[SimpleProfiler] *  WaitForMultipleObjects() call[10-11]:1.718(s)
[SimpleProfiler] *  CloseHandle() call[11-12]:0(s)
[SimpleProfiler] *--<<--
[SimpleProfiler] *--<<--
[SimpleProfiler] ******* Dump Ends *******
its done

出力結果は上述のようになるよ。マーク間の計測時間と、スコープ内処理でかかった総合時間がそれぞれ見やすく表示されているのがおわかりいただけると思う。よく、特定の処理の時間計測で、自分でclock()呼んで、変数に入れて、表示して、とかを一切やらなくてよく、かつ解り易く状況を手っ取り早く知ることができる、これがSimpleProfilerの特徴だよ。
それでは今日はこの辺で。寝ろ!

WaitForMultipleObjects()には気をつけろ!

やあ子供たち。マルチスレッドやっているか。
よくWaitForMultipleObjects()関数が、待ってくれない、などの書き込みを目にするけど、おじさんの場合は笑っちゃうくらい別次元の問題だったようなのでここにメモしておくぞ。
まず、待ってくれないコードが以下。

WaitForMultipleObjects(num_thread, handles, TRUE, INFINITY);

そして、待ってくれるコードが以下。

WaitForMultipleObjects(num_thread, handles, TRUE, INFINITE);

そう、4番目の引数では、INFINITYではなく、INFINITEを使えというわけらしかったぜ。
チャオ!

旧OpenGLのマトリックス操作系APIは自分で作ってしまおう

やあ子供たち。一番寒い季節がやってきたな。元気にやっているかい。年賀状は全部返したか。今日は泣く子も黙るMatrixHelperの紹介だよ。
OpenGL1は機能制限があったが多くのことを僕たちに教えてくれた。
glPushMatrix(), glPopMatrix()といった、3次元空間でのマトリックス演算系APIもその一つだ。
しかしながら、最新のOpenGLではマトリックス演算というものは実際にはGPUに渡すシェーダープログラムの中で行われるべしというふうになっているよ。なので上述のマトリックス演算系APIもDeprecated扱いとなり、悉く駆逐されてしまっています。
古きよき、glPush/PopMatrix()はもういません。私たちは、何かマトリクス演算をしたければ、そのマトリクスをシェーダーのUniform変数として、あるいはUniformBufferObjectとしてGPUの中のシェーダープログラムに転送し、GPUの中で、それを使って計算させる、そういうシェーダーを書く、という手続きを踏むことが前提の設計となっています。
ではGPUに転送するマトリックスはどう管理・保持すればいいのでしょうか。glPush/PopMatrix()を駆使してコードを書きながら3次元形状の階層構造を構築していくあの感覚は非常に重要な体験でした。3次元グラフィックスにおいて、モデル部品間に親子関係、包含関係を持たせることはやはり便利というか、必須です。だからこそ上述のAPIがいたのに、それがもう使えないとあれば、、もう作るしかない!
そう、そこで今回のMatrixHelperの登場となります。以下にMatrixHelperのコードを示します。

template< class Func >
class MatrixHelper
{
public:
	MatrixHelper(Func func)
		:_func(func)
	{
		// スタックのデフォルト基底階層に単位行列をロードしておく
		_matrix_stack.push(glm::mat4(1.0));
	}
	// matrix support
	void LoadIdentity(void) { LoadMatrix(glm::mat4(1.0)); }
	void LoadMatrix(const glm::mat4& mat)
	{
		_matrix_stack.top() = mat;
		// モデルマトリックス送信
		//glUniformMatrix4fv(_uniform_modelmatrix_id, 1, 0,
		//	glm::value_ptr(_matrix_stack.top()));
		_func(_matrix_stack.top());
		return;
	}
	void MultMatrix(const glm::mat4& mat)
	{
		_matrix_stack.top() *= mat;
		// モデルマトリックス送信
		//glUniformMatrix4fv(_uniform_modelmatrix_id, 1, 0,
		//	glm::value_ptr(_matrix_stack.top()));
		_func(_matrix_stack.top());
		return;
	}
	void PushMatrix(void)
	{
		_matrix_stack.push(_matrix_stack.top());
		return;
	}
	void PopMatrix(void)
	{
		_matrix_stack.pop();
		// モデルマトリックス送信
		//glUniformMatrix4fv(_uniform_modelmatrix_id, 1, 0,
		//	glm::value_ptr(_matrix_stack.top()));
		_func(_matrix_stack.top());
	}
	const glm::mat4& GetTopMatrix(void)
	{
		return _matrix_stack.top();
	}
public:
	std::stack< glm::mat4 > _matrix_stack;
	Func _func;
};

はい見てわかるように、これは単なるglm::mat4のスタックを、旧GLAPIライクに操作できるようにしたものです。しかし、そればかりではありません。スタックの最上位にあるマトリックスの内容が変化した場合は、同マトリックスを引数にもつ「処理」を呼び出します。この「処理」には。任意のものを定義可能です。ここでは、スタック最上位のマトリックスを、GPUに転送するような処理を設定してあげます。(コメントにある、glUniformXXX()呼び出しを参考としてください。)
こうすることで、PopMatrix()や、MultMatrix()などの、スタック最上位マトリックスが更新されるような操作を行った際、確実にGPUに新しいスタック最上位マトリックスが転送されるようになります。(Pushで更新用の_funcを呼んでいないのは、最上位マトリックスと同じものが複製されて上に乗っかるのみなので、更新処理は実質必要ないためです。)
こうして、あたかも、旧GLのマトリックスAPIを利用しているかのような、以下のようなコードが書けるようになります。まずはMatrixHelperの定義例から見ていきます。

class LambdaForMatrixHelper
{
public:
	LambdaForMatrixHelper(const int id_ubo, const glm::mat4* prj, const glm::mat4* viw)
		:_id_ubo(id_ubo)
		,_pprj(prj)
		,_pviw(viw)
	{};
	void operator()(glm::mat4& mdl)
	{
		vector< float > buf;
		{
			const auto& prj = *_pprj;
			const auto& viw = *_pviw;
			//
			const float *p = glm::value_ptr(prj);
			buf.insert(buf.end(), p, p + 16);
			const float *r = glm::value_ptr(viw);
			buf.insert(buf.end(), r, r + 16);
			const float *q = glm::value_ptr(mdl);
			buf.insert(buf.end(), q, q + 16);
		}
		GLuint bindingPoint = 1;
		glBindBuffer(GL_UNIFORM_BUFFER, _id_ubo);
		glBufferData(GL_UNIFORM_BUFFER, buf.size() * sizeof(float), buf.data(), GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, _id_ubo);
		return;
	};
public:
	int _id_ubo;
	const glm::mat4* _pprj, *_pviw;
};

// ここがテンプレートの実体化
typedef MatrixHelper< LambdaForMatrixHelper > MyMatrixHelper;

はい、いかがだろうか。ここでは、投影行列やビュー行列、モデル行列といった、いわゆる3次元CGにおける3大マトリックス、およびそれらをまとめて管理するUBOのID、といった情報を管理および、glUniformXXX()転送することができる関数オブジェクトを定義し、それを、MatrixHelperの、更新処理として定義しています。とはいえ、operator()の引数を見るとわかるように、このMatrixHelperは、モデル行列のみを、階層構造管理の対象にしています。(ま普通、ビュー行列や投影行列をそれ単体で階層構造管理することはないですので)。
これを使った実装例が、以下になります。これは、階層モデリングをサポートするモデルクラスの描画関数の例です。

void MyModel::DrawModel( MatrixHelper& mh )
{
        // ...
	mh.PushMatrix();
	mh.MultMatrix(this->_matrix);
	mh.PushMatrix();
	mh.MultMatrix(this->_private_matrix);
	{
          // ここでシェーダー有効化、テクスチャ有効化など
      // ここでモデルを描画(VAAHなど使い)
        }
	mh.PopMatrix();
        // 子モデルの描画
	for(auto ich : this->getChildren())
	{
		ich->DrawModel(mh);// ま再帰呼び出しはあまりよくないが
	}// ich
	mh.PopMatrix();

VAAHについては、こちらの過去日記を参照ください。
それでは今日もこの辺で。チャオ!

OpenGLSimpleShaderAdapter

やあ子供たち。
以前VertexAttribArrayHelper(VAAH)というものを作りました。
これはつまり、glVertexAttribPointer()やglBufferDate()など、何かと解り難いOpenGLAPIの呼び出しをラップして、固定パイプラインと呼ばれる、
API(glBegin(), glVertex3f(), glEnd())の使い勝手を再現しようとしたものであり、旧APIに慣れた身には非常に便利なものではあったが、

しかし、その名にそぐわないことに、対応している頂点アトリビュートが固定で、使用可能なVertexAttributeは、Position, Normal, Color, TexUV のみであった。それ以外の、例えばTangentVectorだとか、Binormalだとか、あるいはマルチテクスチャー時に必要となってくるはずの第2、第3のTexUV値なども頂点バッファに詰め込みたいといった場合、残念ながらVAAHそのものを書き換えなくてはならなかった。

それもまあよかったのだが、そういうのをきっかけとして、また一歩考えを進めてみると、この時代のOpenGLで言う「描画」とは、各シェーダープログラムの記述内容を具現化することのみ、という流れであることに気付いて見れば、VAAHのようなものは、もしかするとシェーダーの読み込み・管理を行う、処理単位と融合された姿が理想なのではないかと考えるに至ったわけである。

なんだか俺はお化けクラスのようなものを作ろうとしているのだろうかと思ってしまうが、概念はシンプルでそれはつまり上述の、

  • シェーダープログラムを解析し(全入力アトリビュートの位置取得し)
  • そこに、必要なだけの頂点バッファ(各入力アトリビュートに対し)
    の内容を流し込み
  • 描画する

という機能をまとめてラップした、いわば「シェーダー実行環境」「シェーダー機能一切合切のラッパー的なもの」名づけようものなら「シェーダーヘルパー」「シェーダーアダプター」を作りたいと思うに至ったわけでありました。

OpenGLのプログラムは本当に煩雑化しやすいです。
ステートマシンである上に内部構造が公開されていないので、BindやらActiveやらいろんなAPIをみんな訳も分からず呼ばざるを得ない状況です。glが頭につくすべてのAPIって全部グローバル関数ですよね。名前空間すらないです。ステートをグローバル変数と読み替えるなら、これは
グローバル変数の状態を常に気にしなくてはならない、グローバル関数が集まってできたAPI体系」、
これがOpenGLです。
こわいですね。各APIに与える引数とは別に、グローバルなステートが常にその動作に何らかの影響を与える可能性があるわけです。よっぽど好きでなければ使いたくはならないはずですこんなライブラリ。改めて冷静に考えてみても、グローバル関数のみからなる、C言語ライクなライブラリは世にたくさんありますが、グローバル変数の状態を常に前提とするような最低な造りはOpenGLくらいしか思いつかないです。
はい、昔からOpenGL大好きでしたが、こう書いてみると本当は大嫌いだったのかも知れません。いえいえ、もちろんOpenGLの出力結果は大好きなのです。しかし、APIの体系は本当に大嫌いです。
次にシェーダー、テクスチャーまわりでまずいと思うことを書きます。
glUniform()を使えばどこからでも任意のシェーダーのパラメータを変更できてしまう。これがまずいけない。これに対する対策を考えた内容がこうです。
「特定のシェーダーパラメータへの一切のglUniform()呼び出しは、シェーダーアダプターが行うべきである。」
そう、例えば赤だったものを青にしたいなどの色情報変更に際して、シェーダーのパラメータの変更がしたければ、その旨シェーダーヘルパーに依頼するようにし、決して勝手なglUniform()呼び出しはしない。
同様に、
「特定のシェーダーが使うテクスチャー情報のGPUへの登録も、シェーダーアダプターが行うべき。」
テクスチャーというものは、フラグメントシェーダーの中で、samplerという変数に抽象化されるので、このsamplerのシンボルと、テクスチャ画像情報とを結びつけることを、シェーダーアダプターが受け持つというしくみにして、その辺はもう全部シェーダーアダプターの中で閉じてしまうのである。
いかがだろうか。これでわけのわからない場所に、glUniform()やら、glActiveTexture()やら、glBindTexture()、glTexImage2d()などが現れ、混乱させられる事態を、回避できる。
素晴らしきシェーダーアダプター。
さてその実装を見てみよう。
(続く。(続かないかも知れないが))