/*
===========================================================================
Copyright (C) 2023-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/>.
===========================================================================
*/
// Shared Rendering Pipeline - private declarations


#pragma once


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


using namespace RHI;


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 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;
	}

	void CreateVertexBuffer(const char* name, MemoryUsage::Id memoryUsage, uint32_t count, uint32_t stride_)
	{
		BufferDesc desc = {};
		desc.committedResource = true;
		desc.initialState = ResourceStates::VertexBufferBit;
		desc.memoryUsage = memoryUsage;
		desc.name = name;
		desc.byteCount = count * stride_;
		buffer = CreateBuffer(desc);
		byteCount = count * stride_;
		stride = stride_;
		totalCount = count;
		batchFirst = 0;
		batchCount = 0;
	}

	void BeginUpload()
	{
		Q_assert(mapped == NULL);
		mapped = BeginBufferUpload(buffer);
	}

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

	HBuffer buffer = RHI_MAKE_NULL_HANDLE();
	uint32_t byteCount = 0;
	uint32_t stride = 0;
	uint8_t* mapped = NULL;
};

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;
};

struct FrameStats
{
	enum { MaxFrames = 1024 };

	void EndFrame();

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

struct MipMapGenerator
{
	void Init(bool ddhi, const ShaderByteCode& g2l, const ShaderByteCode& down, const ShaderByteCode& l2g);
	void GenerateMipMaps(HTexture texture);

private:
	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];
	bool ddhi = false;
};

struct UI
{
	void Init(bool ddhi_, const ShaderByteCode& vs, const ShaderByteCode& ps, TextureFormat::Id rtFormat,
		HDescriptorTable descTable, RootSignatureDesc* rootSigDesc);
	void BeginFrame();
	void Begin(HTexture renderTarget);
	void End();
	void CmdSetColor(const uiSetColorCommand_t& cmd);
	void CmdDrawQuad(const uiDrawQuadCommand_t& cmd);
	void CmdDrawTriangle(const uiDrawTriangleCommand_t& cmd);

private:
	void DrawBatch();

	// 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;
	HDescriptorTable descriptorTable;
	HPipeline pipeline;
	HBuffer indexBuffer;
	HBuffer vertexBuffer;
	Index* indices;
	Vertex* vertices;
	uint32_t color;
	const shader_t* shader;
	bool ddhi; // direct descriptor heap indexing
};

struct ImGUI
{
	HTexture Init(bool ddhi, const ShaderByteCode& vs, const ShaderByteCode& ps, TextureFormat::Id rtFormat, HDescriptorTable descTable, RootSignatureDesc* rootSigDesc);
	void RegisterFontAtlas(uint32_t fontIndex);
	void Draw(HTexture renderTarget);
	void BeginFrame();
	void EndFrame();

private:
	struct FrameResources
	{
		HBuffer indexBuffer;
		HBuffer vertexBuffer;
	};

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

struct Nuklear
{
	void Init(bool ddhi, const ShaderByteCode& vs, const ShaderByteCode& ps, TextureFormat::Id rtFormat, HDescriptorTable descTable, RootSignatureDesc* rootSigDesc);
	void BeginFrame();
	void Begin(HTexture renderTarget);
	void End();
	void Upload(const nuklearUploadCommand_t& cmd);
	void Draw(const nuklearDrawCommand_t& cmd);

private:
	struct FrameResources
	{
		HBuffer indexBuffer;
		HBuffer vertexBuffer;
	};

	HRootSignature rootSignature;
	HDescriptorTable descriptorTable;
	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
	bool ddhi = false;
};

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

	// @NOTE: SRP::BeginFrame doesn't call RHI::BeginFrame
	// @NOTE: SRP::EndFrame calls RHI::EndFrame and Sys_V_EndFrame
	void BeginFrame(); // call at the start of IRenderPipeline::BeginFrame
	void EndFrame(); // call at the end of IRenderPipeline::EndFrame

	void DrawGUI();

	// call this in Init but only on srp.firstInit
	// you need to register them in your own local descriptor table(s)
	void CreateShaderTraceBuffers();

	bool firstInit = true; // first RP init after a RHI init?
	RenderMode::Id renderMode; // necessary for sampler selection, useful for debugging

	// shader trace
	HBuffer traceRenderBuffer;
	HBuffer traceReadbackBuffer;

	// data for frame breakdown and frame graph
	RenderPassFrame renderPasses[FrameCount];
	RenderPassFrame tempRenderPasses;
	RenderPassStats renderPassStats[MaxRenderPasses];
	RenderPassStats wholeFrameStats;
	FrameStats frameStats;
	bool enableRenderPassQueries = true;

	// PSO stats
	bool psoStatsValid = false;
	int psoCount = 0;
	int psoChangeCount = 0;
};

extern SRP srp;

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

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

	uint32_t index;
};

struct ScopedDebugLabel
{
	ScopedDebugLabel(const char* name, float r, float g, float b)
	{
		CmdBeginDebugLabel(name, r, g, b);
	}

	~ScopedDebugLabel()
	{
		CmdEndDebugLabel();
	}
};

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

#define BASE_SAMPLER_COUNT ((int)(TW_COUNT * TextureFilter::Count * MaxTextureMips))

const image_t* GetBundleImage(const textureBundle_t& bundle);
uint32_t GetBaseSamplerIndex(textureWrap_t wrap, TextureFilter::Id filter, uint32_t minLOD);
uint32_t GetSamplerIndex(textureWrap_t wrap, TextureFilter::Id filter, uint32_t minLOD = 0);
uint32_t GetSamplerIndex(const image_t* image);
void ReadTextureImage(void* outPixels, HTexture hreadback, int w, int h, int alignment, colorSpace_t colorSpace);
void UpdateEntityData(bool& depthHack, int entityNum, double originalTime);
cullType_t GetMirrorredCullType(cullType_t cullType);
uint32_t AlphaTestShaderConstFromStateBits(unsigned int stateBits);

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;
}

inline bool IsViewportFullscreen(const viewParms_t& vp)
{
	return
		vp.viewportX == 0 &&
		vp.viewportY == 0 &&
		vp.viewportWidth == glConfig.vidWidth &&
		vp.viewportHeight == glConfig.vidHeight;
}
