#version 450
#pragma shader_stage(fragment)

#define FORWARD 0
#define FRAGMENT 1
#define PBR 1

layout (constant_id = 0) const uint TEXTURES = 1;
layout (constant_id = 1) const uint CUBEMAPS = 128;

#include "../../common/macros.h"
#include "../../common/structs.h"

layout (binding = 6, set = 1) uniform sampler2D samplerTextures[TEXTURES];
layout (binding = 7, set = 1) uniform samplerCube samplerCubemaps[CUBEMAPS];
layout (binding = 8, set = 0) uniform sampler3D samplerNoise;
#if VXGI
	layout (binding = 9, set = 0) uniform sampler3D voxelOutput[CASCADES];
#endif
#if RT
	layout (binding = 10, set = 0) uniform accelerationStructureEXT tlas;
#endif
layout (std140, binding = 11, set = 0) readonly buffer DrawCommands { DrawCommand drawCommands[]; };
layout (std140, binding = 12, set = 0) readonly buffer Instances { Instance instances[]; };
layout (std140, binding = 13, set = 0) readonly buffer InstanceAddresseses { InstanceAddresses addresses[]; };
layout (std140, binding = 14, set = 0) readonly buffer Materials { Material materials[]; };
layout (std140, binding = 15, set = 0) readonly buffer Textures { Texture textures[]; };
layout (std140, binding = 16, set = 0) readonly buffer Lights { Light lights[]; };
layout (std140, binding = 17, set = 0) readonly buffer Objects { Object objects[]; };

layout (binding = 18) uniform Camera { Viewport viewport[2]; } camera;

// to-do: bind UBO for settings?
/*
layout (binding = 19) uniform UBO { EyeMatrices eyes[2]; Settings settings; } ubo;
*/

#include "../../common/functions.h"
//#include "../../common/light.h"
//#include "../../common/shadows.h"

layout (location = 0) flat in uvec4 inId;
layout (location = 1) flat in vec4 inPOS0;
layout (location = 2) in vec4 inPOS1;
layout (location = 3) in vec3 inPosition;
layout (location = 4) sample in vec2 inUv;
layout (location = 5) in vec4 inColor;
layout (location = 6) in vec2 inSt;
layout (location = 7) in vec3 inNormal;
layout (location = 8) in vec3 inTangent;

layout (location = 0) out vec4 outFragColor;

void main() {
	const uint triangleID = gl_PrimitiveID;
	const uint drawID = uint(inId.y);
	const uint instanceID = uint(inId.z);

#if 0

#if CAN_DISCARD
	const DrawCommand drawCommand = drawCommands[drawID];
	const Instance instance = instances[instanceID];
	const Material material = materials[instance.materialID];

	surface.uv.xy = wrap(inUv.xy);
	surface.uv.z = mipLevel(dFdx(inUv), dFdy(inUv));

	surface.st.xy = wrap(inSt.xy);
	surface.st.z = mipLevel(dFdx(inSt), dFdy(inSt));

	vec4 A = inColor * material.colorBase;  
	// sample albedo
	if ( validTextureIndex( material.indexAlbedo ) ) {
		A = sampleTexture( material.indexAlbedo );
	}
	if ( A.a < 0.0001 ) discard;
#endif

	outFragColor.rgb = A.rgb;
	outFragColor.a = A.a;
#else
	const DrawCommand drawCommand = drawCommands[drawID];
	surface.instance = instances[instanceID];
	surface.object = objects[surface.instance.objectID];
	const Material material = materials[surface.instance.materialID];

	surface.uv.xy = wrap(inUv.xy);
	surface.st.xy = wrap(inSt.xy);	
	
	surface.dUvDx = dFdx(inUv);
	surface.dUvDy = dFdy(inUv);

	surface.dStDx = dFdx(inSt);
	surface.dStDy = dFdy(inSt);

	surface.uv.z = mipLevel(surface.dUvDx, surface.dUvDy);
	surface.st.z = mipLevel(surface.dStDx, surface.dStDy);

	surface.position.world = inPosition;
	surface.position.eye = vec3( inverse( surface.object.model ) * vec4(surface.position.world, 1.0) );

	surface.normal.world = normalize(inNormal);
	surface.tangent.world = normalize(inTangent);
	vec3 bitangent = cross(surface.normal.world, surface.tangent.world);
	surface.tbn = mat3(surface.tangent.world, bitangent, surface.normal.world);

	// 
	surface.material.albedo = material.colorBase;
	surface.material.metallic = material.factorMetallic;
	surface.material.roughness = material.factorRoughness;
	surface.material.occlusion = material.factorOcclusion;
	surface.material.lightmapped = false;
	surface.light = material.colorEmissive;

	if ( validTextureIndex( material.indexAlbedo ) ) {
		surface.material.albedo *= sampleTexture( material.indexAlbedo );
	}
	// OPAQUE
	if ( material.modeAlpha == 0 ) {
		surface.material.albedo.a = 1;
	// BLEND
	} else if ( material.modeAlpha == 1 ) {

	// MASK
	} else if ( material.modeAlpha == 2 ) {

	// EMISSIVE
	} else if ( material.modeAlpha == 3 ) {
		surface.light.rgb *= surface.material.albedo.rgb * surface.material.albedo.a;
		surface.material.albedo.a = 1.0;
	}

	// Emissive mapping
	if ( validTextureIndex( material.indexEmissive ) ) {
		surface.light *= sampleTexture( material.indexEmissive );
	}
	// (Occlusion/)Metallic/Roughness map
	if ( validTextureIndex( material.indexMetallicRoughness ) ) {
		vec4 samp = sampleTexture( material.indexMetallicRoughness );

		surface.material.metallic *= samp.b;
		surface.material.roughness *= samp.g;

		if ( material.indexOcclusion == material.indexMetallicRoughness ) {
			surface.material.occlusion = mix(1.0, samp.r, material.factorOcclusion);
		}
	}
	// Occlusion mapping
	if ( validTextureIndex( material.indexOcclusion ) && material.indexOcclusion != material.indexMetallicRoughness ) {
		float occ = sampleTexture( material.indexOcclusion ).r;
		surface.material.occlusion = mix(1.0, occ, material.factorOcclusion);
	}
	// Normal mapping
	if ( validTextureIndex( material.indexNormal ) && surface.tangent.world != vec3(0) ) {
		surface.normal.world = surface.tbn * normalize( sampleTexture( material.indexNormal ).xyz * 2.0 - vec3(1.0));
	}
	{
		surface.normal.eye = normalize(vec3( inverse( surface.object.model ) * vec4(surface.normal.world, 0.0) ));
	}

	// Light mapping
	if ( /*( bool(ubo.settings.lighting.useLightmaps)) &&*/ validTextureIndex( surface.instance.lightmapID ) ) {
		surface.material.lightmapped = true; // light.a > 0.001;
		vec4 light = decodeRGBE( sampleTexture( surface.instance.lightmapID, surface.st.xy, 0.0 ) );

		const vec3 F0 = mix(vec3(0.04), surface.material.albedo.rgb, surface.material.metallic);
		const vec3 Lo = normalize(-surface.position.eye);
		const float cosLo = max(0.0, dot(surface.normal.eye, Lo));
		const vec3 F = F0 + (max(vec3(1.0 - surface.material.roughness), F0) - F0) * pow(clamp(1.0 - cosLo, 0.0, 1.0), 5.0);
		const vec3 kD = (vec3(1.0) - F) * (1.0 - surface.material.metallic);

		surface.light.rgb += (kD * surface.material.albedo.rgb * light.rgb) * surface.material.occlusion;
	}

	if ( surface.material.albedo.a < 0.0001 && dot(surface.light.rgb, vec3(1.0)) < 0.0001 ) discard;

	surface.fragment = vec4(0.0);
	surface.light.rgb += surface.material.albedo.rgb /* ubo.settings.lighting.ambient.rgb*/ * surface.material.occlusion;

/*	
#if PBR
	pbr();
#elif LAMBERT
	lambert();
#endif
*/	
	outFragColor.rgb = surface.fragment.rgb + surface.light.rgb;
	outFragColor.a = surface.material.albedo.a;
#endif
}