#include "OpenGLRenderPass.h"

#include "GlRenderConstants.h"

#include <map>

OpenGLRenderPass::OpenGLRenderPass(OpenGLRenderer& renderer) :
	mRenderer(renderer)
{
}

void OpenGLRenderPass::Render(
	bool hasInputSource,
	const std::vector<RuntimeRenderState>& layerStates,
	unsigned inputFrameWidth,
	unsigned inputFrameHeight,
	unsigned captureTextureWidth,
	VideoIOPixelFormat inputPixelFormat,
	unsigned historyCap,
	const TextBindingUpdater& updateTextBinding,
	const GlobalParamsUpdater& updateGlobalParams)
{
	glDisable(GL_SCISSOR_TEST);
	glDisable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);
	if (hasInputSource)
	{
		RenderDecodePass(inputFrameWidth, inputFrameHeight, captureTextureWidth, inputPixelFormat);
	}
	else
	{
		glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
		glViewport(0, 0, inputFrameWidth, inputFrameHeight);
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
	}

	std::vector<LayerProgram>& layerPrograms = mRenderer.LayerPrograms();
	if (layerStates.empty() || layerPrograms.empty())
	{
		glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
		glBlitFramebuffer(0, 0, inputFrameWidth, inputFrameHeight, 0, 0, inputFrameWidth, inputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
		glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
	}
	else
	{
		const std::vector<RenderPassDescriptor>& passes = BuildLayerPassDescriptors(layerStates, layerPrograms);
		for (const RenderPassDescriptor& pass : passes)
		{
			RenderLayerPass(
				pass,
				inputFrameWidth,
				inputFrameHeight,
				historyCap,
				updateTextBinding,
				updateGlobalParams);
		}
	}

	mRenderer.TemporalHistory().PushSourceFramebuffer(mRenderer.DecodeFramebuffer(), inputFrameWidth, inputFrameHeight);
	mRenderer.FeedbackBuffers().FinalizeFrame();
}

void OpenGLRenderPass::RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat)
{
	glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
	glViewport(0, 0, inputFrameWidth, inputFrameHeight);
	glClear(GL_COLOR_BUFFER_BIT);
	glActiveTexture(GL_TEXTURE0 + kPackedVideoTextureUnit);
	glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
	glBindVertexArray(mRenderer.FullscreenVertexArray());
	glUseProgram(mRenderer.DecodeProgram());

	const GLint packedResolutionLocation = mRenderer.DecodePackedResolutionLocation();
	const GLint decodedResolutionLocation = mRenderer.DecodeDecodedResolutionLocation();
	const GLint inputPixelFormatLocation = mRenderer.DecodeInputPixelFormatLocation();
	if (packedResolutionLocation >= 0)
		glUniform2f(packedResolutionLocation, static_cast<float>(captureTextureWidth), static_cast<float>(inputFrameHeight));
	if (decodedResolutionLocation >= 0)
		glUniform2f(decodedResolutionLocation, static_cast<float>(inputFrameWidth), static_cast<float>(inputFrameHeight));
	if (inputPixelFormatLocation >= 0)
		glUniform1i(inputPixelFormatLocation, inputPixelFormat == VideoIOPixelFormat::V210 ? 1 : 0);

	glDrawArrays(GL_TRIANGLES, 0, 3);

	glUseProgram(0);
	glBindVertexArray(0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE0);
}

std::vector<RenderPassDescriptor> OpenGLRenderPass::BuildLayerPassDescriptors(
	const std::vector<RuntimeRenderState>& layerStates,
	std::vector<LayerProgram>& layerPrograms) const
{
	// Flatten the layer stack into concrete GL passes. A layer may now contain
	// several shader passes, but the outer stack still sees one visible output
	// per layer.
	std::vector<RenderPassDescriptor>& passes = mPassScratch;
	passes.clear();
	const std::size_t passCount = layerStates.size() < layerPrograms.size() ? layerStates.size() : layerPrograms.size();
	std::size_t descriptorCount = 0;
	for (std::size_t index = 0; index < passCount; ++index)
		descriptorCount += layerPrograms[index].passes.size();
	passes.reserve(descriptorCount);

	GLuint sourceTexture = mRenderer.DecodedTexture();
	GLuint sourceFramebuffer = mRenderer.DecodeFramebuffer();
	for (std::size_t index = 0; index < passCount; ++index)
	{
		const RuntimeRenderState& state = layerStates[index];
		LayerProgram& layerProgram = layerPrograms[index];
		if (layerProgram.passes.empty())
			continue;

		// Preserve the original two-target layer ping-pong. Intermediate passes
		// inside this layer are routed through pooled temporary targets instead.
		const std::size_t remaining = layerStates.size() - index;
		const bool writeToMain = (remaining % 2) == 1;
		const GLuint layerOutputTexture = writeToMain ? mRenderer.CompositeTexture() : mRenderer.LayerTempTexture();
		const GLuint layerOutputFramebuffer = writeToMain ? mRenderer.CompositeFramebuffer() : mRenderer.LayerTempFramebuffer();
		const RenderPassOutputTarget layerOutputTarget = writeToMain ? RenderPassOutputTarget::Composite : RenderPassOutputTarget::LayerTemp;

		const GLuint layerInputTexture = sourceTexture;
		const GLuint layerInputFramebuffer = sourceFramebuffer;
		GLuint previousPassTexture = layerInputTexture;
		GLuint previousPassFramebuffer = layerInputFramebuffer;
		std::map<std::string, std::pair<GLuint, GLuint>> namedOutputs;
		std::size_t temporaryTargetIndex = 0;

		for (std::size_t passIndex = 0; passIndex < layerProgram.passes.size(); ++passIndex)
		{
			PassProgram& passProgram = layerProgram.passes[passIndex];
			const bool lastPassForLayer = passIndex + 1 == layerProgram.passes.size();
			const std::string outputName = passProgram.outputName.empty() ? passProgram.passId : passProgram.outputName;
			const bool writesLayerOutput = outputName == "layerOutput" || lastPassForLayer;

			GLuint passSourceTexture = previousPassTexture;
			GLuint passSourceFramebuffer = previousPassFramebuffer;
			if (!passProgram.inputNames.empty())
			{
				// v1 multipass uses the first declared input as gVideoInput.
				// Later inputs are parsed for forward compatibility.
				const std::string& inputName = passProgram.inputNames.front();
				if (inputName == "layerInput")
				{
					passSourceTexture = layerInputTexture;
					passSourceFramebuffer = layerInputFramebuffer;
				}
				else if (inputName == "previousPass")
				{
					passSourceTexture = previousPassTexture;
					passSourceFramebuffer = previousPassFramebuffer;
				}
				else
				{
					auto namedOutputIt = namedOutputs.find(inputName);
					if (namedOutputIt != namedOutputs.end())
					{
						passSourceTexture = namedOutputIt->second.first;
						passSourceFramebuffer = namedOutputIt->second.second;
					}
				}
			}

			GLuint passDestinationTexture = layerOutputTexture;
			GLuint passDestinationFramebuffer = layerOutputFramebuffer;
			RenderPassOutputTarget outputTarget = layerOutputTarget;
			if (!writesLayerOutput)
			{
				// Temporary targets are reserved when the shader stack is
				// committed, avoiding texture allocation during playback.
				if (temporaryTargetIndex < mRenderer.TemporaryRenderTargetCount())
				{
					const RenderTarget& temporaryTarget = mRenderer.TemporaryRenderTarget(temporaryTargetIndex);
					++temporaryTargetIndex;
					passDestinationTexture = temporaryTarget.texture;
					passDestinationFramebuffer = temporaryTarget.framebuffer;
					outputTarget = RenderPassOutputTarget::Temporary;
				}
			}

			RenderPassDescriptor pass;
			pass.kind = RenderPassKind::LayerEffect;
			pass.outputTarget = outputTarget;
			pass.passIndex = passes.size();
			pass.passId = passProgram.passId;
			pass.layerId = state.layerId;
			pass.shaderId = state.shaderId;
			pass.layerInputTexture = layerInputTexture;
			pass.sourceTexture = passSourceTexture;
			pass.sourceFramebuffer = passIndex == 0 ? layerInputFramebuffer : passSourceFramebuffer;
			pass.destinationTexture = passDestinationTexture;
			pass.destinationFramebuffer = passDestinationFramebuffer;
			pass.layerProgram = &layerProgram;
			pass.passProgram = &passProgram;
			pass.layerState = &state;
			pass.capturePreLayerHistory = passIndex == 0 && state.temporalHistorySource == TemporalHistorySource::PreLayerInput;
			pass.captureFeedbackWrite = state.feedback.enabled && passProgram.passId == state.feedback.writePassId;
			passes.push_back(pass);

			// A later pass can reference either the explicit output name or the
			// pass id, which keeps small manifests pleasant to write.
			namedOutputs[outputName] = std::make_pair(passDestinationTexture, passDestinationFramebuffer);
			namedOutputs[passProgram.passId] = std::make_pair(passDestinationTexture, passDestinationFramebuffer);
			previousPassTexture = passDestinationTexture;
			previousPassFramebuffer = passDestinationFramebuffer;
		}

		sourceTexture = layerOutputTexture;
		sourceFramebuffer = layerOutputFramebuffer;
	}

	return passes;
}

void OpenGLRenderPass::RenderLayerPass(
	const RenderPassDescriptor& pass,
	unsigned inputFrameWidth,
	unsigned inputFrameHeight,
	unsigned historyCap,
	const TextBindingUpdater& updateTextBinding,
	const GlobalParamsUpdater& updateGlobalParams)
{
	if (pass.passProgram == nullptr || pass.layerState == nullptr)
		return;

	RenderShaderProgram(
		pass.layerInputTexture,
		pass.sourceTexture,
		pass.destinationFramebuffer,
		*pass.passProgram,
		*pass.layerState,
		inputFrameWidth,
		inputFrameHeight,
		historyCap,
		updateTextBinding,
		updateGlobalParams);

	if (pass.capturePreLayerHistory)
		mRenderer.TemporalHistory().PushPreLayerFramebuffer(pass.layerId, pass.sourceFramebuffer, inputFrameWidth, inputFrameHeight);
	if (pass.captureFeedbackWrite)
		mRenderer.FeedbackBuffers().CaptureFeedbackFramebuffer(pass.layerId, pass.destinationFramebuffer, inputFrameWidth, inputFrameHeight);
}

void OpenGLRenderPass::RenderShaderProgram(
	GLuint layerInputTexture,
	GLuint sourceTexture,
	GLuint destinationFrameBuffer,
	PassProgram& passProgram,
	const RuntimeRenderState& state,
	unsigned inputFrameWidth,
	unsigned inputFrameHeight,
	unsigned historyCap,
	const TextBindingUpdater& updateTextBinding,
	const GlobalParamsUpdater& updateGlobalParams)
{
	for (LayerProgram::TextBinding& textBinding : passProgram.textBindings)
	{
		std::string textError;
		if (!updateTextBinding(state, textBinding, textError))
			OutputDebugStringA((textError + "\n").c_str());
	}

	glBindFramebuffer(GL_FRAMEBUFFER, destinationFrameBuffer);
	glViewport(0, 0, inputFrameWidth, inputFrameHeight);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	const std::vector<GLuint> sourceHistoryTextures = mRenderer.TemporalHistory().ResolveSourceHistoryTextures(sourceTexture, state.isTemporal ? historyCap : 0);
	const std::vector<GLuint> temporalHistoryTextures = mRenderer.TemporalHistory().ResolveTemporalHistoryTextures(state, sourceTexture, state.isTemporal ? historyCap : 0);
	const GLuint feedbackTexture = mRenderer.FeedbackBuffers().ResolveReadTexture(state);
	const ShaderTextureBindings::RuntimeTextureBindingPlan texturePlan =
		mTextureBindings.BuildLayerRuntimeBindingPlan(passProgram, sourceTexture, layerInputTexture, state, feedbackTexture, sourceHistoryTextures, temporalHistoryTextures);
	mTextureBindings.BindRuntimeTexturePlan(texturePlan);
	glBindVertexArray(mRenderer.FullscreenVertexArray());
	glUseProgram(passProgram.program);
	// The UBO is shared by every pass in a layer; texture routing is what
	// changes from pass to pass.
	updateGlobalParams(
		state,
		mRenderer.TemporalHistory().SourceAvailableCount(),
		mRenderer.TemporalHistory().AvailableCountForLayer(state.layerId),
		mRenderer.FeedbackBuffers().FeedbackAvailable(state));
	glDrawArrays(GL_TRIANGLES, 0, 3);
	glUseProgram(0);
	glBindVertexArray(0);
	mTextureBindings.UnbindRuntimeTexturePlan(texturePlan);
}
