/*
===========================================================================
Copyright (C) 2023 Gian 'myT' Schellenbaum

This file is part of Challenge Quake 3 (CNQ3).

Challenge Quake 3 is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Challenge Quake 3 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
===========================================================================
*/
// Gameplay Rendering Pipeline - post-process pass


#include "grp_local.h"
namespace tone_map
{
#include "hlsl/post_gamma_vs.h"
#include "hlsl/post_gamma_ps.h"
}
namespace inverse_tone_map
{
#include "hlsl/post_inverse_gamma_vs.h"
#include "hlsl/post_inverse_gamma_ps.h"
}


#pragma pack(push, 4)

struct GammaVertexRC
{
	float scaleX;
	float scaleY;
};

struct GammaPixelRC
{
	float invGamma;
	float brightness;
	float greyscale;
};

struct InverseGammaPixelRC
{
	float gamma;
	float invBrightness;
};

#pragma pack(pop)


void PostProcess::Init()
{
	if(!grp.firstInit)
	{
		return;
	}

	{
		RootSignatureDesc desc("tone map");
		desc.usingVertexBuffers = false;
		desc.constants[ShaderStage::Vertex].byteCount = sizeof(GammaVertexRC);
		desc.constants[ShaderStage::Pixel].byteCount = sizeof(GammaPixelRC);
		desc.samplerCount = 1;
		desc.samplerVisibility = ShaderStages::PixelBit;
		desc.AddRange(DescriptorType::Texture, 0, 1);
		desc.genericVisibility = ShaderStages::PixelBit;
		toneMapRootSignature = CreateRootSignature(desc);
	}
	{
		DescriptorTableDesc desc("tone map", toneMapRootSignature);
		toneMapDescriptorTable = CreateDescriptorTable(desc);

		DescriptorTableUpdate update;
		update.SetSamplers(1, &grp.samplers[GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear)]);
		UpdateDescriptorTable(toneMapDescriptorTable, update);
	}
	{
		GraphicsPipelineDesc desc("tone map", toneMapRootSignature);
		desc.vertexShader = ShaderByteCode(tone_map::g_vs);
		desc.pixelShader = ShaderByteCode(tone_map::g_ps);
		desc.depthStencil.DisableDepth();
		desc.rasterizer.cullMode = CT_TWO_SIDED;
		desc.AddRenderTarget(0, TextureFormat::RGBA32_UNorm);
		toneMapPipeline = CreateGraphicsPipeline(desc);
	}

	{
		RootSignatureDesc desc("inverse tone map");
		desc.usingVertexBuffers = false;
		desc.constants[ShaderStage::Vertex].byteCount = 0;
		desc.constants[ShaderStage::Pixel].byteCount = sizeof(InverseGammaPixelRC);
		desc.samplerCount = 1;
		desc.samplerVisibility = ShaderStages::PixelBit;
		desc.AddRange(DescriptorType::Texture, 0, 1);
		desc.genericVisibility = ShaderStages::PixelBit;
		inverseToneMapRootSignature = CreateRootSignature(desc);
	}
	{
		DescriptorTableDesc desc("inverse tone map", inverseToneMapRootSignature);
		inverseToneMapDescriptorTable = CreateDescriptorTable(desc);

		DescriptorTableUpdate update;
		update.SetSamplers(1, &grp.samplers[GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear)]);
		UpdateDescriptorTable(inverseToneMapDescriptorTable, update);
	}
	{
		GraphicsPipelineDesc desc("inverse tone map", inverseToneMapRootSignature);
		desc.vertexShader = ShaderByteCode(inverse_tone_map::g_vs);
		desc.pixelShader = ShaderByteCode(inverse_tone_map::g_ps);
		desc.depthStencil.DisableDepth();
		desc.rasterizer.cullMode = CT_TWO_SIDED;
		desc.AddRenderTarget(0, TextureFormat::RGBA32_UNorm);
		inverseToneMapPipeline = CreateGraphicsPipeline(desc);
	}
}

void PostProcess::Draw()
{
	SCOPED_RENDER_PASS("Post-process", 0.125f, 0.125f, 0.5f);

	const HTexture swapChain = GetSwapChainTexture();
	const TextureBarrier barriers[2] =
	{
		TextureBarrier(grp.renderTarget, ResourceStates::PixelShaderAccessBit),
		TextureBarrier(swapChain, ResourceStates::RenderTargetBit)
	};
	CmdBarrier(ARRAY_LEN(barriers), barriers);

	float vsX = 1.0f; // vertex shader scale factors
	float vsY = 1.0f;
	float srX = 1.0f; // scissor rectangle scale factors
	float srY = 1.0f;
	if(r_fullscreen->integer == 1 && r_mode->integer == VIDEOMODE_UPSCALE)
	{
		if(r_blitMode->integer == BLITMODE_CENTERED)
		{
			vsX = (float)glConfig.vidWidth / (float)glInfo.winWidth;
			vsY = (float)glConfig.vidHeight / (float)glInfo.winHeight;
		}
		else if(r_blitMode->integer == BLITMODE_ASPECT)
		{
			const float ars = (float)glConfig.vidWidth / (float)glConfig.vidHeight;
			const float ard = (float)glInfo.winWidth / (float)glInfo.winHeight;
			if(ard > ars)
			{
				vsX = ars / ard;
				vsY = 1.0f;
				srX = (float)glInfo.winHeight / (float)glConfig.vidHeight;
				srY = srX;
			}
			else
			{
				vsX = 1.0f;
				vsY = ard / ars;
				srX = (float)glInfo.winWidth / (float)glConfig.vidWidth;
				srY = srX;
			}
		}
	}

	if(vsX != 1.0f || vsY != 1.0f)
	{
		CmdClearColorTarget(swapChain, colorBlack);

		const int x = (glInfo.winWidth - glInfo.winWidth * vsX) / 2.0f;
		const int y = (glInfo.winHeight - glInfo.winHeight * vsY) / 2.0f;
		CmdSetViewport(0, 0, glInfo.winWidth, glInfo.winHeight);
		CmdSetScissor(x, y, glConfig.vidWidth * srX, glConfig.vidHeight * srY);
	}
	else
	{
		CmdSetViewportAndScissor(0, 0, glInfo.winWidth, glInfo.winHeight);
	}

	GammaVertexRC vertexRC = {};
	vertexRC.scaleX = vsX;
	vertexRC.scaleY = vsY;

	GammaPixelRC pixelRC = {};
	pixelRC.invGamma = 1.0f / r_gamma->value;
	pixelRC.brightness = r_brightness->value;
	pixelRC.greyscale = r_greyscale->value;

	CmdBindRenderTargets(1, &swapChain, NULL);
	CmdBindPipeline(toneMapPipeline);
	CmdBindRootSignature(toneMapRootSignature);
	CmdBindDescriptorTable(toneMapRootSignature, toneMapDescriptorTable);
	CmdSetRootConstants(toneMapRootSignature, ShaderStage::Vertex, &vertexRC);
	CmdSetRootConstants(toneMapRootSignature, ShaderStage::Pixel, &pixelRC);
	CmdDraw(3, 0);
}

void PostProcess::ToneMap()
{
	GammaVertexRC vertexRC = {};
	vertexRC.scaleX = 1.0f;
	vertexRC.scaleY = 1.0f;

	GammaPixelRC pixelRC = {};
	pixelRC.invGamma = 1.0f / r_gamma->value;
	pixelRC.brightness = r_brightness->value;
	pixelRC.greyscale = 0.0f;

	CmdBindPipeline(toneMapPipeline);
	CmdBindRootSignature(toneMapRootSignature);
	CmdBindDescriptorTable(toneMapRootSignature, toneMapDescriptorTable);
	CmdSetRootConstants(toneMapRootSignature, ShaderStage::Vertex, &vertexRC);
	CmdSetRootConstants(toneMapRootSignature, ShaderStage::Pixel, &pixelRC);
	CmdDraw(3, 0);
}

void PostProcess::InverseToneMap()
{
	InverseGammaPixelRC pixelRC = {};
	pixelRC.gamma = r_gamma->value;
	pixelRC.invBrightness = 1.0f / r_brightness->value;

	CmdBindPipeline(inverseToneMapPipeline);
	CmdBindRootSignature(inverseToneMapRootSignature);
	CmdBindDescriptorTable(inverseToneMapRootSignature, inverseToneMapDescriptorTable);
	CmdSetRootConstants(inverseToneMapRootSignature, ShaderStage::Pixel, &pixelRC);
	CmdDraw(3, 0);
}

void PostProcess::SetToneMapInput(HTexture toneMapInput)
{
	DescriptorTableUpdate update;
	update.SetTextures(1, &toneMapInput);
	UpdateDescriptorTable(toneMapDescriptorTable, update);
}

void PostProcess::SetInverseToneMapInput(HTexture inverseToneMapInput)
{
	DescriptorTableUpdate update;
	update.SetTextures(1, &inverseToneMapInput);
	UpdateDescriptorTable(inverseToneMapDescriptorTable, update);
}