/*
===========================================================================
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 - fog volumes


#include "crp_local.h"
#include "compshaders/crp/fog_outside.h"
#include "compshaders/crp/fog_inside.h"


#pragma pack(push, 4)
struct FogRC
{
	float modelViewMatrix[16];
	float projectionMatrix[16];
	float boxMin[4];
	float boxMax[4];
	float color[4];
	float depth;
	float linearDepthA;
	float linearDepthB;
	uint32_t depthTextureIndex;
};
#pragma pack(pop)


void Fog::Init()
{
	{
		const uint32_t indices[] =
		{
			0, 1, 2, 2, 1, 3,
			4, 0, 6, 6, 0, 2,
			7, 5, 6, 6, 5, 4,
			3, 1, 7, 7, 1, 5,
			4, 5, 0, 0, 5, 1,
			3, 7, 2, 2, 7, 6
		};

		BufferDesc desc("box index", sizeof(indices), ResourceStates::IndexBufferBit);
		desc.shortLifeTime = true;
		boxIndexBuffer = CreateBuffer(desc);

		uint8_t* mapped = BeginBufferUpload(boxIndexBuffer);
		memcpy(mapped, indices, sizeof(indices));
		EndBufferUpload(boxIndexBuffer);
	}
	{
		const float vertices[] =
		{
			0.0f, 1.0f, 0.0f,
			1.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f,
			1.0f, 0.0f, 0.0f,
			0.0f, 1.0f, 1.0f,
			1.0f, 1.0f, 1.0f,
			0.0f, 0.0f, 1.0f,
			1.0f, 0.0f, 1.0f
		};

		BufferDesc desc("box vertex", sizeof(vertices), ResourceStates::VertexBufferBit);
		desc.shortLifeTime = true;
		boxVertexBuffer = CreateBuffer(desc);

		uint8_t* mapped = BeginBufferUpload(boxVertexBuffer);
		memcpy(mapped, vertices, sizeof(vertices));
		EndBufferUpload(boxVertexBuffer);
	}
	{
		GraphicsPipelineDesc desc("fog outside");
		desc.shortLifeTime = true;
		desc.vertexShader = ShaderByteCode(g_outside_vs);
		desc.pixelShader = ShaderByteCode(g_outside_ps);
		desc.depthStencil.DisableDepth();
		desc.rasterizer.cullMode = CT_BACK_SIDED;
		desc.rasterizer.polygonOffset = false;
		desc.rasterizer.clampDepth = true;
		desc.AddRenderTarget(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA, crp.renderTargetFormat);
		desc.vertexLayout.AddAttribute(0, ShaderSemantic::Position, DataType::Float32, 3, 0);
		fogOutsidePipeline = CreateGraphicsPipeline(desc);
	}
	{
		GraphicsPipelineDesc desc("fog inside");
		desc.shortLifeTime = true;
		desc.vertexShader = ShaderByteCode(g_inside_vs);
		desc.pixelShader = ShaderByteCode(g_inside_ps);
		desc.depthStencil.DisableDepth();
		desc.rasterizer.cullMode = CT_FRONT_SIDED;
		desc.rasterizer.polygonOffset = false;
		desc.rasterizer.clampDepth = true;
		desc.AddRenderTarget(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA, crp.renderTargetFormat);
		desc.vertexLayout.AddAttribute(0, ShaderSemantic::Position, DataType::Float32, 3, 0);
		fogInsidePipeline = CreateGraphicsPipeline(desc);
	}
}

void Fog::Draw()
{
	// @NOTE: fog 0 is invalid, it must be skipped
	if(tr.world == NULL ||
		tr.world->numfogs <= 1 ||
		(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0)
	{
		return;
	}

	SCOPED_RENDER_PASS("Fog", 0.25f, 0.125f, 0.0f);

	srp.renderMode = RenderMode::World;

	const uint32_t stride = sizeof(vec3_t);
	CmdBindVertexBuffers(1, &boxVertexBuffer, &stride, NULL);
	CmdBindIndexBuffer(boxIndexBuffer, IndexType::UInt32, 0);

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

	CmdBindRenderTargets(1, &crp.renderTarget, NULL);

	int insideIndex = -1;
	for(int f = 1; f < tr.world->numfogs; ++f)
	{
		const fog_t& fog = tr.world->fogs[f];

		bool inside = true;
		for(int a = 0; a < 3; ++a)
		{
			if(backEnd.viewParms.orient.origin[a] <= fog.bounds[0][a] ||
				backEnd.viewParms.orient.origin[a] >= fog.bounds[1][a])
			{
				inside = false;
				break;
			}
		}

		if(inside)
		{
			insideIndex = f;
			break;
		}
	}

	FogRC rc = {};
	memcpy(rc.modelViewMatrix, backEnd.viewParms.world.modelMatrix, sizeof(rc.modelViewMatrix));
	memcpy(rc.projectionMatrix, backEnd.viewParms.projectionMatrix, sizeof(rc.projectionMatrix));
	RB_LinearDepthConstants(&rc.linearDepthA, &rc.linearDepthB);
	rc.depthTextureIndex = GetTextureIndexSRV(crp.depthTexture);

	CmdBindPipeline(fogOutsidePipeline);
	for(int f = 1; f < tr.world->numfogs; ++f)
	{
		if(f == insideIndex)
		{
			continue;
		}

		const fog_t& fog = tr.world->fogs[f];
		VectorScale(fog.parms.color, tr.identityLight, rc.color);
		rc.depth = fog.parms.depthForOpaque;
		VectorCopy(fog.bounds[0], rc.boxMin);
		VectorCopy(fog.bounds[1], rc.boxMax);
		CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
		CmdDrawIndexed(36, 0, 0);
	}

	if(insideIndex > 0)
	{
		CmdBindPipeline(fogInsidePipeline);

		const fog_t& fog = tr.world->fogs[insideIndex];
		VectorScale(fog.parms.color, tr.identityLight, rc.color);
		rc.depth = fog.parms.depthForOpaque;
		VectorCopy(fog.bounds[0], rc.boxMin);
		VectorCopy(fog.bounds[1], rc.boxMax);
		CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
		CmdDrawIndexed(36, 0, 0);
	}
}
