/*
===========================================================================
Copyright (C) 2022-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 - private declarations


#pragma once


#include "tr_local.h"
#include "rhi_local.h"


using namespace RHI;


// @TODO: move out
#define CONCAT_IMM(x, y) x ## y
#define CONCAT(x, y) CONCAT_IMM(x, y)


#pragma pack(push, 4)

struct WorldVertexRC
{
	float modelViewMatrix[16];
	float projectionMatrix[16];
	float clipPlane[4];
};

struct WorldPixelRC
{
	// general
	uint32_t stageIndices[8]; // sampler: 16 - texture: 16
	float greyscale;

	// r_shaderTrace - dynamically enabled
	uint32_t shaderTrace; // shader index: 14 - frame index: 2 - enable: 1
	uint16_t centerPixelX;
	uint16_t centerPixelY;

	// r_depthFade - statically enabled
	uint16_t hFadeDistance;
	uint16_t hFadeOffset;
	uint32_t depthFadeColorTex; // texture index: 12 - color bias: 4 - color scale: 4

	// r_dither - statically enabled
	uint16_t hFrameSeed;
	uint16_t hNoiseScale;
	uint16_t hInvGamma;
	uint16_t hInvBrightness;
};

#pragma pack(pop)

struct BufferBase
{
	bool CanAdd(uint32_t count_)
	{
		return batchFirst + batchCount + count_ <= totalCount;
	}

	void EndBatch()
	{
		batchFirst += batchCount;
		batchCount = 0;
	}

	void EndBatch(uint32_t size)
	{
		batchFirst += size;
		batchCount = 0;
	}

	void Rewind()
	{
		batchFirst = 0;
		batchCount = 0;
	}

	uint32_t totalCount = 0;
	uint32_t batchFirst = 0;
	uint32_t batchCount = 0;
};

struct VertexBuffers : BufferBase
{
	enum BaseId
	{
		BasePosition,
		BaseNormal,
		BaseCount
	};

	enum StageId
	{
		StageTexCoords,
		StageColors,
		StageCount
	};

	void Create(const char* name, MemoryUsage::Id memoryUsage, uint32_t vertexCount)
	{
		totalCount = vertexCount;

		BufferDesc desc = {};
		desc.committedResource = true;
		desc.initialState = ResourceStates::VertexBufferBit;
		desc.memoryUsage = memoryUsage;
		
		desc.name = va("%s position vertex", name);
		desc.byteCount = vertexCount * sizeof(vec3_t);
		buffers[BasePosition] = CreateBuffer(desc);
		strides[BasePosition] = sizeof(vec3_t);

		desc.name = va("%s normal vertex", name);
		desc.byteCount = vertexCount * sizeof(vec3_t);
		buffers[BaseNormal] = CreateBuffer(desc);
		strides[BaseNormal] = sizeof(vec3_t);

		for(uint32_t s = 0; s < MAX_SHADER_STAGES; ++s)
		{
			desc.name = va("%s tex coords #%d vertex", name, (int)s + 1);
			desc.byteCount = vertexCount * sizeof(vec2_t);
			buffers[BaseCount + s * StageCount + StageTexCoords] = CreateBuffer(desc);
			strides[BaseCount + s * StageCount + StageTexCoords] = sizeof(vec2_t);

			desc.name = va("%s color #%d vertex", name, (int)s + 1);
			desc.byteCount = vertexCount * sizeof(color4ub_t);
			buffers[BaseCount + s * StageCount + StageColors] = CreateBuffer(desc);
			strides[BaseCount + s * StageCount + StageColors] = sizeof(color4ub_t);
		}
	}

	void BeginUpload()
	{
		for(uint32_t b = 0; b < BufferCount; ++b)
		{
			mapped[b] = BeginBufferUpload(buffers[b]);
		}
	}

	void EndUpload()
	{
		for(uint32_t b = 0; b < BufferCount; ++b)
		{
			EndBufferUpload(buffers[b]);
			mapped[b] = NULL;
		}
	}

	void Upload(uint32_t firstStage, uint32_t stageCount)
	{
		Q_assert(mapped[0] != NULL);

		const uint32_t batchOffset = batchFirst + batchCount;

		float* pos = (float*)mapped[BasePosition] + 3 * batchOffset;
		for(int v = 0; v < tess.numVertexes; ++v)
		{
			pos[0] = tess.xyz[v][0];
			pos[1] = tess.xyz[v][1];
			pos[2] = tess.xyz[v][2];
			pos += 3;
		}

		float* nor = (float*)mapped[BaseNormal] + 3 * batchOffset;
		for(int v = 0; v < tess.numVertexes; ++v)
		{
			nor[0] = tess.normal[v][0];
			nor[1] = tess.normal[v][1];
			nor[2] = tess.normal[v][2];
			nor += 3;
		}

		for(uint32_t s = 0; s < stageCount; ++s)
		{
			const stageVars_t& sv = tess.svars[s + firstStage];

			uint8_t* const tcBuffer = mapped[BaseCount + s * StageCount + StageTexCoords];
			float* tc = (float*)tcBuffer + 2 * batchOffset;
			memcpy(tc, &sv.texcoords[0], tess.numVertexes * sizeof(vec2_t));

			uint8_t* const colBuffer = mapped[BaseCount + s * StageCount + StageColors];
			uint32_t* col = (uint32_t*)colBuffer + batchOffset;
			memcpy(col, &sv.colors[0], tess.numVertexes * sizeof(color4ub_t));
		}
	}

	static const uint32_t BufferCount = BaseCount + StageCount * MAX_SHADER_STAGES;
	HBuffer buffers[BufferCount] = {};
	uint32_t strides[BufferCount] = {};
	uint8_t* mapped[BufferCount] = {};
};

struct IndexBuffer : BufferBase
{
	void Create(const char* name, MemoryUsage::Id memoryUsage, uint32_t indexCount)
	{
		totalCount = indexCount;

		BufferDesc desc = {};
		desc.committedResource = true;
		desc.initialState = ResourceStates::IndexBufferBit;
		desc.memoryUsage = memoryUsage;
		desc.name = va("%s index", name);
		desc.byteCount = indexCount * sizeof(uint32_t);
		buffer = CreateBuffer(desc);
	}

	void BeginUpload()
	{
		mapped = (uint32_t*)BeginBufferUpload(buffer);
	}

	void EndUpload()
	{
		EndBufferUpload(buffer);
		mapped = NULL;
	}

	void Upload()
	{
		Q_assert(mapped != NULL);

		uint32_t* const idx = mapped + batchFirst + batchCount;
		memcpy(idx, &tess.indexes[0], tess.numIndexes * sizeof(uint32_t));
	}

	uint32_t* GetCurrentAddress()
	{
		return mapped + batchFirst + batchCount;
	}

	HBuffer buffer = RHI_MAKE_NULL_HANDLE();
	uint32_t* mapped = NULL;
};

struct GeometryBuffer : BufferBase
{
	void Init(uint32_t count_, uint32_t stride_)
	{
		buffer = RHI_MAKE_NULL_HANDLE();
		byteCount = count_ * stride_;
		stride = stride_;
		totalCount = count_;
		batchFirst = 0;
		batchCount = 0;
	}

	HBuffer buffer = RHI_MAKE_NULL_HANDLE();
	uint32_t byteCount = 0;
	uint32_t stride = 0;
};

struct GeometryBuffers
{
	void Rewind()
	{
		vertexBuffers.Rewind();
		indexBuffer.Rewind();
	}

	VertexBuffers vertexBuffers;
	IndexBuffer indexBuffer;
};

struct StaticGeometryChunk
{
	uint32_t vertexCount;
	uint32_t indexCount;
	uint32_t firstGPUVertex;
	uint32_t firstGPUIndex;
	uint32_t firstCPUIndex;
};

struct FrameStats
{
	enum { MaxFrames = 1024 };

	void EndFrame();

	float temp[MaxFrames];
	float p2pMS[MaxFrames];
	stats_t p2pStats;
	int frameCount;
	int frameIndex;
	int skippedFrames;
};

struct BatchType
{
	enum Id
	{
		Standard,
		DynamicLight,
		DepthFade,
		Count
	};
};

struct World
{
	void Init();
	void BeginFrame();
	void EndFrame();
	void Begin();
	void End();
	void DrawPrePass(const drawSceneViewCommand_t& cmd);
	void BeginBatch(const shader_t* shader, bool hasStaticGeo, BatchType::Id batchType);
	void EndBatch();
	void EndSkyBatch();
	void RestartBatch();
	void DrawGUI();
	void ProcessWorld(world_t& world);
	void DrawSceneView(const drawSceneViewCommand_t& cmd);
	void BindVertexBuffers(bool staticGeo, uint32_t firstStage, uint32_t stageCount);
	void BindIndexBuffer(bool staticGeo);
	void DrawFog();
	void DrawLitSurfaces(dlight_t* dl, bool opaque);
	void DrawDynamicLights(bool opaque);
	void DrawSkyBox();
	void DrawClouds();

	typedef uint32_t Index;
	const IndexType::Id indexType = IndexType::UInt32;

	HTexture depthTexture;
	uint32_t depthTextureIndex;

	float clipPlane[4];

	struct BufferFamily
	{
		enum Id
		{
			Invalid,
			PrePass,
			Static,
			Dynamic
		};
	};

	// Z pre-pass
	HRootSignature zppRootSignature;
	HDescriptorTable zppDescriptorTable;
	HPipeline zppPipeline;
	GeometryBuffer zppVertexBuffer;

	// shared
	BufferFamily::Id boundVertexBuffers;
	BufferFamily::Id boundIndexBuffer;
	uint32_t boundStaticVertexBuffersFirst;
	uint32_t boundStaticVertexBuffersCount;
	HPipeline batchPSO;
	BatchType::Id batchType;
	bool batchHasStaticGeo;
	int psoChangeCount;
	bool batchDepthHack;
	bool batchOldDepthHack;
	ShadingRate::Id batchShadingRate;
	ShadingRate::Id batchOldShadingRate;

	// dynamic
	GeometryBuffers dynBuffers[FrameCount];

	// static
	GeometryBuffers statBuffers;
	StaticGeometryChunk statChunks[32768];
	uint32_t statChunkCount;
	uint32_t statIndices[1 << 20];
	uint32_t statIndexCount;

	// fog
	HRootSignature fogRootSignature;
	HDescriptorTable fogDescriptorTable;
	HPipeline fogOutsidePipeline;
	HPipeline fogInsidePipeline;
	HBuffer boxVertexBuffer;
	HBuffer boxIndexBuffer;

	// shader trace
	HBuffer traceRenderBuffer;
	HBuffer traceReadbackBuffer;

	// dynamic lights
	HRootSignature dlRootSignature;
	HPipeline dlPipelines[CT_COUNT * 2 * 2]; // { cull type, polygon offset, depth test }
	bool dlOpaque;
	float dlIntensity; // 1 for most surfaces, but can be scaled down for liquids etc.
	bool dlDepthTestEqual;
	// quick explanation on why dlOpaque is useful in the first place:
	// - opaque surfaces can have a diffuse texture whose alpha isn't 255 everywhere
	// - when that happens and we multiply the color by the the alpha (DL uses additive blending),
	//   we get "light holes" in opaque surfaces, which is not what we want
};

struct UI
{
	void Init();
	void BeginFrame();
	void Begin();
	void End();
	void DrawBatch();
	void UISetColor(const uiSetColorCommand_t& cmd);
	void UIDrawQuad(const uiDrawQuadCommand_t& cmd);
	void UIDrawTriangle(const uiDrawTriangleCommand_t& cmd);

	// 32-bit needed until the render logic is fixed!
	typedef uint32_t Index;
	const IndexType::Id indexType = IndexType::UInt32;

	uint32_t renderPassIndex;

#pragma pack(push, 1)
	struct Vertex
	{
		vec2_t position;
		vec2_t texCoords;
		uint32_t color;
	};
#pragma pack(pop)
	int maxIndexCount;
	int maxVertexCount;
	int firstIndex;
	int firstVertex;
	int indexCount;
	int vertexCount;
	HRootSignature rootSignature;
	HPipeline pipeline;
	HBuffer indexBuffer;
	HBuffer vertexBuffer;
	Index* indices;
	Vertex* vertices;
	uint32_t color;
	const shader_t* shader;
};

struct MipMapGenerator
{
	void Init();
	void GenerateMipMaps(HTexture texture);

	struct Stage
	{
		enum Id
		{
			Start,      // gamma to linear
			DownSample, // down sample on 1 axis
			End,        // linear to gamma
			Count
		};

		HRootSignature rootSignature;
		HDescriptorTable descriptorTable;
		HPipeline pipeline;
	};

	struct MipSlice
	{
		enum Id
		{
			Float16_0,
			Float16_1,
			Count
		};
	};

	HTexture textures[MipSlice::Count];
	Stage stages[3];
};

struct ImGUI
{
	void Init();
	void RegisterFontAtlas();
	void Draw();
	void SafeBeginFrame();
	void SafeEndFrame();

	struct FrameResources
	{
		HBuffer indexBuffer;
		HBuffer vertexBuffer;
	};

	HRootSignature rootSignature;
	HPipeline pipeline;
	HTexture fontAtlas;
	FrameResources frameResources[FrameCount];
	bool frameStarted = false;
};

struct Nuklear
{
	void Init();
	void BeginFrame();
	void Begin();
	void End();
	void Upload(const nuklearUploadCommand_t& cmd);
	void Draw(const nuklearDrawCommand_t& cmd);

	struct FrameResources
	{
		HBuffer indexBuffer;
		HBuffer vertexBuffer;
	};

	HRootSignature rootSignature;
	HPipeline pipeline;
	FrameResources frameResources[FrameCount];
	uint32_t renderPassIndex;
	int prevScissorRect[4];

	// reset every frame
	int firstVertex;
	int firstIndex;
	int numVertexes; // set in Upload
	int numIndexes; // set in Upload
};

struct RenderMode
{
	enum Id
	{
		None,
		UI,
		World,
		ImGui,
		Nuklear,
		Count
	};
};

struct RenderPassQueries
{
	char name[64];
	uint32_t gpuDurationUS;
	uint32_t cpuDurationUS;
	int64_t cpuStartUS;
	uint32_t queryIndex;
};

enum
{
	MaxRenderPasses = 64, // cg_draw3dIcons forces tons of 2D/3D transitions...
	MaxStatsFrameCount = 64
};

struct RenderPassStats
{
	void EndFrame(uint32_t cpu, uint32_t gpu);

	uint32_t samplesCPU[MaxStatsFrameCount];
	uint32_t samplesGPU[MaxStatsFrameCount];
	stats_t statsCPU;
	stats_t statsGPU;
	uint32_t count;
	uint32_t index;
};

struct RenderPassFrame
{
	RenderPassQueries passes[MaxRenderPasses];
	uint32_t count;
};

#pragma pack(push, 1)

struct PSODesc
{
	cullType_t cullType;
	bool polygonOffset;
	bool clampDepth;
	bool depthFade;
};

#pragma pack(pop)

struct CachedPSO
{
#if defined(_DEBUG)
	// lets us know which Q3 shader
	// triggered the creation of this PSO
	char name[MAX_QPATH];
#endif
	PSODesc desc;
	uint32_t stageStateBits[MAX_SHADER_STAGES];
	uint32_t stageCount;
	HPipeline pipeline;
};

struct PostProcess
{
	void Init();
	void Draw(const char* renderPassName, HTexture renderTarget);
	void ToneMap();
	void InverseToneMap(int colorFormat);
	void SetToneMapInput(HTexture toneMapInput);
	void SetInverseToneMapInput(HTexture inverseToneMapInput);

	HPipeline toneMapPipeline;
	HRootSignature toneMapRootSignature;
	HDescriptorTable toneMapDescriptorTable;

	HPipeline inverseToneMapPipelines[RTCF_COUNT];
	HRootSignature inverseToneMapRootSignature;
	HDescriptorTable inverseToneMapDescriptorTable;
};

struct SMAA
{
	void Init();
	void Update();
	void Draw(const viewParms_t& parms);

	// matches r_smaa
	struct Mode
	{
		enum Id
		{
			Disabled,
			Low,
			Medium,
			High,
			Ultra,
			Count
		};
	};

	// fixed
	HTexture areaTexture;
	HTexture searchTexture;
	HRootSignature rootSignature;
	HDescriptorTable descriptorTable;

	// depends on render target resolution
	HTexture edgeTexture;
	HTexture blendTexture;
	HTexture stencilTexture;
	HTexture inputTexture;  // tone mapped
	HTexture outputTexture; // tone mapped

	// depends on selected preset/mode
	// SMAA has 3 passes:
	// 1. edge detection
	// 2. blend weight computation
	// 3. neighborhood blending
	HPipeline firstPassPipeline;
	HPipeline secondPassPipeline;
	HPipeline thirdPassPipeline;

	Mode::Id mode = Mode::Disabled;
	int width = -1;
	int height = -1;
	bool fixedLoaded = false;
};

struct GRP : IRenderPipeline
{
	void Init() override;
	void ShutDown(bool fullShutDown) override;
	void CreateTexture(image_t* image, int mipCount, int width, int height) override;
	void UpoadTextureAndGenerateMipMaps(image_t* image, const byte* data) override;
	void BeginTextureUpload(MappedTexture& mappedTexture, image_t* image) override;
	void EndTextureUpload() override;
	void ProcessWorld(world_t& world) override;
	void ProcessModel(model_t& model) override;
	void ProcessShader(shader_t& shader) override;

	void ExecuteRenderCommands(const byte* data, bool readbackRequested) override;

	void UISetColor(const uiSetColorCommand_t& cmd) override { ui.UISetColor(cmd); }
	void UIDrawQuad(const uiDrawQuadCommand_t& cmd) override { ui.UIDrawQuad(cmd); }
	void UIDrawTriangle(const uiDrawTriangleCommand_t& cmd) override { ui.UIDrawTriangle(cmd); }
	void DrawSceneView(const drawSceneViewCommand_t& cmd) override { world.DrawSceneView(cmd); }
	void TessellationOverflow() override { world.RestartBatch(); }
	void DrawSkyBox() override { world.DrawSkyBox(); }
	void DrawClouds() override { world.DrawClouds(); }

	void ReadPixels(int w, int h, int alignment, colorSpace_t colorSpace, void* out) override;

	void BeginFrame();
	void EndFrame();

	uint32_t RegisterTexture(HTexture htexture);

	uint32_t BeginRenderPass(const char* name, float r, float g, float b);
	void EndRenderPass(uint32_t index);

	void DrawGUI();

	uint32_t CreatePSO(CachedPSO& cache, const char* name);

	void UpdateReadbackTexture();

	UI ui;
	World world;
	MipMapGenerator mipMapGen;
	ImGUI imgui;
	PostProcess post;
	SMAA smaa;
	Nuklear nuklear;
	bool firstInit = true;
	RenderMode::Id renderMode; // necessary for sampler selection, useful for debugging
	float frameSeed;
	bool updateReadbackTexture;

	// @TODO: what's up with rootSignature and uberRootSignature?
	// probably need to nuke one of them...

	HTexture renderTarget;
	TextureFormat::Id renderTargetFormat;
	HTexture readbackRenderTarget;
	RootSignatureDesc rootSignatureDesc;
	HRootSignature rootSignature;
	HDescriptorTable descriptorTable;
	uint32_t textureIndex;
	HSampler samplers[TW_COUNT * TextureFilter::Count * MaxTextureMips];

	RenderPassFrame renderPasses[FrameCount];
	RenderPassFrame tempRenderPasses;
	RenderPassStats renderPassStats[MaxRenderPasses];
	RenderPassStats wholeFrameStats;

	FrameStats frameStats;

	CachedPSO psos[1024];
	uint32_t psoCount;
	HRootSignature uberRootSignature;
};

extern GRP grp;

struct ScopedRenderPass
{
	ScopedRenderPass(const char* name, float r, float g, float b)
	{
		index = grp.BeginRenderPass(name, r, g, b);
	}

	~ScopedRenderPass()
	{
		grp.EndRenderPass(index);
	}

	uint32_t index;
};

#define SCOPED_RENDER_PASS(Name, R, G, B) ScopedRenderPass CONCAT(rp_, __LINE__)(Name, R, G, B)

inline void CmdSetViewportAndScissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
	CmdSetViewport(x, y, w, h);
	CmdSetScissor(x, y, w, h);
}

inline void CmdSetViewportAndScissor(const viewParms_t& vp)
{
	CmdSetViewportAndScissor(vp.viewportX, vp.viewportY, vp.viewportWidth, vp.viewportHeight);
}

inline bool IsDepthFadeEnabled(const shader_t& shader)
{
	return
		r_depthFade->integer != 0 &&
		shader.dfType > DFT_NONE &&
		shader.dfType < DFT_TBD;
}

const image_t* GetBundleImage(const textureBundle_t& bundle);
uint32_t GetSamplerIndex(textureWrap_t wrap, TextureFilter::Id filter, uint32_t minLOD = 0);
uint32_t GetSamplerIndex(const image_t* image);
