// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

#ifndef __GBUFFER__
#define __GBUFFER__

#include "./Flax/GBufferCommon.hlsl"

#if !defined(NO_GBUFFER_SAMPLING)

// GBuffer
Texture2D GBuffer0 : register(t0);
Texture2D GBuffer1 : register(t1);
Texture2D GBuffer2 : register(t2);
Texture2D Depth : register(t3);
#if defined(USE_GBUFFER_CUSTOM_DATA)
Texture2D GBuffer3 : register(t4);
#endif

// GBuffer Layout:
// GBuffer0 = [RGB] Color, [A] AO 
// GBuffer1 = [RGB] Normal, [A] ShadingModel 
// GBuffer2 = [R] Roughness, [G] Metalness, [B] Specular, [A] UNUSED
// GBuffer3 = [RGBA] Custom Data (per shading mode)

#endif

// Linearize raw device depth
float LinearizeZ(GBufferData gBuffer, float depth)
{
    return gBuffer.ViewInfo.w / (depth - gBuffer.ViewInfo.z);
}

// Convert linear depth to device depth
float LinearZ2DeviceDepth(GBufferData gBuffer, float linearDepth)
{
    return (gBuffer.ViewInfo.w / linearDepth) + gBuffer.ViewInfo.z;
}

// Get view space position at given pixel coordinate with given device depth
float3 GetViewPos(GBufferData gBuffer, float2 uv, float deviceDepth)
{
    float4 clipPos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), deviceDepth, 1.0);
    float4 viewPos = mul(clipPos, gBuffer.InvProjectionMatrix);
    return viewPos.xyz / viewPos.w;
}

// Get world space position at given pixel coordinate with given device depth
float3 GetWorldPos(GBufferData gBuffer, float2 uv, float deviceDepth)
{
    float3 viewPos = GetViewPos(gBuffer, uv, deviceDepth);
    return mul(float4(viewPos, 1), gBuffer.InvViewMatrix).xyz;
}

#if !defined(NO_GBUFFER_SAMPLING)

// Sample raw device depth buffer
float SampleZ(float2 uv)
{
    return SAMPLE_RT(Depth, uv).r;
}

// Sample linear depth
float SampleDepth(GBufferData gBuffer, float2 uv)
{
    float deviceDepth = SampleZ(uv);
    return LinearizeZ(gBuffer, deviceDepth);
}

// Get view space position at given pixel coordinate
float3 GetViewPos(GBufferData gBuffer, float2 uv)
{
    float deviceDepth = SampleZ(uv);
    return GetViewPos(gBuffer, uv, deviceDepth);
}

// Get world space position at given pixel coordinate
float3 GetWorldPos(GBufferData gBuffer, float2 uv)
{
    float deviceDepth = SampleZ(uv);
    return GetWorldPos(gBuffer, uv, deviceDepth);
}

// Sample normal vector with pixel shading model
float3 SampleNormal(float2 uv, out int shadingModel)
{
    // Sample GBuffer
    float4 gBuffer1 = SAMPLE_RT(GBuffer1, uv);

    // Decode normal and shading model
    shadingModel = (int)(gBuffer1.a * 3.999);
    return DecodeNormal(gBuffer1.rgb);
}

// Sample GBuffer
GBufferSample SampleGBuffer(GBufferData gBuffer, float2 uv)
{
    GBufferSample result;

    // Sample GBuffer
    float4 gBuffer0 = SAMPLE_RT(GBuffer0, uv);
    float4 gBuffer1 = SAMPLE_RT(GBuffer1, uv);
    float4 gBuffer2 = SAMPLE_RT(GBuffer2, uv);
#if defined(USE_GBUFFER_CUSTOM_DATA)
	float4 gBuffer3 = SAMPLE_RT(GBuffer3, uv);
#endif

    // Decode normal and shading model
    result.Normal = DecodeNormal(gBuffer1.rgb);
    result.ShadingModel = (int)(gBuffer1.a * 3.999);

    // Calculate view space and world space positions
    result.ViewPos = GetViewPos(gBuffer, uv);
    result.WorldPos = mul(float4(result.ViewPos, 1), gBuffer.InvViewMatrix).xyz;

    // Decode GBuffer data
    result.Color = gBuffer0.rgb;
    result.AO = gBuffer0.a;
    result.Roughness = gBuffer2.r;
    result.Metalness = gBuffer2.g;
    result.Specular = gBuffer2.b;
#if defined(USE_GBUFFER_CUSTOM_DATA)
	result.CustomData = gBuffer3;
#endif

    return result;
}

// Sample GBuffer (fast - only few parameters are being sampled)
GBufferSample SampleGBufferFast(GBufferData gBuffer, float2 uv)
{
    GBufferSample result;

    // Sample GBuffer
    float4 gBuffer1 = SAMPLE_RT(GBuffer1, uv);

    // Decode normal and shading model
    result.Normal = DecodeNormal(gBuffer1.rgb);
    result.ShadingModel = (int)(gBuffer1.a * 3.999);

    // Calculate view space position
    result.ViewPos = GetViewPos(gBuffer, uv);
    result.WorldPos = mul(float4(result.ViewPos, 1), gBuffer.InvViewMatrix).xyz;

    return result;
}

#if defined(USE_GBUFFER_CUSTOM_DATA)

// Sample GBuffer custom data only
float4 SampleGBufferCustomData(float2 uv)
{
	return SAMPLE_RT(GBuffer3, uv);
}

#endif

#endif

#endif
