/*
===========================================================================
Copyright (C) 2024 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/>.
===========================================================================
*/
// Cinematic Rendering Pipeline - G-Buffer texture visualization


#include "crp_local.h"
#include "../client/cl_imgui.h"
#include "compshaders/crp/fullscreen.h"
#include "compshaders/crp/gbufferviz_depth.h"
#include "compshaders/crp/gbufferviz_normal.h"
#include "compshaders/crp/gbufferviz_position.h"
#include "compshaders/crp/gbufferviz_motion.h"


#pragma pack(push, 4)

struct LinearizeDepthRC
{
	uint32_t depthTextureIndex;
	float linearDepthA;
	float linearDepthB;
	float zFarInv;
};

struct DecodeNormalsRC
{
	uint32_t normalTextureIndex;
};

struct DecodePositionRC
{
	uint32_t textureIndex;
	uint32_t coloredDelta;
};

struct MotionVectorRC
{
	uint32_t textureIndex;
};

#pragma pack(pop)


void GBufferViz::Init()
{
	{
		GraphicsPipelineDesc desc("G-Buffer Depth");
		MakeFullScreenPipeline(desc, ShaderByteCode(g_gbufferviz_depth_ps));
		desc.AddRenderTarget(0, crp.renderTargetFormat);
		linearizeDepthPipeline = CreateGraphicsPipeline(desc);
	}

	{
		GraphicsPipelineDesc desc("G-Buffer Normal");
		MakeFullScreenPipeline(desc, ShaderByteCode(g_gbufferviz_normal_ps));
		desc.AddRenderTarget(0, crp.renderTargetFormat);
		decodeNormalsPipeline = CreateGraphicsPipeline(desc);
	}

	{
		GraphicsPipelineDesc desc("G-Buffer Position");
		MakeFullScreenPipeline(desc, ShaderByteCode(g_gbufferviz_position_ps));
		desc.AddRenderTarget(0, crp.renderTargetFormat);
		decodeShadingPositionPipeline = CreateGraphicsPipeline(desc);
	}

	{
		GraphicsPipelineDesc desc("G-Buffer Motion");
		MakeFullScreenPipeline(desc, ShaderByteCode(g_gbufferviz_motion_ps));
		desc.AddRenderTarget(0, crp.renderTargetFormat);
		motionVectorPipeline = CreateGraphicsPipeline(desc);
	}
}

void GBufferViz::DrawGUI()
{
	GUI_AddMainMenuItem(GUI_MainMenu::Tools, "Show G-Buffer", "", &windowActive);

	if(!windowActive)
	{
		return;
	}

	const HTexture renderTarget = crp.GetReadRenderTarget();

	if(textureIndex == GBufferTexture::Depth)
	{
		srp.renderMode = RenderMode::None;

		SCOPED_DEBUG_LABEL("Debug Depth", 1.0f, 1.0f, 1.0f);

		CmdSetViewportAndScissor(0, 0, glConfig.vidWidth, glConfig.vidHeight);

		CmdBeginBarrier();
		CmdTextureBarrier(crp.depthTexture, ResourceStates::PixelShaderAccessBit);
		CmdTextureBarrier(renderTarget, ResourceStates::RenderTargetBit);
		CmdEndBarrier();

		LinearizeDepthRC rc = {};
		rc.depthTextureIndex = GetTextureIndexSRV(crp.depthTexture);
		RB_LinearDepthConstants(&rc.linearDepthA, &rc.linearDepthB);
		rc.zFarInv = 1.0f / backEnd.viewParms.zFar;

		CmdBindRenderTargets(1, &renderTarget, NULL);
		CmdBindPipeline(linearizeDepthPipeline);
		CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
		CmdDraw(3, 0);
	}
	else if(textureIndex == GBufferTexture::Normal)
	{
		srp.renderMode = RenderMode::None;

		SCOPED_DEBUG_LABEL("Debug Normal", 1.0f, 1.0f, 1.0f);

		CmdSetViewportAndScissor(0, 0, glConfig.vidWidth, glConfig.vidHeight);

		CmdBeginBarrier();
		CmdTextureBarrier(crp.normalTexture, ResourceStates::PixelShaderAccessBit);
		CmdTextureBarrier(renderTarget, ResourceStates::RenderTargetBit);
		CmdEndBarrier();

		DecodeNormalsRC rc = {};
		rc.normalTextureIndex = GetTextureIndexSRV(crp.normalTexture);

		CmdBindRenderTargets(1, &renderTarget, NULL);
		CmdBindPipeline(decodeNormalsPipeline);
		CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
		CmdDraw(3, 0);
	}
	else if(textureIndex == GBufferTexture::ShadingPositionDelta)
	{
		srp.renderMode = RenderMode::None;

		SCOPED_DEBUG_LABEL("Debug Position", 1.0f, 1.0f, 1.0f);

		CmdSetViewportAndScissor(0, 0, glConfig.vidWidth, glConfig.vidHeight);

		CmdBeginBarrier();
		CmdTextureBarrier(crp.shadingPositionTexture, ResourceStates::PixelShaderAccessBit);
		CmdTextureBarrier(renderTarget, ResourceStates::RenderTargetBit);
		CmdEndBarrier();

		DecodePositionRC rc = {};
		rc.textureIndex = GetTextureIndexSRV(crp.shadingPositionTexture);
		rc.coloredDelta = coloredPositionDelta ? 1 : 0;

		CmdBindRenderTargets(1, &renderTarget, NULL);
		CmdBindPipeline(decodeShadingPositionPipeline);
		CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
		CmdDraw(3, 0);
	}
	else if(textureIndex == GBufferTexture::MotionVectorRaw ||
		textureIndex == GBufferTexture::MotionVectorMB)
	{
		srp.renderMode = RenderMode::None;

		const HTexture texture =
			textureIndex == GBufferTexture::MotionVectorRaw ?
			crp.motionVectorTexture :
			crp.motionVectorMBTexture;

		SCOPED_DEBUG_LABEL("Debug Motion", 1.0f, 1.0f, 1.0f);

		CmdSetViewportAndScissor(0, 0, glConfig.vidWidth, glConfig.vidHeight);

		CmdBeginBarrier();
		CmdTextureBarrier(texture, ResourceStates::PixelShaderAccessBit);
		CmdTextureBarrier(renderTarget, ResourceStates::RenderTargetBit);
		CmdEndBarrier();

		MotionVectorRC rc = {};
		rc.textureIndex = GetTextureIndexSRV(texture);

		CmdBindRenderTargets(1, &renderTarget, NULL);
		CmdBindPipeline(motionVectorPipeline);
		CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
		CmdDraw(3, 0);
	}

	if(ImGui::Begin("G-Buffer", &windowActive, ImGuiWindowFlags_AlwaysAutoResize))
	{
		ImGui::Checkbox("Full-res", &fullResolution);
		ImGui::SameLine();
		ImGui::RadioButton("Depth", &textureIndex, GBufferTexture::Depth);
		ImGui::SameLine();
		ImGui::RadioButton("Normal", &textureIndex, GBufferTexture::Normal);
		ImGui::SameLine();
		ImGui::RadioButton("Light", &textureIndex, GBufferTexture::Light);

		ImGui::RadioButton("Motion Raw", &textureIndex, GBufferTexture::MotionVectorRaw);
		ImGui::SameLine();
		ImGui::RadioButton("Motion MB", &textureIndex, GBufferTexture::MotionVectorMB);
		ImGui::SameLine();
		ImGui::RadioButton("Shading Position Delta", &textureIndex, GBufferTexture::ShadingPositionDelta);
		if(textureIndex == GBufferTexture::ShadingPositionDelta)
		{
			ImGui::SameLine();
			ImGui::Checkbox("Per-axis delta", &coloredPositionDelta);
		}

		ImVec2 resolution = ImVec2(glConfig.vidWidth, glConfig.vidHeight);
		if(!fullResolution)
		{
			resolution.x /= 2;
			resolution.y /= 2;
		}

		HTexture texture;
		switch(textureIndex)
		{
			case GBufferTexture::Depth: texture = renderTarget; break;
			case GBufferTexture::Normal: texture = renderTarget; break;
			case GBufferTexture::Light: texture = crp.lightTexture; break;
			case GBufferTexture::ShadingPositionDelta: texture = renderTarget; break;
			case GBufferTexture::MotionVectorRaw: texture = renderTarget; break;
			case GBufferTexture::MotionVectorMB: texture = renderTarget; break;
			default: Q_assert(!"Invalid G-Buffer texture index"); texture = crp.lightTexture; break;
		}
		ImGui::Image((ImTextureID)GetTextureIndexSRV(texture), resolution);

		CmdBeginBarrier();
		CmdTextureBarrier(texture, ResourceStates::PixelShaderAccessBit);
		CmdEndBarrier();
	}
	ImGui::End();
}
