/*****************************************************************************
The Dark Mod GPL Source Code

This file is part of the The Dark Mod Source Code, originally based
on the Doom 3 GPL Source Code as published in 2011.

The Dark Mod Source Code 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 3 of the License,
or (at your option) any later version. For details, see LICENSE.TXT.

Project: The Dark Mod (http://www.thedarkmod.com/)

******************************************************************************/

#include "precompiled.h"
#pragma hdrstop

#include "renderer/backend/RenderBackend.h"

#include "renderer/backend/stages/AmbientOcclusionStage.h"
#include "renderer/backend/GLSLProgram.h"
#include "renderer/backend/FrameBufferManager.h"
#include "renderer/backend/FrameBuffer.h"

RenderBackend renderBackendImpl;
RenderBackend *renderBackend = &renderBackendImpl;

namespace {
	void CreateLightgemFbo( FrameBuffer *fbo ) {
		fbo->Init( DARKMOD_LG_RENDER_WIDTH, DARKMOD_LG_RENDER_WIDTH );
		fbo->AddColorRenderBuffer( 0, GL_RGB8 );
		fbo->AddDepthStencilRenderBuffer( GL_DEPTH24_STENCIL8 );
	}
}

RenderBackend::RenderBackend() {}

void RenderBackend::Init() {
	initialized = true;

	depthStage.Init();
	interactionStage.Init();
	stencilShadowStage.Init();
	shadowMapStage.Init();
	surfacePassesStage.Init();
	lightPassesStage.Init();
	frobOutlineStage.Init();
	tonemapStage.Init();

	lightgemFbo = frameBuffers->CreateFromGenerator( "lightgem", CreateLightgemFbo );
	qglGenBuffers( 3, lightgemPbos );
	for ( int i = 0; i < 3; ++i ) {
		qglBindBuffer( GL_PIXEL_PACK_BUFFER, lightgemPbos[i] );
		qglBufferData( GL_PIXEL_PACK_BUFFER, DARKMOD_LG_RENDER_WIDTH * DARKMOD_LG_RENDER_WIDTH * DARKMOD_LG_BPP, nullptr, GL_STREAM_READ );
	}
	qglBindBuffer( GL_PIXEL_PACK_BUFFER, 0 ); // reset to default to allow sysmem ReadPixels if LG disabled
}

void RenderBackend::Shutdown() {
	if (!initialized)
		return;
	qglDeleteBuffers( 3, lightgemPbos );
	
	tonemapStage.Shutdown();
	frobOutlineStage.Shutdown();
	lightPassesStage.Shutdown();
	surfacePassesStage.Shutdown();
	shadowMapStage.Shutdown();
	stencilShadowStage.Shutdown();
	interactionStage.Shutdown();
	depthStage.Shutdown();
}

void RenderBackend::DrawView( const viewDef_t *viewDef, bool colorIsBackground ) {
	TRACE_CPU_SCOPE_FORMAT( "DrawView", "viewCount = %d\nID = %d", viewDef->viewCount, viewDef->renderView.viewID );
	TRACE_GL_SCOPE( "DrawView" );

	// skip render bypasses everything that has models, assuming
	// them to be 3D views, but leaves 2D rendering visible
	if ( viewDef->viewEntitys && r_skipRender.GetBool() ) {
		return;
	}

	// skip render context sets the wgl context to NULL,
	// which should factor out the API cost, under the assumption
	// that all gl calls just return if the context isn't valid
	if ( viewDef->viewEntitys && r_skipRenderContext.GetBool() ) {
		GLimp_DeactivateContext();
	}
	backEnd.pc.c_surfaces += viewDef->numDrawSurfs;

	RB_ShowOverdraw();


	backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL;

	// clear the framebuffer, set the projection matrix, etc
	RB_BeginDrawingView( colorIsBackground );

	backEnd.lightScale = r_lightScale.GetFloat();
	backEnd.overBright = 1.0f;

	drawSurf_t **drawSurfs = ( drawSurf_t ** )&viewDef->drawSurfs[ 0 ];
	int numDrawSurfs = viewDef->numDrawSurfs;

	// if we are just doing 2D rendering, no need to fill the depth buffer
	if ( viewDef->viewEntitys ) {
		// fill the depth buffer and clear color buffer to black except on subviews
		depthStage.DrawDepth( viewDef, drawSurfs, numDrawSurfs );
		if( ambientOcclusion->ShouldEnableForCurrentView() ) {
			ambientOcclusion->ComputeSSAOFromDepth();
		}
		DrawShadowsAndInteractions( viewDef );
	}

	int beforePostproc = 0;
	while ( beforePostproc < numDrawSurfs && drawSurfs[beforePostproc]->sort < SS_POST_PROCESS )
		beforePostproc++;
	const drawSurf_t **postprocSurfs = (const drawSurf_t **)drawSurfs + beforePostproc;
	int postprocCount = numDrawSurfs - beforePostproc;

	surfacePassesStage.DrawSurfaces( viewDef, (const drawSurf_t **)drawSurfs, beforePostproc );

	if (r_frobOutlineMode.IsModified())
	{
		r_frobOutlineMode.ClearModified();
		SetFrobOutlineMode(r_frobOutlineMode.GetInteger());
	}

	if ( (r_frobOutline.GetInteger() > 0 || r_newFrob.GetInteger() == 1) && !viewDef->IsLightGem() ) {
		frobOutlineStage.DrawFrobOutline( drawSurfs, numDrawSurfs , viewDef->renderView.isHighlightedEntityValuable );
	}

	LightPassesStage::DrawMask mask;
	mask.opaque = true;
	mask.translucent = false;
	lightPassesStage.DrawAllFogLights( viewDef, mask );

	lightPassesStage.DrawAllBlendLights( viewDef );
	volumetric->RenderAll( viewDef );

	if ( surfacePassesStage.NeedCurrentRenderTexture( viewDef, postprocSurfs, postprocCount ) )
		frameBuffers->UpdateCurrentRenderCopy();

	surfacePassesStage.DrawSurfaces( viewDef, postprocSurfs, postprocCount );

	// 2.08: second fog pass, translucent only
	mask.opaque = false;
	mask.translucent = true;
	lightPassesStage.DrawAllFogLights( viewDef, mask );

	RB_RenderDebugTools( drawSurfs, numDrawSurfs );

	// restore the context for 2D drawing if we were stubbing it out
	if ( r_skipRenderContext.GetBool() && viewDef->viewEntitys ) {
		GLimp_ActivateContext();
		RB_SetDefaultGLState();
	}
}

void RenderBackend::DrawLightgem( const viewDef_t *viewDef, byte *lightgemData ) {
	TRACE_GL_SCOPE( "DrawLightgem" );

	FrameBuffer *currentFbo = frameBuffers->activeFbo;
	FrameBuffer *renderFbo = frameBuffers->currentRenderFbo;
	frameBuffers->currentRenderFbo = lightgemFbo;
	lightgemFbo->Bind();
	
	DrawView( viewDef, false );

	{
		TRACE_GL_SCOPE( "CopyToPbo" );
		// asynchronously copy contents of the lightgem framebuffer to a pixel buffer
		qglBindBuffer( GL_PIXEL_PACK_BUFFER, lightgemPbos[currentLightgemPbo] );
		qglPixelStorei( GL_PACK_ALIGNMENT, 1 );	// otherwise small rows get padded to 32 bits
		qglReadPixels( 0, 0, DARKMOD_LG_RENDER_WIDTH, DARKMOD_LG_RENDER_WIDTH, GL_RGB, GL_UNSIGNED_BYTE, nullptr );
	}

	{
		TRACE_GL_SCOPE( "ReadFromPbo" );
		// advance PBO index and actually copy the data stored in that PBO to local memory
		// this PBO is from a previous frame, and data transfer should thus be reasonably fast
		currentLightgemPbo = ( currentLightgemPbo + 1 ) % 3;
		qglBindBuffer( GL_PIXEL_PACK_BUFFER, lightgemPbos[currentLightgemPbo] );
		qglGetBufferSubData( GL_PIXEL_PACK_BUFFER, 0, DARKMOD_LG_RENDER_WIDTH * DARKMOD_LG_RENDER_WIDTH * DARKMOD_LG_BPP, lightgemData );
	}

	qglBindBuffer( GL_PIXEL_PACK_BUFFER, 0 );
	currentFbo->Bind();
	frameBuffers->currentRenderFbo = renderFbo;
}

void RenderBackend::EndFrame() {}

void RenderBackend::DrawInteractionsWithShadowMapping( const viewDef_t *viewDef, const viewLight_t *vLight ) {
	TRACE_GL_SCOPE( "DrawLight_ShadowMap" );

	if ( vLight->globalShadows || vLight->localShadows ) {
		ShadowMapStage::DrawMask mask;

		mask.clear = true;
		mask.global = true;
		mask.local = false;
		shadowMapStage.DrawShadowMapSingleLight( viewDef, vLight, mask );

		interactionStage.DrawInteractions( viewDef, vLight, DSL_LOCAL, nullptr );

		mask.clear = false;
		mask.global = false;
		mask.local = true;
		shadowMapStage.DrawShadowMapSingleLight( viewDef, vLight, mask );

	} else {
		interactionStage.DrawInteractions( viewDef, vLight, DSL_LOCAL, nullptr );
	}

	interactionStage.DrawInteractions( viewDef, vLight, DSL_GLOBAL, nullptr );

	interactionStage.DrawInteractions( viewDef, vLight, DSL_TRANSLUCENT, nullptr );

	GLSLProgram::Deactivate();
}

void RenderBackend::DrawInteractionsWithStencilShadows( const viewDef_t *viewDef, const viewLight_t *vLight ) {
	TRACE_GL_SCOPE( "DrawLight_Stencil" );

	bool useShadowFbo = r_softShadowsQuality.GetBool() && !viewDef->IsLightGem();
	idScreenRect lightScissor = stencilShadowStage.ExpandScissorRectForSoftShadows( viewDef, vLight->scissorRect );

	// clear the stencil buffer if needed
	if ( vLight->globalShadows || vLight->localShadows ) {
		backEnd.currentScissor = lightScissor;
		FB_ApplyScissor();

		if ( useShadowFbo ) {
			frameBuffers->EnterShadowStencil();
		}
		qglClear( GL_STENCIL_BUFFER_BIT );
	} else {
		// no shadows, so no need to read or write the stencil buffer
		qglStencilFunc( GL_ALWAYS, 128, 255 );
	}

	if ( vLight->globalShadows ) {
		stencilShadowStage.DrawStencilShadows( viewDef, vLight, vLight->globalShadows );
		if ( useShadowFbo && r_multiSamples.GetInteger() > 1 ) {
			backEnd.currentScissor = lightScissor;
			FB_ApplyScissor();
			frameBuffers->ResolveShadowStencilAA();
		}
	}

	if ( useShadowFbo ) {
		frameBuffers->LeaveShadowStencil();
	}
	interactionStage.DrawInteractions( viewDef, vLight, DSL_LOCAL, nullptr );

	if ( useShadowFbo ) {
		frameBuffers->EnterShadowStencil();
	}

	if ( vLight->localShadows ) {
		stencilShadowStage.DrawStencilShadows( viewDef, vLight, vLight->localShadows );
		if ( useShadowFbo && r_multiSamples.GetInteger() > 1 ) {
			backEnd.currentScissor = lightScissor;
			FB_ApplyScissor();
			frameBuffers->ResolveShadowStencilAA();
		}
	}
	if ( vLight->globalShadows || vLight->localShadows ) {
		stencilShadowStage.FillStencilShadowMipmaps( viewDef, lightScissor );
	}

	if ( useShadowFbo ) {
		frameBuffers->LeaveShadowStencil();
	}

	interactionStage.DrawInteractions( viewDef, vLight, DSL_GLOBAL, &stencilShadowStage.stencilShadowMipmap );

	qglStencilFunc( GL_ALWAYS, 128, 255 );
	interactionStage.DrawInteractions( viewDef, vLight, DSL_TRANSLUCENT, nullptr );

	GLSLProgram::Deactivate();
}

void RenderBackend::DrawShadowsAndInteractions( const viewDef_t *viewDef ) {
	TRACE_GL_SCOPE( "LightInteractions" );

	bool singlePassShadowMaps = r_shadows.GetInteger() == 2 && r_shadowMapSinglePass.GetBool();
	if ( singlePassShadowMaps ) {
		for ( int pass = 0; pass < 2; pass++ ) {
			// draw all shadow maps: global on pass 0, add local on pass 1
			ShadowMapStage::DrawMask mask;
			mask.clear = ( pass == 0 );
			mask.global = ( pass == 0 );
			mask.local = ( pass == 1 );
			shadowMapStage.DrawShadowMapAllLights( viewDef, mask );

			// draw interactions for all shadow-mapped lights
			for ( viewLight_t *vLight = viewDef->viewLights; vLight; vLight = vLight->next ) {
				if ( !interactionStage.ShouldDrawLight( vLight ) || vLight->shadows != LS_MAPS )
					continue;

				if ( pass == 0 ) {
					interactionStage.DrawInteractions( viewDef, vLight, DSL_LOCAL, nullptr );
				} else {
					interactionStage.DrawInteractions( viewDef, vLight, DSL_GLOBAL, nullptr );
					interactionStage.DrawInteractions( viewDef, vLight, DSL_TRANSLUCENT, nullptr );
				}
			}
		}
	}

	// draw interactions and shadows for each light separately
	// note: even with single-pass shadow maps on, we might still have some stencil-shadowed lights to process here
	for ( viewLight_t *vLight = viewDef->viewLights; vLight; vLight = vLight->next ) {
		if ( !interactionStage.ShouldDrawLight( vLight ) )
			continue;
	
		if ( vLight->shadows == LS_MAPS ) {
			if ( !singlePassShadowMaps ) {
				DrawInteractionsWithShadowMapping( viewDef, vLight );
			}
		} else {
			DrawInteractionsWithStencilShadows( viewDef, vLight );
		}
	}

	// disable stencil shadow test
	qglStencilFunc( GL_ALWAYS, 128, 255 );
	GL_SelectTexture( 0 );
}

void RenderBackend::Tonemap() {
	tonemapStage.ApplyTonemap( frameBuffers->defaultFbo, globalImages->guiRenderImage );
}
