/**
 * This file contains the shaders (vertex, geometry and pixel) that make up the rendering pipeline used for
 * drawing our particles to the screen.
 **/

// Include common declarations
#include "Declarations.hlsl"

// This is a constant buffer that is automatically set by the Ziggurat engine for each rendering pass
cbuffer MatrixBuffer : register(b0) {
	float4x4 View;
	float4x4 Projection;
	float4x4 ViewProj;			// Precomputed on a per-frame basis
};

// This cbuffer provides the current position of the rendering camera in world space units (which
// is used to ensure all particle quads are facing the camera) as well as the current timestamp value
// (which is used to make the pixel shader fade the individual particles out near the end of their lifespan)
cbuffer PerFrameBuffer : register(b1) {
	float3	EyePos;
	uint	Timestamp;
};


// Output struct from the vertex shader (/ input to the geometry shader) stage
struct vs_out {
	uint id			: VERTEXID;
};

// Output from the geometry shader (/ input to the pixel shader) stage
struct gs_out {
	float4 pos		: SV_POSITION;
	float4 col		: COLOUR;
	float2 texcoord : TEXCOORD;
	uint   id		: PARTICLEID;
};

// The latest output particle buffer is bound as a read-only resource for use with the rendering shaders.
// When bound as a read-only buffer an append/consume buffer is really just a StructuredBuffer.
// Note that all buffers bound to rendering shaders use the eight registers after the first sixteen reserved
// by textures (in other words t16 through t23) and as such the buffer bound to "slot 0" from DBPro will end 
// up in register t16 here on the shader side.
StructuredBuffer<Particle>	ParticleData	: register(t16);
// We also require an index lookup buffer that is sorted in such a way that we will use it to index into the
// above particle buffer from the current control point ("vertex") id and it will give us the particles sorted 
// back-to-front based on their view space depth values.
Buffer<uint>				IndexLookup		: register(t17);


// Transform data is supplied by the Ziggurat engine using a special StructuredBuffer bound to register t24
struct sInstanceTransform {
	float4x4 World;
	float4x4 WorldViewProjection;
};

StructuredBuffer<sInstanceTransform> Transform : register(t24);


// The particle texture and a sampler used to sample said texture.
// The Ziggurat engine will provide a default sampler if you don't set one yourself.
Texture2D Texture : register(t0);
sampler	  Samp	  : register(s0);


/************************************************************************************************
 * RENDERING SHADERS																			*
 ************************************************************************************************/

/**
 * Vertex (control point pass-through) shader.
 **/
vs_out VS(uint id : SV_VertexId) {
	vs_out OUT;
	OUT.id = id;
	return OUT;
}

/**
 * Geometry shader (responsible for expanding each control point "vertex" into a quad used to represent a single particle).
 **/
[maxvertexcount(4)]
void GS(point vs_out input[1], inout TriangleStream<gs_out> stream) {
	gs_out v;

	// The index into the ParticleData buffer from where to read the current particle quad's data
	uint pId = IndexLookup[input[0].id];

	// Determine vertex positions (in local space) and rotate the particle quad such that it will always face the rendering camera
	float3 position = ParticleData[pId].pos;
	float3 vert[4];
	float halfSize = ParticleData[pId].size / 2;
	
	const float3 vecUp = float3(0, 1, 0);
	float3 lookAt = position - EyePos;
	lookAt = normalize(lookAt);
	float3 vecRight = cross(vecUp, lookAt);

	vert[0] = position + halfSize * (-vecRight - vecUp);
	vert[1] = position + halfSize * (-vecRight + vecUp);
	vert[2] = position + halfSize * (vecRight - vecUp);
	vert[3] = position + halfSize * (vecRight + vecUp);

	// Texture coordinates
	float2 texcoord[4];
	texcoord[0] = float2(0, 0);
	texcoord[1] = float2(0, 1);
	texcoord[2] = float2(1, 0);
	texcoord[3] = float2(1, 1);

	// Transform quad coordinates into WorldViewProjection space and output to the triangle stream
	for(uint n = 0; n < 4; n++) {
		// The Transform buffer is normally indexed by the instance id (obtainable by the vertex shader stage
		// from the input assembler using the SV_InstanceId semantic on a uint argument). In this case we know 
		// for a fact there will only ever be a single instance of the particle object (which contains all particles)
		// though, so we can just index it with a constant zero.
		v.pos		= mul(float4(vert[n], 1), Transform[0].WorldViewProjection);
		v.texcoord	= texcoord[n];
		v.col		= float4(ParticleData[pId].colour, 1);
		v.id		= pId;
		stream.Append(v);
	}
}

/**
 * Pixel shader used to apply texture and colour data
 **/
float4 PS(gs_out IN) : SV_Target0 {
	// Sample grayscale texture and blend with the colour of this particle
	float4 col = Texture.Sample(Samp, IN.texcoord) * IN.col;

	// Fade out the particle to 0 alpha as it has 1 second or less time left until it times out
	float timeDif = (ParticleData[IN.id].timeout - Timestamp) * 0.001;
	if(timeDif <= 1.0)
		col.a = lerp(col.a, 0.0, 1.0 - timeDif);

	// Ensure that the final colour value of the particles has at most 0.6 alpha to ensure all
	// particles are semi-transparent at all texels
	return col * float4(1, 1, 1, 0.6);
}

