/*
===========================================================================
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 - raytracing acceleration structures management


#include "crp_local.h"
#include "shaders/crp/raytracing.h.hlsli"


struct BLASBuilder
{
	bool IsEmpty()
	{
		return totalVertexCount <= 0 || totalIndexCount <= 0;
	}

	uint32_t totalVertexCount;
	uint32_t totalIndexCount;
	uint32_t meshCount;
	float* buildVertices;
	uint32_t* buildIndices;
	BLASMeshDesc* buildMeshes;
	uint32_t firstVertex;
	uint32_t firstIndex;
	uint32_t meshIndex;

	BLASVertex* traceVertices;
	uint32_t* traceIndices;
	BLASMesh* traceMeshes;
};

static world_t* s_world;

static bool IsStaticBLASSurface(const msurface_t* surf)
{
	if(surf->shader->numStages == 0 ||
		surf->shader->isDynamic ||
		surf->shader->polygonOffset)
	{
		return false;
	}

	return true;
}

static uint32_t GetBLASBucketIndex(const shader_t* shader)
{
	const uint32_t index = (uint32_t)shader->cullType;

	return index;
}

static const char* GetBLASBucketName(uint32_t index)
{
	switch(index)
	{
		case CT_FRONT_SIDED: return "front-sided";
		case CT_BACK_SIDED: return "back-sided";
		case CT_TWO_SIDED: return "two-sided";
		default: Q_assert(!"Invalid bucket index"); return "???";
	}
}

static void TransformPoint(const vec3_t original, const float* matrix4x4, vec3_t result)
{
	float x = original[0] * matrix4x4[0] + original[1] * matrix4x4[4] + original[2] * matrix4x4[ 8] + matrix4x4[12];
	float y = original[0] * matrix4x4[1] + original[1] * matrix4x4[5] + original[2] * matrix4x4[ 9] + matrix4x4[13];
	float z = original[0] * matrix4x4[2] + original[1] * matrix4x4[6] + original[2] * matrix4x4[10] + matrix4x4[14];
	float w = original[0] * matrix4x4[3] + original[1] * matrix4x4[7] + original[2] * matrix4x4[11] + matrix4x4[15];
	if(w != 1.0f && w != 0.0f)
	{
		x /= w;
		y /= w;
		z /= w;
	}
	result[0] = x;
	result[1] = y;
	result[2] = z;
}

// true when the surface should be skipped
static bool Tessellate(
	int& surfVertexCount, int& surfIndexCount, const surfaceType_t* surface,
	const shader_t* shader, int entityNum, double originalTime)
{
	if(shader->numStages <= 0)
	{
		return true;
	}

	bool depthHack;
	tess.numVertexes = 0;
	tess.numIndexes = 0;
	tess.shader = shader; // needed by R_ComputeTexCoords etc.
	UpdateEntityData(depthHack, entityNum, originalTime);
	R_TessellateSurface(surface);
	surfVertexCount = tess.numVertexes;
	surfIndexCount = tess.numIndexes;
	if(surfVertexCount <= 0 || surfIndexCount <= 0)
	{
		return true;
	}

	RB_DeformTessGeometry(0, surfVertexCount, 0, surfIndexCount);
	const shaderStage_t& stage = *shader->stages[0];
	R_ComputeColors(&stage, tess.svars[0], 0, surfVertexCount);
	R_ComputeTexCoords(&stage, tess.svars[0], 0, surfVertexCount, qfalse);

	return false;
}

// true when the surface should be skipped
static bool EstimateTessellatedSize(
	int& surfVertexCount, int& surfIndexCount, const surfaceType_t* surface,
	const shader_t* shader, int entityNum, double originalTime)
{
	if(shader->numStages <= 0)
	{
		return true;
	}

	bool depthHack;
	tess.numVertexes = 0;
	tess.numIndexes = 0;
	tess.shader = shader;
	UpdateEntityData(depthHack, entityNum, originalTime);
	R_ComputeTessellatedSize(&surfVertexCount, &surfIndexCount, surface);
	if(surfVertexCount <= 0 || surfIndexCount <= 0)
	{
		return true;
	}

	return false;
}

static void CreateOrGrowBuffer(HBuffer& buffer, uint32_t& curByteCount, const BufferDesc& desc)
{
	if(desc.byteCount <= curByteCount)
	{
		return;
	}

	curByteCount = max(curByteCount * 2, desc.byteCount);
	DestroyBufferDelayed(buffer);
	buffer = CreateBuffer(desc);
}


void Raytracing::Init()
{
	// make sure we're not trying to use deleted buffers after a video restart
	for(uint32_t f = 0; f < RTFrameCount; f++)
	{
		FrameData& fd = frameData[f];
		for(uint32_t i = 0; i < BLASBucket::Count; i++)
		{
			fd.blasBuildBuffers[i] = {};
			fd.dynamicBLASBuffers[i] = {};
		}
		fd.tlasBuffer = RHI_MAKE_NULL_HANDLE();
	}
	for(uint32_t i = 0; i < BLASBucket::Count; i++)
	{
		staticBLASBuffers[i] = {};
	}

	if(!rhiInfo.hasInlineRaytracing)
	{
		for(uint32_t f = 0; f < RTFrameCount; f++)
		{
			FrameData& fd = frameData[f];
			fd.tlasInstanceBuffer = RHI_MAKE_NULL_HANDLE();
		}
		return;
	}

	for(uint32_t f = 0; f < RTFrameCount; f++)
	{
		FrameData& fd = frameData[f];
		const uint32_t structByteCount = sizeof(TLASInstance);
		BufferDesc desc("BLAS support instance", 2 * BLASBucket::Count * structByteCount, ResourceStates::ShaderAccessBits);
		desc.shortLifeTime = true;
		desc.structureByteCount = structByteCount;
		fd.tlasInstanceBuffer = CreateBuffer(desc);
	}
}

void Raytracing::ProcessWorld(world_t& world)
{
	if(!rhiInfo.hasInlineRaytracing)
	{
		return;
	}

	TagMapSurfacesRecursively(world.nodes);

	BLASBuilder staticBLASes[BLASBucket::Count];
	WorldSurfaceList surfaceList;
	s_world = &world;
	BuildBLASes(staticBLASBuffers, staticBLASes, &surfaceList);
	s_world = NULL;

	FrameData& fd = frameData[GetRTFrameIndex()];

	// create ASes
	BeginTempCommandList();
	for(uint32_t i = 0; i < BLASBucket::Count; i++)
	{
		BLASBuilder& bucket = staticBLASes[i];
		if(bucket.IsEmpty())
		{
			continue;
		}

		BLASBuildBuffers& buffers = fd.blasBuildBuffers[i];
		BLASDesc desc = {};
		desc.name = va("static BLAS %s", GetBLASBucketName(i));
		desc.vertexBuffer = buffers.vertexBuffer;
		desc.indexBuffer = buffers.indexBuffer;
		desc.meshes = bucket.buildMeshes;
		desc.meshCount = bucket.meshCount;
		CmdCreateBLAS(&staticBLASBuffers[i].blasBuffer, desc);
	}
	EndTempCommandList();
	for(uint32_t i = 0; i < BLASBucket::Count; i++)
	{
		BLASBuilder& bucket = staticBLASes[i];
		if(bucket.IsEmpty())
		{
			continue;
		}

		free(bucket.buildMeshes);
	}

	staticTLASInstanceCount = 0;
	tlasInstanceDescs.Clear();
	for(uint32_t i = 0; i < BLASBucket::Count; i++)
	{
		BLASBuilder& bucket = staticBLASes[i];
		if(bucket.IsEmpty())
		{
			continue;
		}

		TLASInstanceDesc inst;
		inst = {};
		inst.blasBuffer = staticBLASBuffers[i].blasBuffer;
		inst.cullMode = (cullType_t)i;
		inst.instanceId = staticTLASInstanceCount++;
		inst.instanceMask = 0xFF;
		inst.transform[0] = 1.0f;
		inst.transform[4] = 1.0f;
		inst.transform[8] = 1.0f;
		tlasInstanceDescs.Add(inst);
	}
}

void Raytracing::BeginFrame(bool wantUpdate)
{
	if(!rhiInfo.hasInlineRaytracing)
	{
		return;
	}

	if(tr.world == NULL || tr.sceneCounterRT == 0)
	{
		return;
	}

	FrameData& fd = frameData[GetRTFrameIndex()];

	if((crp_updateRTAS->integer == 0 || !wantUpdate) &&
		!IsNullHandle(fd.tlasBuffer))
	{
		return;
	}

	backEnd.refdef = tr.rtRefdef;

	BeginTempCommandList();
	const uint32_t renderPass = srp.BeginRenderPass("RTAS Build", 1.0f, 1.0f, 1.0f);
	BLASBuilder dynamicBLASes[BLASBucket::Count];
	DynamicSurfaceList surfaceList;
	BuildBLASes(fd.dynamicBLASBuffers, dynamicBLASes, &surfaceList);
	for(uint32_t i = 0; i < BLASBucket::Count; i++)
	{
		BLASBuilder& bucket = dynamicBLASes[i];
		if(bucket.IsEmpty())
		{
			continue;
		}

		BLASBuildBuffers& buffers = fd.blasBuildBuffers[i];
		BLASDesc desc = {};
		desc.name = va("dynamic BLAS %s", GetBLASBucketName(i));
		desc.vertexBuffer = buffers.vertexBuffer;
		desc.indexBuffer = buffers.indexBuffer;
		desc.meshes = bucket.buildMeshes;
		desc.meshCount = bucket.meshCount;
		CmdCreateBLAS(&fd.dynamicBLASBuffers[i].blasBuffer, desc);
	}
	{
		uint32_t instanceId = staticTLASInstanceCount;
		tlasInstanceDescs.count = staticTLASInstanceCount;
		for(uint32_t i = 0; i < BLASBucket::Count; i++)
		{
			BLASBuilder& bucket = dynamicBLASes[i];
			if(bucket.IsEmpty())
			{
				continue;
			}

			TLASInstanceDesc inst = {};
			inst.blasBuffer = fd.dynamicBLASBuffers[i].blasBuffer;
			inst.cullMode = (cullType_t)i;
			inst.instanceId = instanceId++;
			inst.instanceMask = 0xFF;
			inst.transform[0] = 1.0f;
			inst.transform[4] = 1.0f;
			inst.transform[8] = 1.0f;
			tlasInstanceDescs.Add(inst);
		}

		TLASDesc desc = {};
		desc.instanceCount = tlasInstanceDescs.count;
		desc.instances = tlasInstanceDescs.items;
		CmdCreateTLAS(&fd.tlasBuffer, desc);
	}
	srp.EndRenderPass(renderPass);
	EndTempCommandList();

	TLASInstance* traceInstances = (TLASInstance*)BeginBufferUpload(fd.tlasInstanceBuffer);
	uint32_t instanceId = 0;
	for(uint32_t i = 0; i < ARRAY_LEN(staticBLASBuffers); i++)
	{
		const BLASBuffers& buffers = staticBLASBuffers[i];
		if(IsNullHandle(buffers.blasBuffer))
		{
			continue;
		}

		Q_assert(instanceId == tlasInstanceDescs[instanceId].instanceId);
		TLASInstance traceInst = {};
		traceInst.meshBufferIndex = GetBufferIndexSRV(buffers.meshBuffer);
		traceInst.vertexBufferIndex = GetBufferIndexSRV(buffers.vertexBuffer);
		traceInst.indexBufferIndex = GetBufferIndexSRV(buffers.indexBuffer);
		traceInst.cullMode = (uint32_t)tlasInstanceDescs[instanceId++].cullMode;
		*traceInstances++ = traceInst;
	}
	for(uint32_t i = 0; i < ARRAY_LEN(fd.dynamicBLASBuffers); i++)
	{
		const BLASBuffers& buffers = fd.dynamicBLASBuffers[i];
		if(IsNullHandle(buffers.blasBuffer))
		{
			continue;
		}

		Q_assert(instanceId == tlasInstanceDescs[instanceId].instanceId);
		TLASInstance traceInst = {};
		traceInst.meshBufferIndex = GetBufferIndexSRV(buffers.meshBuffer);
		traceInst.vertexBufferIndex = GetBufferIndexSRV(buffers.vertexBuffer);
		traceInst.indexBufferIndex = GetBufferIndexSRV(buffers.indexBuffer);
		traceInst.cullMode = (uint32_t)tlasInstanceDescs[instanceId++].cullMode;
		*traceInstances++ = traceInst;
	}
	EndBufferUpload(fd.tlasInstanceBuffer);

#if defined(_DEBUG)
	for(uint32_t i = 0; i < tlasInstanceDescs.count; i++)
	{
		Q_assert(tlasInstanceDescs[i].instanceId == i);
	}
#endif
}

void Raytracing::TagMapSurfacesRecursively(mnode_t* node)
{
	do
	{
		if(node->contents != CONTENTS_NODE)
		{
			break;
		}

		// recurse down the children, front side first
		TagMapSurfacesRecursively(node->children[0]);

		// tail recurse
		node = node->children[1];
	} 
	while(true);

	// add the individual surfaces
	int c = node->nummarksurfaces;
	msurface_t** mark = node->firstmarksurface;
	while(c--)
	{
		msurface_t* const surf = *mark++;
		if(surf->shader->isSky)
		{
			continue;
		}

		if(IsStaticBLASSurface(surf))
		{
			surf->rtSurfType = RTST_STATIC;
		}
		else
		{
			surf->rtSurfType = RTST_DYNAMIC;
		}
	}
}

void Raytracing::EnsureBuffersAreLargeEnough(Raytracing::BLASBuildBuffers& buffers, uint32_t maxVertexCount, uint32_t maxIndexCount)
{
	{
		BufferDesc desc("BLAS build vertex", 2 * maxVertexCount * sizeof(vec3_t), ResourceStates::Common);
		desc.shortLifeTime = true;
		CreateOrGrowBuffer(buffers.vertexBuffer, buffers.vertexBufferByteCount, desc);
	}
	{
		BufferDesc desc("BLAS build index", 2 * maxIndexCount * sizeof(uint32_t), ResourceStates::Common);
		desc.shortLifeTime = true;
		CreateOrGrowBuffer(buffers.indexBuffer, buffers.indexBufferByteCount, desc);
	}
}

void Raytracing::EnsureBuffersAreLargeEnough(Raytracing::BLASBuffers& buffers, uint32_t maxVertexCount, uint32_t maxIndexCount, uint32_t maxMeshCount)
{
	{
		const uint32_t structByteCount = sizeof(BLASVertex);
		BufferDesc desc("BLAS support vertex", maxVertexCount * structByteCount, ResourceStates::ShaderAccessBits);
		desc.shortLifeTime = true;
		desc.structureByteCount = structByteCount;
		CreateOrGrowBuffer(buffers.vertexBuffer, buffers.vertexBufferByteCount, desc);
	}
	{
		const uint32_t structByteCount = sizeof(uint32_t);
		BufferDesc desc("BLAS support index", maxIndexCount * structByteCount, ResourceStates::ShaderAccessBits);
		desc.shortLifeTime = true;
		desc.structureByteCount = structByteCount;
		CreateOrGrowBuffer(buffers.indexBuffer, buffers.indexBufferByteCount, desc);
	}
	{
		const uint32_t structByteCount = sizeof(BLASMesh);
		BufferDesc desc("BLAS support mesh", maxMeshCount * structByteCount, ResourceStates::ShaderAccessBits);
		desc.shortLifeTime = true;
		desc.structureByteCount = structByteCount;
		CreateOrGrowBuffer(buffers.meshBuffer, buffers.meshBufferByteCount, desc);
	}
}

void Raytracing::BuildBLASes(BLASBuffers* blasBuffers, BLASBuilder* blasBuilders, ISurfaceList* surfaceList)
{
	tess.tessellator = Tessellator::None;
	tr.forceHighestLod = true;

	memset(blasBuilders, 0, sizeof(BLASBuilder) * BLASBucket::Count);

	const double originalTime = backEnd.refdef.floatTime;

	FrameData& fd = frameData[GetRTFrameIndex()];

	// gather stats on all surfaces we can bake
	for(uint32_t i = 0, count = surfaceList->GetSurfaceCount(); i < count; i++)
	{
		Surface surface;
		if(surfaceList->GetSurface(surface, i))
		{
			continue;
		}

		int surfVertexCount, surfIndexCount;
		if(EstimateTessellatedSize(surfVertexCount, surfIndexCount, surface.surface, surface.shader, surface.entityNum, originalTime))
		{
			continue;
		}

		BLASBuilder& bucket = blasBuilders[GetBLASBucketIndex(surface.shader)];
		bucket.totalVertexCount += surfVertexCount;
		bucket.totalIndexCount += surfIndexCount;
		bucket.meshCount++;
	}

	// correct the vertex and index counts since the estimations might be a little off
	for(uint32_t i = 0; i < BLASBucket::Count; i++)
	{
		BLASBuilder& bucket = blasBuilders[i];
		if(!bucket.IsEmpty())
		{
			bucket.totalVertexCount = max(2 * bucket.totalVertexCount, 8192u);
			bucket.totalIndexCount = max(2 * bucket.totalIndexCount, 32768u);
		}
	}

	// create buffers and map them
	for(uint32_t i = 0; i < BLASBucket::Count; i++)
	{
		BLASBuilder& bucket = blasBuilders[i];
		if(bucket.IsEmpty())
		{
			continue;
		}

		EnsureBuffersAreLargeEnough(fd.blasBuildBuffers[i], bucket.totalVertexCount, bucket.totalIndexCount);
		bucket.buildMeshes = (BLASMeshDesc*)malloc(bucket.meshCount * sizeof(BLASMeshDesc));
		if(bucket.buildMeshes == NULL)
		{
			ri.Error(ERR_FATAL, "Failed to allocate %d BLASMeshDesc instances\n", (int)bucket.meshCount);
		}
		bucket.buildVertices = (float*)BeginBufferUpload(fd.blasBuildBuffers[i].vertexBuffer);
		bucket.buildIndices = (uint32_t*)BeginBufferUpload(fd.blasBuildBuffers[i].indexBuffer);

		EnsureBuffersAreLargeEnough(blasBuffers[i], bucket.totalVertexCount, bucket.totalIndexCount, bucket.meshCount);
		bucket.traceVertices = (BLASVertex*)BeginBufferUpload(blasBuffers[i].vertexBuffer);
		bucket.traceIndices = (uint32_t*)BeginBufferUpload(blasBuffers[i].indexBuffer);
		bucket.traceMeshes = (BLASMesh*)BeginBufferUpload(blasBuffers[i].meshBuffer);
	}

	// upload vertex and index data
	for(uint32_t i = 0, count = surfaceList->GetSurfaceCount(); i < count; i++)
	{
		Surface surface;
		if(surfaceList->GetSurface(surface, i))
		{
			continue;
		}

		int surfVertexCount, surfIndexCount;
		if(Tessellate(surfVertexCount, surfIndexCount, surface.surface, surface.shader, surface.entityNum, originalTime))
		{
			continue;
		}

		BLASBuilder& bucket = blasBuilders[GetBLASBucketIndex(surface.shader)];

		// tess.xyz is an array of vec4_t
		for(uint32_t v = 0; v < surfVertexCount; v++)
		{
			if(surface.entityNum == ENTITYNUM_WORLD)
			{
				bucket.buildVertices[0] = tess.xyz[v][0];
				bucket.buildVertices[1] = tess.xyz[v][1];
				bucket.buildVertices[2] = tess.xyz[v][2];
			}
			else
			{
				const float original[3] = { tess.xyz[v][0], tess.xyz[v][1], tess.xyz[v][2] };
				float newPos[3];
				TransformPoint(original, backEnd.modelMatrix, newPos);
				bucket.buildVertices[0] = newPos[0];
				bucket.buildVertices[1] = newPos[1];
				bucket.buildVertices[2] = newPos[2];
			}
			bucket.buildVertices += 3;

			bucket.traceVertices->texCoords[0] = tess.svars[0].texcoords[v][0];
			bucket.traceVertices->texCoords[1] = tess.svars[0].texcoords[v][1];
			bucket.traceVertices->color[0] = tess.svars[0].colors[v][0];
			bucket.traceVertices->color[1] = tess.svars[0].colors[v][1];
			bucket.traceVertices->color[2] = tess.svars[0].colors[v][2];
			bucket.traceVertices->color[3] = tess.svars[0].colors[v][3];
			bucket.traceVertices++;
		}

		memcpy(bucket.buildIndices, tess.indexes, surfIndexCount * sizeof(uint32_t));
		bucket.buildIndices += surfIndexCount;

		memcpy(bucket.traceIndices, tess.indexes, surfIndexCount * sizeof(uint32_t));
		bucket.traceIndices += surfIndexCount;

		const shaderStage_t& stage0 = *surface.shader->stages[0];
		const image_t& image0 = *stage0.bundle.image[0];
		bucket.buildMeshes[bucket.meshIndex].firstVertex = bucket.firstVertex;
		bucket.buildMeshes[bucket.meshIndex].vertexCount = surfVertexCount;
		bucket.buildMeshes[bucket.meshIndex].firstIndex = bucket.firstIndex;
		bucket.buildMeshes[bucket.meshIndex].indexCount = surfIndexCount;
		bucket.buildMeshes[bucket.meshIndex].isFullyOpaque = surface.shader->isOpaque && !surface.shader->isAlphaTestedOpaque;
		bucket.traceMeshes[bucket.meshIndex].firstVertex = bucket.firstVertex;
		bucket.traceMeshes[bucket.meshIndex].firstIndex = bucket.firstIndex;
		bucket.traceMeshes[bucket.meshIndex].textureIndex = image0.textureIndex;
		bucket.traceMeshes[bucket.meshIndex].samplerIndex = GetSamplerIndex(image0.wrapClampMode, TextureFilter::Linear);
		bucket.traceMeshes[bucket.meshIndex].alphaTestMode = AlphaTestShaderConstFromStateBits(stage0.stateBits);
		bucket.traceMeshes[bucket.meshIndex].blendBits = stage0.stateBits & GLS_BLEND_BITS;
		bucket.meshIndex++;

		bucket.firstVertex += surfVertexCount;
		bucket.firstIndex += surfIndexCount;
	}

	// unmap buffers
	for(uint32_t i = 0; i < BLASBucket::Count; i++)
	{
		BLASBuilder& bucket = blasBuilders[i];
		if(bucket.IsEmpty())
		{
			continue;
		}

		EndBufferUpload(fd.blasBuildBuffers[i].vertexBuffer);
		EndBufferUpload(fd.blasBuildBuffers[i].indexBuffer);
		bucket.buildVertices = NULL;
		bucket.buildIndices = NULL;

		EndBufferUpload(blasBuffers[i].vertexBuffer);
		EndBufferUpload(blasBuffers[i].indexBuffer);
		EndBufferUpload(blasBuffers[i].meshBuffer);
		bucket.traceVertices = NULL;
		bucket.traceIndices = NULL;
		bucket.traceMeshes = NULL;
	}

	backEnd.refdef.floatTime = originalTime;
	tr.forceHighestLod = false;
}

uint32_t Raytracing::WorldSurfaceList::GetSurfaceCount()
{
	return s_world->numsurfaces;
}

bool Raytracing::WorldSurfaceList::GetSurface(Surface& surface, uint32_t index)
{
	Q_assert(index < (uint32_t)s_world->numsurfaces);

	const msurface_t& surf = s_world->surfaces[index];
	if(surf.rtSurfType != RTST_STATIC || surf.shader->numStages <= 0)
	{
		return true;
	}

	surface.surface = surf.data;
	surface.shader = surf.shader;
	surface.entityNum = ENTITYNUM_WORLD;

	return false;
}

uint32_t Raytracing::DynamicSurfaceList::GetSurfaceCount()
{
	return tr.numRTSurfs + tr.world->numsurfaces;
}

bool Raytracing::DynamicSurfaceList::GetSurface(Surface& surface, uint32_t index)
{
	Q_assert(index < (uint32_t)tr.numRTSurfs + (uint32_t)tr.world->numsurfaces);

	bool skip = false;
	if(index < tr.numRTSurfs)
	{
		const rtSurf_t& surf = tr.rtSurfs[index];
		surface.surface = surf.surface;
		surface.shader = surf.shader;
		surface.entityNum = surf.entityNum;
	}
	else
	{
		index -= tr.numRTSurfs;
		Q_assert(index < (uint32_t)tr.world->numsurfaces);

		const msurface_t& surf = tr.world->surfaces[index];
		if(surf.rtSurfType != RTST_DYNAMIC)
		{
			skip = true;
		}

		surface.surface = surf.data;
		surface.shader = surf.shader;
		surface.entityNum = ENTITYNUM_WORLD;
	}

	skip = skip || surface.shader->numStages <= 0;

	return skip;
}

uint32_t Raytracing::GetTLASBufferIndex()
{
	const HBuffer tlasBuffer = frameData[GetRTFrameIndex()].tlasBuffer;
	if(IsNullHandle(tlasBuffer))
	{
		return 0;
	}

	return GetBufferIndexSRV(tlasBuffer);
}

uint32_t Raytracing::GetInstanceBufferIndex()
{
	const HBuffer tlasInstanceBuffer = frameData[GetRTFrameIndex()].tlasInstanceBuffer;
	if(IsNullHandle(tlasInstanceBuffer))
	{
		return 0;
	}

	return GetBufferIndexSRV(tlasInstanceBuffer);
}

bool Raytracing::CanRaytrace()
{
	return
		rhiInfo.hasInlineRaytracing &&
		GetTLASBufferIndex() != 0 &&
		GetInstanceBufferIndex() != 0;
}
