/*
===========================================================================
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/>.
===========================================================================
*/
// Cinematic Rendering Pipeline - vertex and index buffer management


#include "crp_local.h"


void GeoBuffers::Create(const char* name, uint32_t vertexCount, uint32_t indexCount)
{
	baseVertexBuffers[BaseBufferId::Position].CreateVertexBuffer(
		va("%s position", name), MemoryUsage::Upload, vertexCount, sizeof(vec3_t));
	baseVertexBuffers[BaseBufferId::Normal].CreateVertexBuffer(
		va("%s normal", name), MemoryUsage::Upload, vertexCount, sizeof(vec3_t));
	stageVertexBuffers[StageBufferId::TexCoords].CreateVertexBuffer(
		va("%s tc", name), MemoryUsage::Upload, vertexCount * MAX_SHADER_STAGES, sizeof(vec2_t));
	stageVertexBuffers[StageBufferId::Color].CreateVertexBuffer(
		va("%s color", name), MemoryUsage::Upload, vertexCount * MAX_SHADER_STAGES, sizeof(color4ub_t));
	indexBuffer.Create(name, MemoryUsage::Upload, indexCount);

	vertexBuffers[0] = baseVertexBuffers[BaseBufferId::Position].buffer;
	vertexBuffers[1] = baseVertexBuffers[BaseBufferId::Normal].buffer;
	vertexBuffers[2] = stageVertexBuffers[StageBufferId::TexCoords].buffer;
	vertexBuffers[3] = stageVertexBuffers[StageBufferId::Color].buffer;
	vertexBufferStrides[0] = sizeof(vec3_t);
	vertexBufferStrides[1] = sizeof(vec3_t);
	vertexBufferStrides[2] = sizeof(vec2_t);
	vertexBufferStrides[3] = sizeof(color4ub_t);
}

void GeoBuffers::Rewind()
{
	for(uint32_t b = 0; b < ARRAY_LEN(baseVertexBuffers); ++b)
	{
		baseVertexBuffers[b].Rewind();
	}
	for(uint32_t b = 0; b < ARRAY_LEN(stageVertexBuffers); ++b)
	{
		stageVertexBuffers[b].Rewind();
	}
	indexBuffer.Rewind();
}

void GeoBuffers::BeginUpload()
{
	for(uint32_t b = 0; b < ARRAY_LEN(baseVertexBuffers); ++b)
	{
		baseVertexBuffers[b].BeginUpload();
	}
	for(uint32_t b = 0; b < ARRAY_LEN(stageVertexBuffers); ++b)
	{
		stageVertexBuffers[b].BeginUpload();
	}
	indexBuffer.BeginUpload();
}

void GeoBuffers::EndUpload()
{
	for(uint32_t b = 0; b < ARRAY_LEN(baseVertexBuffers); ++b)
	{
		baseVertexBuffers[b].EndUpload();
	}
	for(uint32_t b = 0; b < ARRAY_LEN(stageVertexBuffers); ++b)
	{
		stageVertexBuffers[b].EndUpload();
	}
	indexBuffer.EndUpload();
}

void GeoBuffers::UploadBase()
{
	indexBuffer.Upload();

	const uint32_t batchOffset = baseVertexBuffers[0].batchFirst + baseVertexBuffers[0].batchCount;

	float* pos = (float*)baseVertexBuffers[BaseBufferId::Position].mapped + 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*)baseVertexBuffers[BaseBufferId::Normal].mapped + 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;
	}
}

void GeoBuffers::UploadStage(uint32_t svarsIndex)
{
	const uint32_t batchOffset = stageVertexBuffers[0].batchFirst + stageVertexBuffers[0].batchCount;
	const stageVars_t& sv = tess.svars[svarsIndex];

	uint8_t* const tcBuffer = stageVertexBuffers[StageBufferId::TexCoords].mapped;
	float* tc = (float*)tcBuffer + 2 * batchOffset;
	memcpy(tc, &sv.texcoords[0], tess.numVertexes * sizeof(vec2_t));

	uint8_t* const colBuffer = stageVertexBuffers[StageBufferId::Color].mapped;
	uint32_t* col = (uint32_t*)colBuffer + batchOffset;
	memcpy(col, &sv.colors[0], tess.numVertexes * sizeof(color4ub_t));
}

void GeoBuffers::EndBaseBatch(uint32_t vertexCount)
{
	baseVertexBuffers[BaseBufferId::Position].EndBatch(vertexCount);
	baseVertexBuffers[BaseBufferId::Normal].EndBatch(vertexCount);
	indexBuffer.EndBatch(tess.numIndexes);
}

bool GeoBuffers::CanAdd(uint32_t vertexCount, uint32_t indexCount, uint32_t stageCount)
{
	return
		baseVertexBuffers[0].CanAdd(vertexCount) &&
		stageVertexBuffers[0].CanAdd(vertexCount * stageCount) &&
		indexBuffer.CanAdd(indexCount);
}

void GeoBuffers::DrawStage(uint32_t vertexCount, uint32_t indexCount)
{
	const uint32_t vertexOffset = stageVertexBuffers[0].batchFirst - baseVertexBuffers[0].batchFirst;
	uint32_t byteOffsets[BaseBufferId::Count + StageBufferId::Count] = {};
	byteOffsets[BaseBufferId::Count + StageBufferId::TexCoords] = vertexOffset * sizeof(vec2_t);
	byteOffsets[BaseBufferId::Count + StageBufferId::Color] = vertexOffset * sizeof(color4ub_t);
	CmdBindVertexBuffers(ARRAY_LEN(vertexBuffers), vertexBuffers, vertexBufferStrides, byteOffsets);

	CmdDrawIndexed(indexCount, indexBuffer.batchFirst, baseVertexBuffers[0].batchFirst);

	// @NOTE: must happen after the final vertex buffer byte offsets have been computed
	stageVertexBuffers[StageBufferId::TexCoords].EndBatch(vertexCount);
	stageVertexBuffers[StageBufferId::Color].EndBatch(vertexCount);
}

void GeoBuffers::DrawPositionOnly(uint32_t vertexCount, uint32_t indexCount)
{
	const HBuffer buffer = vertexBuffers[BaseBufferId::Position];
	const uint32_t stride = vertexBufferStrides[BaseBufferId::Position];
	const uint32_t byteOffset = 0;
	CmdBindVertexBuffers(1, &buffer, &stride, &byteOffset);
	CmdDrawIndexed(indexCount, indexBuffer.batchFirst, baseVertexBuffers[0].batchFirst);
}

void GeoBuffers::UploadAndDrawDebugNormals()
{
	if(!baseVertexBuffers[0].CanAdd(tess.numVertexes * 2) ||
		!indexBuffer.CanAdd(tess.numVertexes * 3))
	{
		return;
	}

	const uint32_t posBatchOffset = baseVertexBuffers[BaseBufferId::Position].batchFirst + baseVertexBuffers[BaseBufferId::Position].batchCount;
	float* positions = (float*)baseVertexBuffers[BaseBufferId::Position].mapped + 3 * posBatchOffset;
	for(uint32_t v = 0; v < tess.numVertexes; v++)
	{
		vec3_t newPoint;
		VectorMA(tess.xyz[v], 4.0f, tess.normal[v], newPoint);
		*positions++ = tess.xyz[v][0];
		*positions++ = tess.xyz[v][1];
		*positions++ = tess.xyz[v][2];
		*positions++ = newPoint[0];
		*positions++ = newPoint[1];
		*positions++ = newPoint[2];
	}

	const uint32_t colBatchOffset = stageVertexBuffers[StageBufferId::Color].batchFirst + stageVertexBuffers[StageBufferId::Color].batchCount;
	uint8_t* const colBuffer = stageVertexBuffers[StageBufferId::Color].mapped;
	uint32_t* col = (uint32_t*)colBuffer + colBatchOffset;
	for(uint32_t v = 0; v < tess.numVertexes; v++)
	{
		*col++ = 0xFF0000FF;
		*col++ = 0xFFFF7F00;
	}

	uint32_t* indices = indexBuffer.GetCurrentAddress();
	for(uint32_t i = 0; i < tess.numVertexes; i++)
	{
		*indices++ = i * 2 + 0;
		*indices++ = i * 2 + 0;
		*indices++ = i * 2 + 1;
	}

	const uint32_t vertexOffset = stageVertexBuffers[0].batchFirst - baseVertexBuffers[0].batchFirst;
	const uint32_t byteOffsets[2] = { 0, vertexOffset * sizeof(color4ub_t) };
	HBuffer vb[2];
	vb[0] = vertexBuffers[BaseBufferId::Position];
	vb[1] = vertexBuffers[BaseBufferId::Count + StageBufferId::Color];
	uint32_t strides[2];
	strides[0] = vertexBufferStrides[BaseBufferId::Position];
	strides[1] = vertexBufferStrides[BaseBufferId::Count + StageBufferId::Color];
	CmdBindVertexBuffers(ARRAY_LEN(vb), vb, strides, byteOffsets);
	CmdDrawIndexed(tess.numVertexes * 3, indexBuffer.batchFirst, baseVertexBuffers[0].batchFirst);

	for(int b = 0; b < ARRAY_LEN(baseVertexBuffers); b++)
	{
		baseVertexBuffers[b].EndBatch(tess.numVertexes * 2);
	}
	for(int b = 0; b < ARRAY_LEN(stageVertexBuffers); b++)
	{
		stageVertexBuffers[b].EndBatch(tess.numVertexes * 2);
	}
	indexBuffer.EndBatch(tess.numVertexes * 3);
}
