Branimir Karadžić 9 years ago
parent
commit
f9934d5253

+ 1 - 1
examples/32-particles/particles.cpp

@@ -110,7 +110,7 @@ struct Emitter
 //		if (ImGui::CollapsingHeader("General") )
 		{
 			if (ImGui::Combo("Shape", (int*)&m_shape, s_shapeNames, BX_COUNTOF(s_shapeNames) )
-					||  ImGui::Combo("Direction", (int*)&m_direction, s_directionName, BX_COUNTOF(s_directionName) ) )
+			||  ImGui::Combo("Direction", (int*)&m_direction, s_directionName, BX_COUNTOF(s_directionName) ) )
 			{
 				psDestroyEmitter(m_handle);
 				m_handle = psCreateEmitter(m_shape, m_direction, 1024);

+ 453 - 432
examples/common/ps/particle_system.cpp

@@ -96,286 +96,6 @@ struct PosColorTexCoord0Vertex
 
 bgfx::VertexDecl PosColorTexCoord0Vertex::ms_decl;
 
-struct Particle
-{
-	float start[3];
-	float end[2][3];
-	float blendStart;
-	float blendEnd;
-	float scaleStart;
-	float scaleEnd;
-
-	uint32_t rgba[5];
-
-	float life;
-	float lifeSpan;
-};
-
-struct ParticleSort
-{
-	float    dist;
-	uint32_t idx;
-};
-
-inline uint32_t toAbgr(const float* _rgba)
-{
-	return 0
-		| (uint8_t(_rgba[0]*255.0f)<< 0)
-		| (uint8_t(_rgba[1]*255.0f)<< 8)
-		| (uint8_t(_rgba[2]*255.0f)<<16)
-		| (uint8_t(_rgba[3]*255.0f)<<24)
-		;
-}
-
-inline uint32_t toAbgr(float _rr, float _gg, float _bb, float _aa)
-{
-	return 0
-		| (uint8_t(_rr*255.0f)<< 0)
-		| (uint8_t(_gg*255.0f)<< 8)
-		| (uint8_t(_bb*255.0f)<<16)
-		| (uint8_t(_aa*255.0f)<<24)
-		;
-}
-
-struct Emitter
-{
-	void create(EmitterShape::Enum _shape, EmitterDirection::Enum _direction, uint32_t _maxParticles);
-	void destroy();
-
-	void reset()
-	{
-		m_num = 0;
-	}
-
-	void update(float _dt)
-	{
-		uint32_t num = m_num;
-		for (uint32_t ii = 0; ii < num; ++ii)
-		{
-			Particle& particle = m_particles[ii];
-			particle.life += _dt * 1.0f/particle.lifeSpan;
-
-			if (particle.life > 1.0f)
-			{
-				if (ii != num-1)
-				{
-					memcpy(&particle, &m_particles[num-1], sizeof(Particle) );
-					--ii;
-				}
-
-				--num;
-			}
-		}
-
-		m_num = num;
-
-		if (0 < m_uniforms.m_particlesPerSecond)
-		{
-			spawn(_dt);
-		}
-	}
-
-	void spawn(float _dt)
-	{
-		float mtx[16];
-		bx::mtxSRT(mtx
-			, 1.0f, 1.0f, 1.0f
-			, m_uniforms.m_angle[0],    m_uniforms.m_angle[1],    m_uniforms.m_angle[2]
-			, m_uniforms.m_position[0], m_uniforms.m_position[1], m_uniforms.m_position[2]
-			);
-
-		const float timePerParticle = 1.0f/m_uniforms.m_particlesPerSecond;
-		m_dt += _dt;
-		const uint32_t numParticles = uint32_t(m_dt / timePerParticle);
-		m_dt -= numParticles * timePerParticle;
-
-		float time = 0.0f;
-		for (uint32_t ii = 0
-			; ii < numParticles && m_num < m_max
-			; ++ii
-			)
-		{
-			Particle& particle = m_particles[m_num];
-			m_num++;
-
-			const float up[3] = { 0.0f, 1.0f, 0.0f };
-
-			float pos[3];
-			switch (m_shape)
-			{
-				default:
-				case EmitterShape::Sphere:
-					bx::randUnitSphere(pos, &m_rng);
-					break;
-
-				case EmitterShape::Hemisphere:
-					bx::randUnitHemisphere(pos, &m_rng, up);
-					break;
-
-				case EmitterShape::Circle:
-					bx::randUnitCircle(pos, &m_rng);
-					break;
-
-				case EmitterShape::Disc:
-					{
-						float tmp[3];
-						bx::randUnitCircle(tmp, &m_rng);
-						bx::vec3Mul(pos, tmp, bx::frnd(&m_rng) );
-					}
-					break;
-
-				case EmitterShape::Rect:
-					pos[0] = bx::frndh(&m_rng);
-					pos[1] = 0.0f;
-					pos[2] = bx::frndh(&m_rng);
-					break;
-			}
-
-			float dir[3];
-			switch (m_direction)
-			{
-				default:
-				case EmitterDirection::Up:
-					bx::vec3Move(dir, up);
-					break;
-
-				case EmitterDirection::Outward:
-					bx::vec3Norm(dir, pos);
-					break;
-			}
-
-			float start[3];
-			float end[3];
-			const float startOffset = bx::flerp(m_uniforms.m_offsetStart[0], m_uniforms.m_offsetStart[1], bx::frnd(&m_rng) );
-			bx::vec3Mul(start, pos, startOffset);
-
-			const float endOffset = bx::flerp(m_uniforms.m_offsetEnd[0], m_uniforms.m_offsetEnd[1], bx::frnd(&m_rng) );
-			float tmp1[3];
-			bx::vec3Mul(tmp1, dir, endOffset);
-			bx::vec3Add(end, tmp1, start);
-
-			particle.life = time;
-			particle.lifeSpan = bx::flerp(m_uniforms.m_lifeSpan[0], m_uniforms.m_lifeSpan[1], bx::frnd(&m_rng) );
-
-			float gravity[3] = { 0.0f, -9.81f * m_uniforms.m_gravityScale * bx::fsq(particle.lifeSpan), 0.0f };
-
-			bx::vec3MulMtx(particle.start,  start, mtx);
-			bx::vec3MulMtx(particle.end[0], end,   mtx);
-			bx::vec3Add(particle.end[1], particle.end[0], gravity);
-
-			memcpy(particle.rgba, m_uniforms.m_rgba, BX_COUNTOF(m_uniforms.m_rgba)*sizeof(uint32_t) );
-
-			particle.blendStart = bx::flerp(m_uniforms.m_blendStart[0], m_uniforms.m_blendStart[1], bx::frnd(&m_rng) );
-			particle.blendEnd   = bx::flerp(m_uniforms.m_blendEnd[0],   m_uniforms.m_blendEnd[1],   bx::frnd(&m_rng) );
-
-			particle.scaleStart = bx::flerp(m_uniforms.m_scaleStart[0], m_uniforms.m_scaleStart[1], bx::frnd(&m_rng) );
-			particle.scaleEnd   = bx::flerp(m_uniforms.m_scaleEnd[0],   m_uniforms.m_scaleEnd[1],   bx::frnd(&m_rng) );
-
-			time += timePerParticle;
-		}
-	}
-
-	uint32_t render(const float* _mtxView, const float* _eye, uint32_t _first, uint32_t _max, ParticleSort* _outSort, PosColorTexCoord0Vertex* _outVertices) const
-	{
-		bx::EaseFn easeRgba  = s_easeFunc[m_uniforms.m_easeRgba];
-		bx::EaseFn easePos   = s_easeFunc[m_uniforms.m_easePos];
-		bx::EaseFn easeBlend = s_easeFunc[m_uniforms.m_easeBlend];
-		bx::EaseFn easeScale = s_easeFunc[m_uniforms.m_easeScale];
-
-		for (uint32_t jj = 0, num = m_num, current = _first
-			; jj < num && current < _max
-			; ++jj, ++current
-			)
-		{
-			const Particle& particle = m_particles[jj];
-
-			const float ttPos   = easePos(particle.life);
-			const float ttScale = easeScale(particle.life);
-			const float ttBlend = bx::fsaturate(easeBlend(particle.life) );
-			const float ttRgba  = bx::fsaturate(easeRgba(particle.life) );
-
-			float p0[3];
-			bx::vec3Lerp(p0, particle.start, particle.end[0], ttPos);
-
-			float p1[3];
-			bx::vec3Lerp(p1, particle.end[0], particle.end[1], ttPos);
-
-			float pos[3];
-			bx::vec3Lerp(pos, p0, p1, ttPos);
-
-			ParticleSort& sort = _outSort[current];
-			float tmp[3];
-			bx::vec3Sub(tmp, _eye, pos);
-			sort.dist = bx::fsqrt(bx::vec3Dot(tmp, tmp) );
-			sort.idx  = current;
-
-			uint32_t idx = uint32_t(ttRgba*4);
-			float ttmod = bx::fmod(ttRgba, 0.25f)/0.25f;
-			uint32_t rgbaStart = particle.rgba[idx];
-			uint32_t rgbaEnd   = particle.rgba[idx+1];
-
-			float rr = bx::flerp( ( (uint8_t*)&rgbaStart)[0], ( (uint8_t*)&rgbaEnd)[0], ttmod)/255.0f;
-			float gg = bx::flerp( ( (uint8_t*)&rgbaStart)[1], ( (uint8_t*)&rgbaEnd)[1], ttmod)/255.0f;
-			float bb = bx::flerp( ( (uint8_t*)&rgbaStart)[2], ( (uint8_t*)&rgbaEnd)[2], ttmod)/255.0f;
-			float aa = bx::flerp( ( (uint8_t*)&rgbaStart)[3], ( (uint8_t*)&rgbaEnd)[3], ttmod)/255.0f;
-
-			float blend = bx::flerp(particle.blendStart, particle.blendEnd, ttBlend);
-			float scale = bx::flerp(particle.scaleStart, particle.scaleEnd, ttScale);
-
-			uint32_t abgr = toAbgr(rr, gg, bb, aa);
-
-			float udir[3] = { _mtxView[0]*scale, _mtxView[4]*scale, _mtxView[8]*scale };
-			float vdir[3] = { _mtxView[1]*scale, _mtxView[5]*scale, _mtxView[9]*scale };
-
-			PosColorTexCoord0Vertex* vertex = &_outVertices[current*4];
-			bx::vec3Sub(tmp, pos, udir);
-			bx::vec3Sub(&vertex->m_x, tmp, vdir);
-			vertex->m_abgr  = abgr;
-			vertex->m_u     = 0.0f;
-			vertex->m_v     = 0.0f;
-			vertex->m_blend = blend;
-			++vertex;
-
-			bx::vec3Add(tmp, pos, udir);
-			bx::vec3Sub(&vertex->m_x, tmp, vdir);
-			vertex->m_abgr  = abgr;
-			vertex->m_u     = 1.0f;
-			vertex->m_v     = 0.0f;
-			vertex->m_blend = blend;
-			++vertex;
-
-			bx::vec3Add(tmp, pos, udir);
-			bx::vec3Add(&vertex->m_x, tmp, vdir);
-			vertex->m_abgr  = abgr;
-			vertex->m_u     = 1.0f;
-			vertex->m_v     = 1.0f;
-			vertex->m_blend = blend;
-			++vertex;
-
-			bx::vec3Sub(tmp, pos, udir);
-			bx::vec3Add(&vertex->m_x, tmp, vdir);
-			vertex->m_abgr  = abgr;
-			vertex->m_u     = 0.0f;
-			vertex->m_v     = 1.0f;
-			vertex->m_blend = blend;
-			++vertex;
-		}
-
-		return m_num;
-	}
-
-	EmitterShape::Enum     m_shape;
-	EmitterDirection::Enum m_direction;
-
-	float           m_dt;
-	bx::RngMwc      m_rng;
-	EmitterUniforms m_uniforms;
-
-	Particle* m_particles;
-	uint32_t m_num;
-	uint32_t m_max;
-};
-
 void EmitterUniforms::reset()
 {
 	m_position[0] = 0.0f;
@@ -420,234 +140,535 @@ void EmitterUniforms::reset()
 	m_easeScale = bx::Easing::Linear;
 }
 
-static int32_t particleSortFn(const void* _lhs, const void* _rhs)
+namespace ps
 {
-	const ParticleSort& lhs = *(const ParticleSort*)_lhs;
-	const ParticleSort& rhs = *(const ParticleSort*)_rhs;
-	return lhs.dist > rhs.dist ? -1 : 1;
-}
-
-struct ParticleSystem
-{
-	void init(bx::AllocatorI* _allocator)
+	struct Particle
 	{
-		m_allocator = _allocator;
-
-#if BX_CONFIG_ALLOCATOR_CRT
-		if (NULL == _allocator)
-		{
-			static bx::CrtAllocator allocator;
-			m_allocator = &allocator;
-		}
-#endif // BX_CONFIG_ALLOCATOR_CRT
+		float start[3];
+		float end[2][3];
+		float blendStart;
+		float blendEnd;
+		float scaleStart;
+		float scaleEnd;
 
-		PosColorTexCoord0Vertex::init();
+		uint32_t rgba[5];
 
-		m_num = 0;
+		float life;
+		float lifeSpan;
+	};
 
-		s_texColor = bgfx::createUniform("s_texColor", bgfx::UniformType::Int1);
-		m_particleTexture = loadTexture("textures/particle.ktx");
+	struct ParticleSort
+	{
+		float    dist;
+		uint32_t idx;
+	};
 
-		bgfx::RendererType::Enum type = bgfx::getRendererType();
-		m_particleProgram = bgfx::createProgram(
-			  bgfx::createEmbeddedShader(s_embeddedShaders, type, "vs_particle")
-			, bgfx::createEmbeddedShader(s_embeddedShaders, type, "fs_particle")
-			, true
-			);
+	inline uint32_t toAbgr(const float* _rgba)
+	{
+		return 0
+			| (uint8_t(_rgba[0]*255.0f)<< 0)
+			| (uint8_t(_rgba[1]*255.0f)<< 8)
+			| (uint8_t(_rgba[2]*255.0f)<<16)
+			| (uint8_t(_rgba[3]*255.0f)<<24)
+			;
 	}
 
-	void shutdown()
+	inline uint32_t toAbgr(float _rr, float _gg, float _bb, float _aa)
 	{
-		bgfx::destroyProgram(m_particleProgram);
-		bgfx::destroyTexture(m_particleTexture);
-		bgfx::destroyUniform(s_texColor);
-
-		m_allocator = NULL;
+		return 0
+			| (uint8_t(_rr*255.0f)<< 0)
+			| (uint8_t(_gg*255.0f)<< 8)
+			| (uint8_t(_bb*255.0f)<<16)
+			| (uint8_t(_aa*255.0f)<<24)
+			;
 	}
 
-	void update(float _dt)
+	struct Emitter
 	{
-		uint32_t numParticles = 0;
-		for (uint16_t ii = 0, num = m_emitterAlloc.getNumHandles(); ii < num; ++ii)
+		void create(EmitterShape::Enum _shape, EmitterDirection::Enum _direction, uint32_t _maxParticles);
+		void destroy();
+
+		void reset()
 		{
-			const uint16_t idx = m_emitterAlloc.getHandleAt(ii);
-			Emitter& emitter = m_emitter[idx];
-			emitter.update(_dt);
-			numParticles += emitter.m_num;
+			m_num = 0;
 		}
 
-		m_num = numParticles;
-	}
+		void update(float _dt)
+		{
+			uint32_t num = m_num;
+			for (uint32_t ii = 0; ii < num; ++ii)
+			{
+				Particle& particle = m_particles[ii];
+				particle.life += _dt * 1.0f/particle.lifeSpan;
 
-	void render(uint8_t _view, const float* _mtxView, const float* _eye)
-	{
-		if (0 != m_num)
+				if (particle.life > 1.0f)
+				{
+					if (ii != num-1)
+					{
+						memcpy(&particle, &m_particles[num-1], sizeof(Particle) );
+						--ii;
+					}
+
+					--num;
+				}
+			}
+
+			m_num = num;
+
+			if (0 < m_uniforms.m_particlesPerSecond)
+			{
+				spawn(_dt);
+			}
+		}
+
+		void spawn(float _dt)
 		{
-			bgfx::TransientVertexBuffer tvb;
-			bgfx::TransientIndexBuffer tib;
-
-			const uint32_t numVertices = bgfx::getAvailTransientVertexBuffer(m_num*4, PosColorTexCoord0Vertex::ms_decl);
-			const uint32_t numIndices  = bgfx::getAvailTransientIndexBuffer(m_num*6);
-			const uint32_t max = bx::uint32_min(numVertices/4, numIndices/6);
-			BX_WARN(m_num == max
-				, "Truncating transient buffer for particles to maximum available (requested %d, available %d)."
-				, m_num
-				, max
+			float mtx[16];
+			bx::mtxSRT(mtx
+				, 1.0f, 1.0f, 1.0f
+				, m_uniforms.m_angle[0],    m_uniforms.m_angle[1],    m_uniforms.m_angle[2]
+				, m_uniforms.m_position[0], m_uniforms.m_position[1], m_uniforms.m_position[2]
 				);
 
-			if (0 < max)
+			const float timePerParticle = 1.0f/m_uniforms.m_particlesPerSecond;
+			m_dt += _dt;
+			const uint32_t numParticles = uint32_t(m_dt / timePerParticle);
+			m_dt -= numParticles * timePerParticle;
+
+			float time = 0.0f;
+			for (uint32_t ii = 0
+				; ii < numParticles && m_num < m_max
+				; ++ii
+				)
 			{
-				bgfx::allocTransientBuffers(&tvb
-					, PosColorTexCoord0Vertex::ms_decl
-					, max*4
-					, &tib
-					, max*6
-					);
-				PosColorTexCoord0Vertex* vertices = (PosColorTexCoord0Vertex*)tvb.data;
+				Particle& particle = m_particles[m_num];
+				m_num++;
 
-				ParticleSort* particleSort = (ParticleSort*)BX_ALLOC(m_allocator, max*sizeof(ParticleSort) );
+				const float up[3] = { 0.0f, 1.0f, 0.0f };
 
-				uint32_t pos = 0;
-				for (uint16_t ii = 0, numEmitters = m_emitterAlloc.getNumHandles(); ii < numEmitters; ++ii)
+				float pos[3];
+				switch (m_shape)
 				{
-					const uint16_t idx = m_emitterAlloc.getHandleAt(ii);
-					const Emitter& emitter = m_emitter[idx];
-					pos += emitter.render(_mtxView, _eye, pos, max, particleSort, vertices);
+					default:
+					case EmitterShape::Sphere:
+						bx::randUnitSphere(pos, &m_rng);
+						break;
+
+					case EmitterShape::Hemisphere:
+						bx::randUnitHemisphere(pos, &m_rng, up);
+						break;
+
+					case EmitterShape::Circle:
+						bx::randUnitCircle(pos, &m_rng);
+						break;
+
+					case EmitterShape::Disc:
+						{
+							float tmp[3];
+							bx::randUnitCircle(tmp, &m_rng);
+							bx::vec3Mul(pos, tmp, bx::frnd(&m_rng) );
+						}
+						break;
+
+					case EmitterShape::Rect:
+						pos[0] = bx::frndh(&m_rng);
+						pos[1] = 0.0f;
+						pos[2] = bx::frndh(&m_rng);
+						break;
 				}
 
-				qsort(particleSort
-					, max
-					, sizeof(ParticleSort)
-					, particleSortFn
-					);
-
-				uint16_t* indices = (uint16_t*)tib.data;
-				for (uint32_t ii = 0; ii < max; ++ii)
+				float dir[3];
+				switch (m_direction)
 				{
-					const ParticleSort& sort = particleSort[ii];
-					uint16_t* index = &indices[ii*6];
-					uint16_t idx = (uint16_t)sort.idx;
-					index[0] = idx*4+0;
-					index[1] = idx*4+1;
-					index[2] = idx*4+2;
-					index[3] = idx*4+2;
-					index[4] = idx*4+3;
-					index[5] = idx*4+0;
+					default:
+					case EmitterDirection::Up:
+						bx::vec3Move(dir, up);
+						break;
+
+					case EmitterDirection::Outward:
+						bx::vec3Norm(dir, pos);
+						break;
 				}
 
-				BX_FREE(m_allocator, particleSort);
+				float start[3];
+				float end[3];
+				const float startOffset = bx::flerp(m_uniforms.m_offsetStart[0], m_uniforms.m_offsetStart[1], bx::frnd(&m_rng) );
+				bx::vec3Mul(start, pos, startOffset);
 
-				bgfx::setState(0
-					| BGFX_STATE_RGB_WRITE
-					| BGFX_STATE_ALPHA_WRITE
-					| BGFX_STATE_DEPTH_TEST_LESS
-					| BGFX_STATE_CULL_CW
-					| BGFX_STATE_BLEND_NORMAL
-					);
-				bgfx::setVertexBuffer(&tvb);
-				bgfx::setIndexBuffer(&tib);
-				bgfx::setTexture(0, s_texColor, m_particleTexture);
-				bgfx::submit(_view, m_particleProgram);
+				const float endOffset = bx::flerp(m_uniforms.m_offsetEnd[0], m_uniforms.m_offsetEnd[1], bx::frnd(&m_rng) );
+				float tmp1[3];
+				bx::vec3Mul(tmp1, dir, endOffset);
+				bx::vec3Add(end, tmp1, start);
+
+				particle.life = time;
+				particle.lifeSpan = bx::flerp(m_uniforms.m_lifeSpan[0], m_uniforms.m_lifeSpan[1], bx::frnd(&m_rng) );
+
+				float gravity[3] = { 0.0f, -9.81f * m_uniforms.m_gravityScale * bx::fsq(particle.lifeSpan), 0.0f };
+
+				bx::vec3MulMtx(particle.start,  start, mtx);
+				bx::vec3MulMtx(particle.end[0], end,   mtx);
+				bx::vec3Add(particle.end[1], particle.end[0], gravity);
+
+				memcpy(particle.rgba, m_uniforms.m_rgba, BX_COUNTOF(m_uniforms.m_rgba)*sizeof(uint32_t) );
+
+				particle.blendStart = bx::flerp(m_uniforms.m_blendStart[0], m_uniforms.m_blendStart[1], bx::frnd(&m_rng) );
+				particle.blendEnd   = bx::flerp(m_uniforms.m_blendEnd[0],   m_uniforms.m_blendEnd[1],   bx::frnd(&m_rng) );
+
+				particle.scaleStart = bx::flerp(m_uniforms.m_scaleStart[0], m_uniforms.m_scaleStart[1], bx::frnd(&m_rng) );
+				particle.scaleEnd   = bx::flerp(m_uniforms.m_scaleEnd[0],   m_uniforms.m_scaleEnd[1],   bx::frnd(&m_rng) );
+
+				time += timePerParticle;
+			}
+		}
+
+		uint32_t render(const float* _mtxView, const float* _eye, uint32_t _first, uint32_t _max, ParticleSort* _outSort, PosColorTexCoord0Vertex* _outVertices) const
+		{
+			bx::EaseFn easeRgba  = s_easeFunc[m_uniforms.m_easeRgba];
+			bx::EaseFn easePos   = s_easeFunc[m_uniforms.m_easePos];
+			bx::EaseFn easeBlend = s_easeFunc[m_uniforms.m_easeBlend];
+			bx::EaseFn easeScale = s_easeFunc[m_uniforms.m_easeScale];
+
+			for (uint32_t jj = 0, num = m_num, current = _first
+				; jj < num && current < _max
+				; ++jj, ++current
+				)
+			{
+				const Particle& particle = m_particles[jj];
+
+				const float ttPos   = easePos(particle.life);
+				const float ttScale = easeScale(particle.life);
+				const float ttBlend = bx::fsaturate(easeBlend(particle.life) );
+				const float ttRgba  = bx::fsaturate(easeRgba(particle.life) );
+
+				float p0[3];
+				bx::vec3Lerp(p0, particle.start, particle.end[0], ttPos);
+
+				float p1[3];
+				bx::vec3Lerp(p1, particle.end[0], particle.end[1], ttPos);
+
+				float pos[3];
+				bx::vec3Lerp(pos, p0, p1, ttPos);
+
+				ParticleSort& sort = _outSort[current];
+				float tmp[3];
+				bx::vec3Sub(tmp, _eye, pos);
+				sort.dist = bx::fsqrt(bx::vec3Dot(tmp, tmp) );
+				sort.idx  = current;
+
+				uint32_t idx = uint32_t(ttRgba*4);
+				float ttmod = bx::fmod(ttRgba, 0.25f)/0.25f;
+				uint32_t rgbaStart = particle.rgba[idx];
+				uint32_t rgbaEnd   = particle.rgba[idx+1];
+
+				float rr = bx::flerp( ( (uint8_t*)&rgbaStart)[0], ( (uint8_t*)&rgbaEnd)[0], ttmod)/255.0f;
+				float gg = bx::flerp( ( (uint8_t*)&rgbaStart)[1], ( (uint8_t*)&rgbaEnd)[1], ttmod)/255.0f;
+				float bb = bx::flerp( ( (uint8_t*)&rgbaStart)[2], ( (uint8_t*)&rgbaEnd)[2], ttmod)/255.0f;
+				float aa = bx::flerp( ( (uint8_t*)&rgbaStart)[3], ( (uint8_t*)&rgbaEnd)[3], ttmod)/255.0f;
+
+				float blend = bx::flerp(particle.blendStart, particle.blendEnd, ttBlend);
+				float scale = bx::flerp(particle.scaleStart, particle.scaleEnd, ttScale);
+
+				uint32_t abgr = toAbgr(rr, gg, bb, aa);
+
+				float udir[3] = { _mtxView[0]*scale, _mtxView[4]*scale, _mtxView[8]*scale };
+				float vdir[3] = { _mtxView[1]*scale, _mtxView[5]*scale, _mtxView[9]*scale };
+
+				PosColorTexCoord0Vertex* vertex = &_outVertices[current*4];
+				bx::vec3Sub(tmp, pos, udir);
+				bx::vec3Sub(&vertex->m_x, tmp, vdir);
+				vertex->m_abgr  = abgr;
+				vertex->m_u     = 0.0f;
+				vertex->m_v     = 0.0f;
+				vertex->m_blend = blend;
+				++vertex;
+
+				bx::vec3Add(tmp, pos, udir);
+				bx::vec3Sub(&vertex->m_x, tmp, vdir);
+				vertex->m_abgr  = abgr;
+				vertex->m_u     = 1.0f;
+				vertex->m_v     = 0.0f;
+				vertex->m_blend = blend;
+				++vertex;
+
+				bx::vec3Add(tmp, pos, udir);
+				bx::vec3Add(&vertex->m_x, tmp, vdir);
+				vertex->m_abgr  = abgr;
+				vertex->m_u     = 1.0f;
+				vertex->m_v     = 1.0f;
+				vertex->m_blend = blend;
+				++vertex;
+
+				bx::vec3Sub(tmp, pos, udir);
+				bx::vec3Add(&vertex->m_x, tmp, vdir);
+				vertex->m_abgr  = abgr;
+				vertex->m_u     = 0.0f;
+				vertex->m_v     = 1.0f;
+				vertex->m_blend = blend;
+				++vertex;
 			}
+
+			return m_num;
 		}
+
+		EmitterShape::Enum     m_shape;
+		EmitterDirection::Enum m_direction;
+
+		float           m_dt;
+		bx::RngMwc      m_rng;
+		EmitterUniforms m_uniforms;
+
+		Particle* m_particles;
+		uint32_t m_num;
+		uint32_t m_max;
+	};
+
+	static int32_t particleSortFn(const void* _lhs, const void* _rhs)
+	{
+		const ParticleSort& lhs = *(const ParticleSort*)_lhs;
+		const ParticleSort& rhs = *(const ParticleSort*)_rhs;
+		return lhs.dist > rhs.dist ? -1 : 1;
 	}
 
-	EmitterHandle createEmitter(EmitterShape::Enum _shape, EmitterDirection::Enum _direction, uint32_t _maxParticles)
+	struct ParticleSystem
 	{
-		EmitterHandle handle = { m_emitterAlloc.alloc() };
+		void init(uint16_t _maxEmitters, bx::AllocatorI* _allocator)
+		{
+			m_allocator = _allocator;
+
+#if BX_CONFIG_ALLOCATOR_CRT
+			if (NULL == _allocator)
+			{
+				static bx::CrtAllocator allocator;
+				m_allocator = &allocator;
+			}
+#endif // BX_CONFIG_ALLOCATOR_CRT
+
+			m_emitterAlloc = bx::createHandleAlloc(m_allocator, _maxEmitters);
+			m_emitter = (Emitter*)BX_ALLOC(m_allocator, sizeof(Emitter)*_maxEmitters);
+
+			PosColorTexCoord0Vertex::init();
+
+			m_num = 0;
+
+			s_texColor = bgfx::createUniform("s_texColor", bgfx::UniformType::Int1);
+			m_particleTexture = loadTexture("textures/particle.ktx");
 
-		if (UINT16_MAX != handle.idx)
+			bgfx::RendererType::Enum type = bgfx::getRendererType();
+			m_particleProgram = bgfx::createProgram(
+				bgfx::createEmbeddedShader(s_embeddedShaders, type, "vs_particle")
+				, bgfx::createEmbeddedShader(s_embeddedShaders, type, "fs_particle")
+				, true
+				);
+		}
+
+		void shutdown()
 		{
-			m_emitter[handle.idx].create(_shape, _direction, _maxParticles);
+			bgfx::destroyProgram(m_particleProgram);
+			bgfx::destroyTexture(m_particleTexture);
+			bgfx::destroyUniform(s_texColor);
+
+			bx::destroyHandleAlloc(m_allocator, m_emitterAlloc);
+			BX_FREE(m_allocator, m_emitter);
+
+			m_allocator = NULL;
 		}
 
-		return handle;
-	}
+		void update(float _dt)
+		{
+			uint32_t numParticles = 0;
+			for (uint16_t ii = 0, num = m_emitterAlloc->getNumHandles(); ii < num; ++ii)
+			{
+				const uint16_t idx = m_emitterAlloc->getHandleAt(ii);
+				Emitter& emitter = m_emitter[idx];
+				emitter.update(_dt);
+				numParticles += emitter.m_num;
+			}
 
-	void updateEmitter(EmitterHandle _handle, const EmitterUniforms* _uniforms)
-	{
-		Emitter& emitter = m_emitter[_handle.idx];
+			m_num = numParticles;
+		}
 
-		if (NULL == _uniforms)
+		void render(uint8_t _view, const float* _mtxView, const float* _eye)
 		{
-			emitter.reset();
+			if (0 != m_num)
+			{
+				bgfx::TransientVertexBuffer tvb;
+				bgfx::TransientIndexBuffer tib;
+
+				const uint32_t numVertices = bgfx::getAvailTransientVertexBuffer(m_num*4, PosColorTexCoord0Vertex::ms_decl);
+				const uint32_t numIndices  = bgfx::getAvailTransientIndexBuffer(m_num*6);
+				const uint32_t max = bx::uint32_min(numVertices/4, numIndices/6);
+				BX_WARN(m_num == max
+					, "Truncating transient buffer for particles to maximum available (requested %d, available %d)."
+					, m_num
+					, max
+					);
+
+				if (0 < max)
+				{
+					bgfx::allocTransientBuffers(&tvb
+						, PosColorTexCoord0Vertex::ms_decl
+						, max*4
+						, &tib
+						, max*6
+						);
+					PosColorTexCoord0Vertex* vertices = (PosColorTexCoord0Vertex*)tvb.data;
+
+					ParticleSort* particleSort = (ParticleSort*)BX_ALLOC(m_allocator, max*sizeof(ParticleSort) );
+
+					uint32_t pos = 0;
+					for (uint16_t ii = 0, numEmitters = m_emitterAlloc->getNumHandles(); ii < numEmitters; ++ii)
+					{
+						const uint16_t idx = m_emitterAlloc->getHandleAt(ii);
+						const Emitter& emitter = m_emitter[idx];
+						pos += emitter.render(_mtxView, _eye, pos, max, particleSort, vertices);
+					}
+
+					qsort(particleSort
+						, max
+						, sizeof(ParticleSort)
+						, particleSortFn
+						);
+
+					uint16_t* indices = (uint16_t*)tib.data;
+					for (uint32_t ii = 0; ii < max; ++ii)
+					{
+						const ParticleSort& sort = particleSort[ii];
+						uint16_t* index = &indices[ii*6];
+						uint16_t idx = (uint16_t)sort.idx;
+						index[0] = idx*4+0;
+						index[1] = idx*4+1;
+						index[2] = idx*4+2;
+						index[3] = idx*4+2;
+						index[4] = idx*4+3;
+						index[5] = idx*4+0;
+					}
+
+					BX_FREE(m_allocator, particleSort);
+
+					bgfx::setState(0
+						| BGFX_STATE_RGB_WRITE
+						| BGFX_STATE_ALPHA_WRITE
+						| BGFX_STATE_DEPTH_TEST_LESS
+						| BGFX_STATE_CULL_CW
+						| BGFX_STATE_BLEND_NORMAL
+						);
+					bgfx::setVertexBuffer(&tvb);
+					bgfx::setIndexBuffer(&tib);
+					bgfx::setTexture(0, s_texColor, m_particleTexture);
+					bgfx::submit(_view, m_particleProgram);
+				}
+			}
 		}
-		else
+
+		EmitterHandle createEmitter(EmitterShape::Enum _shape, EmitterDirection::Enum _direction, uint32_t _maxParticles)
 		{
-			memcpy(&emitter.m_uniforms, _uniforms, sizeof(EmitterUniforms) );
+			EmitterHandle handle = { m_emitterAlloc->alloc() };
+
+			if (UINT16_MAX != handle.idx)
+			{
+				m_emitter[handle.idx].create(_shape, _direction, _maxParticles);
+			}
+
+			return handle;
 		}
-	}
 
-	void destroyEmitter(EmitterHandle _handle)
-	{
-		m_emitter[_handle.idx].destroy();
-		m_emitterAlloc.free(_handle.idx);
-	}
+		void updateEmitter(EmitterHandle _handle, const EmitterUniforms* _uniforms)
+		{
+			BX_CHECK(m_emitterAlloc.isValid(_handle.idx)
+				, "destroyEmitter handle %d is not valid."
+				, _handle.idx
+				);
+
+			Emitter& emitter = m_emitter[_handle.idx];
 
-	bx::AllocatorI* m_allocator;
+			if (NULL == _uniforms)
+			{
+				emitter.reset();
+			}
+			else
+			{
+				memcpy(&emitter.m_uniforms, _uniforms, sizeof(EmitterUniforms) );
+			}
+		}
 
-#define MAX_EMITTERS 64
-	bx::HandleAllocT<MAX_EMITTERS> m_emitterAlloc;
-	Emitter m_emitter[MAX_EMITTERS];
+		void destroyEmitter(EmitterHandle _handle)
+		{
+			BX_CHECK(m_emitterAlloc.isValid(_handle.idx)
+				, "destroyEmitter handle %d is not valid."
+				, _handle.idx
+				);
 
-	bgfx::UniformHandle s_texColor;
-	bgfx::TextureHandle m_particleTexture;
-	bgfx::ProgramHandle m_particleProgram;
+			m_emitter[_handle.idx].destroy();
+			m_emitterAlloc->free(_handle.idx);
+		}
 
-	uint32_t m_num;
-};
+		bx::AllocatorI* m_allocator;
 
-static ParticleSystem s_ps;
+		bx::HandleAlloc* m_emitterAlloc;
+		Emitter* m_emitter;
 
-void Emitter::create(EmitterShape::Enum _shape, EmitterDirection::Enum _direction, uint32_t _maxParticles)
-{
-	m_dt = 0.0f;
-	m_uniforms.reset();
-	m_shape     = _shape;
-	m_direction = _direction;
-
-	m_num = 0;
-	m_max = _maxParticles;
-	m_particles = (Particle*)BX_ALLOC(s_ps.m_allocator, m_max*sizeof(Particle) );
-}
+		bgfx::UniformHandle s_texColor;
+		bgfx::TextureHandle m_particleTexture;
+		bgfx::ProgramHandle m_particleProgram;
 
-void Emitter::destroy()
-{
-	BX_FREE(s_ps.m_allocator, m_particles);
-	m_particles = NULL;
-}
+		uint32_t m_num;
+	};
+
+	static ParticleSystem s_ctx;
+
+	void Emitter::create(EmitterShape::Enum _shape, EmitterDirection::Enum _direction, uint32_t _maxParticles)
+	{
+		m_dt = 0.0f;
+		m_uniforms.reset();
+		m_shape     = _shape;
+		m_direction = _direction;
+
+		m_num = 0;
+		m_max = _maxParticles;
+		m_particles = (Particle*)BX_ALLOC(s_ctx.m_allocator, m_max*sizeof(Particle) );
+	}
+
+	void Emitter::destroy()
+	{
+		BX_FREE(s_ctx.m_allocator, m_particles);
+		m_particles = NULL;
+	}
+
+} // namespace ps
+
+using namespace ps;
 
-void psInit(bx::AllocatorI* _allocator)
+void psInit(uint16_t _maxEmitters, bx::AllocatorI* _allocator)
 {
-	s_ps.init(_allocator);
+	s_ctx.init(_maxEmitters, _allocator);
 }
 
 void psShutdown()
 {
-	s_ps.shutdown();
+	s_ctx.shutdown();
 }
 
 EmitterHandle psCreateEmitter(EmitterShape::Enum _shape, EmitterDirection::Enum _direction, uint32_t _maxParticles)
 {
-	return s_ps.createEmitter(_shape, _direction, _maxParticles);
+	return s_ctx.createEmitter(_shape, _direction, _maxParticles);
 }
 
 void psUpdateEmitter(EmitterHandle _handle, const EmitterUniforms* _uniforms)
 {
-	s_ps.updateEmitter(_handle, _uniforms);
+	s_ctx.updateEmitter(_handle, _uniforms);
 }
 
 void psDestroyEmitter(EmitterHandle _handle)
 {
-	s_ps.destroyEmitter(_handle);
+	s_ctx.destroyEmitter(_handle);
 }
 
 void psUpdate(float _dt)
 {
-	s_ps.update(_dt);
+	s_ctx.update(_dt);
 }
 
 void psRender(uint8_t _view, const float* _mtxView, const float* _eye)
 {
-	s_ps.render(_view, _mtxView, _eye);
+	s_ctx.render(_view, _mtxView, _eye);
 }

+ 1 - 1
examples/common/ps/particle_system.h

@@ -63,7 +63,7 @@ struct EmitterUniforms
 struct EmitterHandle { uint16_t idx; };
 
 ///
-void psInit(bx::AllocatorI* _allocator = NULL);
+void psInit(uint16_t _maxEmitters = 64, bx::AllocatorI* _allocator = NULL);
 
 ///
 void psShutdown();