/*
===========================================================================
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 - scatter-as-gather motion blur


#include "crp_local.h"
#include "compshaders/crp/fullscreen.h"
#include "compshaders/crp/mblur_pack.h"
#include "compshaders/crp/mblur_tile_gen.h"
#include "compshaders/crp/mblur_tile_max.h"
#include "compshaders/crp/mblur_blur.h"


#pragma pack(push, 4)

struct MBPackRC
{
	float motionScale;
	float maxRadiusPx;
};

struct MBTileGenRC
{
	uint32_t inputTextureIndex;
	uint32_t outputTextureIndex;
};

struct MBTileMaxRC
{
	uint32_t inputTextureIndex;
	uint32_t outputTextureIndex;
	uint32_t samplerIndex; // point/clamp
};

struct MBBlurRC
{
	uint32_t colorTextureIndex;
	uint32_t tileTextureIndex;
	uint32_t packedTextureIndex;
	uint32_t blueNoiseTextureIndex;
	uint32_t pointSamplerIndex; // clamp
	uint32_t linearSamplerIndex; // clamp
};

#pragma pack(pop)


void MotionBlur::Init()
{
	tileTextureWidth = (uint32_t)(glConfig.vidWidth + 15) / 16;
	tileTextureHeight = (uint32_t)(glConfig.vidHeight + 15) / 16;

	{
		GraphicsPipelineDesc desc("MBlur velocity/depth packing");
		MakeFullScreenPipeline(desc, ShaderByteCode(g_pack_ps));
		desc.AddRenderTarget(0, TextureFormat::R32G32_UInt);
		packPipeline = CreateGraphicsPipeline(desc);
	}

	tileGenPipeline = CreateComputePipeline("MBlur tile generation", ShaderByteCode(g_tile_gen_cs));
	tileMaxPipeline = CreateComputePipeline("MBlur tile dilation", ShaderByteCode(g_tile_max_cs));

	{
		GraphicsPipelineDesc desc("MBlur reconstruction filter");
		MakeFullScreenPipeline(desc, ShaderByteCode(g_blur_ps));
		desc.AddRenderTarget(0, crp.renderTargetFormat);
		blurPipeline = CreateGraphicsPipeline(desc);
	}

	{
		TextureDesc desc("MBlur velocity tile #1", tileTextureWidth, tileTextureHeight);
		desc.shortLifeTime = true;
		desc.committedResource = true;
		desc.initialState = ResourceStates::UnorderedAccessBit;
		desc.allowedState = ResourceStates::UnorderedAccessBit | ResourceStates::ComputeShaderAccessBit | ResourceStates::PixelShaderAccessBit;
		desc.format = TextureFormat::R16G16_Float;
		tileTexture = CreateTexture(desc);
		desc.name = "MBlur velocity tile #2";
		tileTexture2 = CreateTexture(desc);
	}

	{
		TextureDesc desc("MBlur packed velocity/depth", glConfig.vidWidth, glConfig.vidHeight);
		desc.shortLifeTime = true;
		desc.committedResource = true;
		desc.initialState = ResourceStates::RenderTargetBit;
		desc.allowedState = ResourceStates::RenderTargetBit | ResourceStates::ComputeShaderAccessBit | ResourceStates::PixelShaderAccessBit;
		desc.format = TextureFormat::R32G32_UInt;
		packedTexture = CreateTexture(desc);
	}
}

void MotionBlur::Draw()
{
	if(crp_mblur->integer == 0 ||
		(backEnd.refdef.rdflags & RDF_CNQ3_TELEPORTED) != 0 ||
		!IsViewportFullscreen(backEnd.viewParms))
	{
		return;
	}

	SCOPED_RENDER_PASS("Motion Blur", 0.125f, 0.125f, 0.25f);

	DrawPack();
	DrawTileGen();
	DrawTileMax();
	DrawBlur();
}

void MotionBlur::DrawPack()
{
	SCOPED_DEBUG_LABEL("MBlur Pack", 0.125f, 0.125f, 0.25f);

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

	MBPackRC rc = {};
	rc.motionScale = crp_mblur_exposure->value;
	rc.maxRadiusPx = min(glConfig.vidHeight / 25.0f, 30.0f);

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

void MotionBlur::DrawTileGen()
{
	SCOPED_DEBUG_LABEL("MBlur Tile Gen", 0.125f, 0.125f, 0.25f);

	CmdBeginBarrier();
	CmdTextureBarrier(packedTexture, ResourceStates::ComputeShaderAccessBit);
	CmdTextureBarrier(tileTexture, ResourceStates::UnorderedAccessBit);
	CmdEndBarrier();

	MBTileGenRC rc = {};
	rc.inputTextureIndex = GetTextureIndexSRV(packedTexture);
	rc.outputTextureIndex = GetTextureIndexUAV(tileTexture, 0);

	CmdBindPipeline(tileGenPipeline);
	CmdSetComputeRootConstants(0, sizeof(rc), &rc);
	CmdDispatch((tileTextureWidth + 7) / 8, (tileTextureHeight + 7) / 8, 1);
}

void MotionBlur::DrawTileMax()
{
	SCOPED_DEBUG_LABEL("MBlur Tile Max", 0.125f, 0.125f, 0.25f);

	CmdBeginBarrier();
	CmdTextureBarrier(tileTexture, ResourceStates::ComputeShaderAccessBit);
	CmdTextureBarrier(tileTexture2, ResourceStates::UnorderedAccessBit);
	CmdEndBarrier();

	MBTileMaxRC rc = {};
	rc.inputTextureIndex = GetTextureIndexSRV(tileTexture);
	rc.outputTextureIndex = GetTextureIndexUAV(tileTexture2, 0);
	rc.samplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Point);

	CmdBindPipeline(tileMaxPipeline);
	CmdSetComputeRootConstants(0, sizeof(rc), &rc);
	CmdDispatch((tileTextureWidth + 7) / 8, (tileTextureHeight + 7) / 8, 1);
}

void MotionBlur::DrawBlur()
{
	SCOPED_DEBUG_LABEL("MBlur Blur", 0.125f, 0.125f, 0.25f);

	crp.SwapRenderTargets();

	CmdBeginBarrier();
	CmdTextureBarrier(crp.GetReadRenderTarget(), ResourceStates::PixelShaderAccessBit);
	CmdTextureBarrier(tileTexture2, ResourceStates::PixelShaderAccessBit);
	CmdTextureBarrier(packedTexture, ResourceStates::PixelShaderAccessBit);
	CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit);
	CmdEndBarrier();

	MBBlurRC rc = {};
	rc.colorTextureIndex = GetTextureIndexSRV(crp.GetReadRenderTarget());
	rc.tileTextureIndex = GetTextureIndexSRV(tileTexture2);
	rc.packedTextureIndex = GetTextureIndexSRV(packedTexture);
	rc.blueNoiseTextureIndex = GetTextureIndexSRV(crp.blueNoise2D);
	rc.pointSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Point);
	rc.linearSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear);

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