cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

openFrameworksのshaderでGLSL4.2のatomic counter bufferを使って画素の数をカウントしてみる(その2)

大量のatomic counterを使って色分布のヒストグラムを作りたい、と考えたときに普通の発想であれば配列化してしまえば良い、と考えますが、資料[1]によるとshader内でそのような書き方はできない、とあります。
どういうことなのか、見てみましょう。

まず、openGL側の関数を引数を足して、少し汎用化します。

// from here: http://www.lighthouse3d.com/tutorials/opengl-atomic-counters/

void create_a_buffer_for_atomic_counters(GLuint& buffer, int size = 1, int index = 0)
{
	glGenBuffers(1, &buffer);
	// bind the buffer and define its initial storage capacity
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer);
	glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(GLuint) * size, NULL, GL_DYNAMIC_DRAW);
	// unbind the buffer 
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, index);
}

void reset_the_atomic_counter_buffers(GLuint& buffer, int size = 1, int index = 0) {
	GLuint *userCounters;
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer);
	// map the buffer, userCounters will point to the buffers data
	userCounters = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
		0,
		sizeof(GLuint) * size,
		GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT
	);
	// set the memory to zeros, resetting the values in the buffer
	memset(userCounters, 0, sizeof(GLuint) * size);
	// unmap the buffer
	glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);

	glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, index, buffer); // added important
}

void simpler_reset_the_atomic_counter_buffers(GLuint& buffer, int size = 1, int index = 0, int offset = 0) {
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer);

	GLuint *a = new GLuint[size];
	memset(a, 0, size * sizeof(GLuint));
	glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, offset, sizeof(GLuint) * size, a);
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, index);

	glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, index, buffer); // added important
	delete[] a;
8}

void read_back_the_values_from_the_buffer(GLuint& acb, int* userVariables, int size = 1, int index = 0, int offset = 0)
{
	GLuint *userCounters;
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acb);
	// again we map the buffer to userCounters, but this time for read-only access
	userCounters = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
		(GLintptr)offset,
		sizeof(GLuint) * size,
		GL_MAP_READ_BIT
	);

	// copy the values to other variables because...
	for (int i = 0; i < size; i++) {
		userVariables[i] = (int)userCounters[i];
	}
	// ... as soon as we unmap the buffer
	// the pointer userCounters becomes invalid.
	glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
}

void alternative_read_back_the_values_from_the_buffer(GLuint& buffer, int* userVariables, int size = 1, int offset = 0) {
	GLuint *userCounters = new GLuint[size];
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer);
	glGetBufferSubData(GL_ATOMIC_COUNTER_BUFFER, offset, sizeof(GLuint) * size, userCounters);
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);
	for (int i = 0; i < size; i++) {
		userVariables[i] = (int)userCounters[i];
	}
}

次に、shaderのプログラムにヒストグラムカウントを書き足します。このコードは、正しくありませんので実行する必要はありません。

	string fragmentShaderProgram = STRINGIFY(
		#version 450 compatibility\n
		#extension GL_ARB_shader_atomic_counters : enable\n
		#extension GL_EXT_gpu_shader4 : enable\n

		layout(binding = 0, offset = 0) uniform atomic_uint atRed;
	layout(binding = 0, offset = 4) uniform atomic_uint atGreen;
	layout(binding = 0, offset = 8) uniform atomic_uint atBlue;

	layout(binding = 1, offset = 0) uniform atomic_uint myIntReds[256]; // このような書き方はできない

	uniform sampler2DRect tex0;

	in vec4 gl_FragCoord;
	out vec4 colorOut;

	void main() {
		vec2 pos = vec2(gl_FragCoord.x, gl_FragCoord.y); // テクスチャ上の座標を取得する

		float r = texture2DRect(tex0, pos).r;
		float g = texture2DRect(tex0, pos).g;
		float b = texture2DRect(tex0, pos).b;
		float a = texture2DRect(tex0, pos).a;

		if (r > 0.5) atomicCounterIncrement(atRed);
		if (g > 0.5) atomicCounterIncrement(atGreen);
		if (b > 0.5) atomicCounterIncrement(atBlue);

		int myIntRed = int(255.0 * r);
		atomicCounterIncrement(myIntReds[myIntRed]);    // これが動かない
		
		colorOut = vec4(r, g, b, a);
	}
	);

テスト用にコードをヒストグラム読み出し部分を適当に少し足します。

	alternative_read_back_the_values_from_the_buffer(myIntReds, reds, 256, 0);
	simpler_reset_the_atomic_counter_buffers(myIntReds, 256, 1, 0);

	int total_r = 0;
	cout << "rgb count=" << cnt[0] << ", " << cnt[1] << ", " << cnt[2] << std::endl;
	for (int i = 0; i < 256; i++) {
		cout << reds[i] << " ";
		total_r += reds[i];
	}
	cout << endl;
	cout << "total_r = " << total_r << endl;

この実行結果は次のようになります。

rgb count=376021, 307367, 232583
7892 3783 3359 3061 2629 1981 1615 1382 1459 1467 1248 1096 1057 1115 1235 1385
1550 1586 1718 1687 1682 1835 1935 1732 2161 2389 2205 1987 1767 1834 2068 2172
2262 2346 2496 2424 2433 2307 2201 2100 2310 2432 2833 2850 2733 2635 2548 2506
2640 2545 2430 2519 2800 3017 2999 2913 2957 2863 2797 2919 2741 2629 2606 2882
3006 3142 3070 3010 2866 2852 2791 2738 2631 2614 2952 3162 3255 3218 3281 3240
3262 3121 2968 3045 2985 3048 2936 2904 2784 2931 3256 3305 3345 3404 3496 3762
3736 3701 3673 3798 3818 3964 4087 3994 4313 4753 5354 5516 5438 5546 5543 6006
6266 6478 6180 6295 5983 5980 5763 5492 5302 5149 5151 5020 4973 4917 5057 5040
5244 5282 5130 5041 5094 5015 5120 5144 5026 5061 4963 4996 5198 5190 5219 5381
5372 5433 5693 5315 5543 5858 5471 5574 5635 5475 5377 5445 5303 5166 4675 4436
4240 4088 3786 3372 3223 2987 3021 2831 2753 2667 2788 2772 2706 2740 2532 2458
2461 2324 2288 2182 2192 2248 2253 2434 2516 2378 2311 2094 2067 2043 2088 1979
1951 1883 1964 2052 2092 2037 2096 1982 1898 1855 1835 1778 1818 1824 1879 1811
1822 1838 1795 1671 1682 1636 1669 1744 1770 1766 1749 1734 1791 1809 1800 1831
1868 1929 1837 1759 1688 1613 1570 1564 1483 1470 1414 1391 1346 1362 1322 1414
1439 1345 1309 1281 1302 1272 1169 1211 1243 1213 1274 1538 2622 4229 8019 8966

total_r = 786432

1024×768=786432です。あれ?????動いていそうですね?
本来は答えが不定のはずで、偶然でしょうか。。。
上手く動かない、が、この記事の結論のつもりだったのですが、困ったな。


追記:もうチョイましにするため、クラス化しました。

class AtomicCounterBuffer
	{
	public:
		AtomicCounterBuffer()
			: size(1), index(0), offset(0)
		{
			data.reserve(size);
		}

		~AtomicCounterBuffer()
		{
			data.clear();
			glDeleteBuffers(1, &buffer);
		}

		// アトミックカウンタバッファの作成
		void create(int sz, int idx, int off)
		{
			// パラメータの設定
			size = sz;        // バッファのサイズ
			index = idx;      // マウント先のインデックス
			offset = off;     // マウント先のメモリオフセット
			data.resize(sz);

			// バッファの作成
			glGenBuffers(1, &buffer);
			// bind the buffer and define its initial storage capacity
			glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer);
			glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(GLuint) * size, NULL, GL_DYNAMIC_DRAW);
			// unbind the buffer 
			glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, index);
		}

		// カウンタを全てゼロにリセット
		void reset() {
			GLuint *userCounters;
			glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer);
			// map the buffer, userCounters will point to the buffers data
			userCounters = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
				0,
				sizeof(GLuint) * size,
				GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT
			);
			// set the memory to zeros, resetting the values in the buffer
			memset(userCounters, 0, sizeof(GLuint) * size);
			// unmap the buffer
			glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);

			glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, index, buffer); // added important
		}

		// カウンタを全てゼロにリセット
		// reset関数と同じ
		void simpler_reset() {
			glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer);

			GLuint *a = new GLuint[size];
			memset(a, 0, size * sizeof(GLuint));
			glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, offset, sizeof(GLuint) * size, a);
			glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, index);

			glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, index, buffer); // added important
			delete[] a;
		}

		// カウンタ値を読み出してベクターに格納
		int* read_back()
		{
			GLuint *userCounters;
			glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer);
			// again we map the buffer to userCounters, but this time for read-only access
			userCounters = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
				(GLintptr)offset,
				sizeof(GLuint) * size,
				GL_MAP_READ_BIT
			);

			// copy the values to other variables because...
			for (int i = 0; i < size; i++) {
				data[i] = (int)userCounters[i];
			}
			// ... as soon as we unmap the buffer
			// the pointer userCounters becomes invalid.
			glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);

			return data.data();
		}

		// カウンタ値を読み出してベクターに格納
		// read_back関数と同じ
		int* alternative_read_back() {
			GLuint *userCounters = new GLuint[size];
			glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer);
			glGetBufferSubData(GL_ATOMIC_COUNTER_BUFFER, offset, sizeof(GLuint) * size, userCounters);
			glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);
			for (int i = 0; i < size; i++) {
				data[i] = (int)userCounters[i];
			}
			delete[] userCounters;

			return data.data();
		}

		// dataの参照を直接返す
		std::vector<int>& values() {
			return data;
		}

	protected:
		GLuint buffer;
		std::vector<int> data;
		int size;
		int index;
		int offset;
	};


[1] http://www.lighthouse3d.com/tutorials/opengl-atomic-counters/