From b641cdf4343692743e15b2427f183a92890e118f Mon Sep 17 00:00:00 2001
From: havoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Date: Sun, 16 Oct 2011 09:54:40 +0000
Subject: [PATCH] removed cvars r_hdr and r_hdr_range - now the only way to get
 HDR rendering is using r_viewfbo 2 (which is faster and nicer) implemented
 fbo rendering for water, bloom and gamma, setting r_viewfbo to 1 gives a
 consistent speed gain because it avoids all texture copies

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@11426 d7cf8633-e32d-0410-b094-e92efae38249
---
 gl_rmain.c | 457 ++++++++++++++++++++++++++---------------------------
 menu.c     |  24 ++-
 r_shadow.c |  19 +--
 render.h   |  42 ++---
 4 files changed, 259 insertions(+), 283 deletions(-)

diff --git a/gl_rmain.c b/gl_rmain.c
index 0082d614..6c3af4a7 100644
--- a/gl_rmain.c
+++ b/gl_rmain.c
@@ -197,10 +197,8 @@ cvar_t r_bloom_resolution = {CVAR_SAVE, "r_bloom_resolution", "320", "what resol
 cvar_t r_bloom_colorexponent = {CVAR_SAVE, "r_bloom_colorexponent", "1", "how exaggerated the glow is"};
 cvar_t r_bloom_colorsubtract = {CVAR_SAVE, "r_bloom_colorsubtract", "0.125", "reduces bloom colors by a certain amount"};
 
-cvar_t r_hdr = {CVAR_SAVE, "r_hdr", "0", "enables High Dynamic Range bloom effect (higher quality version of r_bloom)"};
 cvar_t r_hdr_scenebrightness = {CVAR_SAVE, "r_hdr_scenebrightness", "1", "global rendering brightness"};
 cvar_t r_hdr_glowintensity = {CVAR_SAVE, "r_hdr_glowintensity", "1", "how bright light emitting textures should appear"};
-cvar_t r_hdr_range = {CVAR_SAVE, "r_hdr_range", "4", "how much dynamic range to render bloom with (equivalent to multiplying r_bloom_brighten by this value and dividing r_bloom_colorscale by this value)"};
 cvar_t r_hdr_irisadaptation = {CVAR_SAVE, "r_hdr_irisadaptation", "0", "adjust scene brightness according to light intensity at player location"};
 cvar_t r_hdr_irisadaptation_multiplier = {CVAR_SAVE, "r_hdr_irisadaptation_multiplier", "2", "brightness at which value will be 1.0"};
 cvar_t r_hdr_irisadaptation_minvalue = {CVAR_SAVE, "r_hdr_irisadaptation_minvalue", "0.5", "minimum value that can result from multiplier / brightness"};
@@ -4312,10 +4310,8 @@ void GL_Main_Init(void)
 	Cvar_RegisterVariable(&r_bloom_resolution);
 	Cvar_RegisterVariable(&r_bloom_colorexponent);
 	Cvar_RegisterVariable(&r_bloom_colorsubtract);
-	Cvar_RegisterVariable(&r_hdr);
 	Cvar_RegisterVariable(&r_hdr_scenebrightness);
 	Cvar_RegisterVariable(&r_hdr_glowintensity);
-	Cvar_RegisterVariable(&r_hdr_range);
 	Cvar_RegisterVariable(&r_hdr_irisadaptation);
 	Cvar_RegisterVariable(&r_hdr_irisadaptation_multiplier);
 	Cvar_RegisterVariable(&r_hdr_irisadaptation_minvalue);
@@ -5572,12 +5568,21 @@ static void R_Water_StartFrame(void)
 			if (p->texture_refraction)
 				R_FreeTexture(p->texture_refraction);
 			p->texture_refraction = NULL;
+			if (p->fbo_refraction)
+				R_Mesh_DestroyFramebufferObject(p->fbo_refraction);
+			p->fbo_refraction = 0;
 			if (p->texture_reflection)
 				R_FreeTexture(p->texture_reflection);
 			p->texture_reflection = NULL;
+			if (p->fbo_reflection)
+				R_Mesh_DestroyFramebufferObject(p->fbo_reflection);
+			p->fbo_reflection = 0;
 			if (p->texture_camera)
 				R_FreeTexture(p->texture_camera);
 			p->texture_camera = NULL;
+			if (p->fbo_camera)
+				R_Mesh_DestroyFramebufferObject(p->fbo_camera);
+			p->fbo_camera = 0;
 		}
 		memset(&r_fb.water, 0, sizeof(r_fb.water));
 		r_fb.water.texturewidth = texturewidth;
@@ -5758,24 +5763,45 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 		if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION))
 		{
 			if (!p->texture_refraction)
-				p->texture_refraction = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_refraction", planeindex), r_fb.water.texturewidth, r_fb.water.textureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
+				p->texture_refraction = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_refraction", planeindex), r_fb.water.texturewidth, r_fb.water.textureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
 			if (!p->texture_refraction)
 				goto error;
+			if (r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object)
+			{
+				if (r_fb.water.depthtexture == NULL)
+					r_fb.water.depthtexture = R_LoadTextureShadowMap2D(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, 24, false);
+				if (p->fbo_refraction == 0)
+					p->fbo_refraction = R_Mesh_CreateFramebufferObject(r_fb.water.depthtexture, p->texture_refraction, NULL, NULL, NULL);
+			}
 		}
 		else if (p->materialflags & MATERIALFLAG_CAMERA)
 		{
 			if (!p->texture_camera)
-				p->texture_camera = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_camera", planeindex), r_fb.water.camerawidth, r_fb.water.cameraheight, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCELINEAR, -1, NULL);
+				p->texture_camera = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_camera", planeindex), r_fb.water.camerawidth, r_fb.water.cameraheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR, -1, NULL);
 			if (!p->texture_camera)
 				goto error;
+			if (r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object)
+			{
+				if (r_fb.water.depthtexture == NULL)
+					r_fb.water.depthtexture = R_LoadTextureShadowMap2D(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, 24, false);
+				if (p->fbo_camera == 0)
+					p->fbo_camera = R_Mesh_CreateFramebufferObject(r_fb.water.depthtexture, p->texture_camera, NULL, NULL, NULL);
+			}
 		}
 
 		if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION))
 		{
 			if (!p->texture_reflection)
-				p->texture_reflection = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_reflection", planeindex), r_fb.water.texturewidth, r_fb.water.textureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
+				p->texture_reflection = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_reflection", planeindex), r_fb.water.texturewidth, r_fb.water.textureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
 			if (!p->texture_reflection)
 				goto error;
+			if (r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object)
+			{
+				if (r_fb.water.depthtexture == NULL)
+					r_fb.water.depthtexture = R_LoadTextureShadowMap2D(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, 24, false);
+				if (p->fbo_reflection == 0)
+					p->fbo_reflection = R_Mesh_CreateFramebufferObject(r_fb.water.depthtexture, p->texture_reflection, NULL, NULL, NULL);
+			}
 		}
 	}
 
@@ -5794,7 +5820,7 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 			r_refdef.view = myview;
 			if(r_water_scissormode.integer)
 			{
-				R_SetupView(true, fbo, depthtexture, colortexture);
+				R_SetupView(true, p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection);
 				if(R_ScissorForBBox(p->mins, p->maxs, myscissor))
 					continue; // FIXME the plane then still may get rendered but with broken texture, but it sure won't be visible
 			}
@@ -5817,7 +5843,7 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 			}
 
 			r_fb.water.hideplayer = r_water_hideplayer.integer >= 2;
-			R_ResetViewRendering3D(fbo, depthtexture, colortexture);
+			R_ResetViewRendering3D(p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection);
 			R_ClearScreen(r_refdef.fogenabled);
 			if(r_water_scissormode.integer & 2)
 				R_View_UpdateWithScissor(myscissor);
@@ -5825,9 +5851,10 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 				R_View_Update();
 			if(r_water_scissormode.integer & 1)
 				GL_Scissor(myscissor[0], myscissor[1], myscissor[2], myscissor[3]);
-			R_RenderScene(fbo, depthtexture, colortexture);
+			R_RenderScene(p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection);
 
-			R_Mesh_CopyToTexture(p->texture_reflection, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
+			if (!p->fbo_reflection)
+				R_Mesh_CopyToTexture(p->texture_reflection, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
 			r_fb.water.hideplayer = false;
 		}
 
@@ -5838,7 +5865,7 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 			r_refdef.view = myview;
 			if(r_water_scissormode.integer)
 			{
-				R_SetupView(true, fbo, depthtexture, colortexture);
+				R_SetupView(true, p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction);
 				if(R_ScissorForBBox(p->mins, p->maxs, myscissor))
 					continue; // FIXME the plane then still may get rendered but with broken texture, but it sure won't be visible
 			}
@@ -5864,7 +5891,7 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 
 			PlaneClassify(&r_refdef.view.clipplane);
 
-			R_ResetViewRendering3D(fbo, depthtexture, colortexture);
+			R_ResetViewRendering3D(p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction);
 			R_ClearScreen(r_refdef.fogenabled);
 			if(r_water_scissormode.integer & 2)
 				R_View_UpdateWithScissor(myscissor);
@@ -5872,9 +5899,10 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 				R_View_Update();
 			if(r_water_scissormode.integer & 1)
 				GL_Scissor(myscissor[0], myscissor[1], myscissor[2], myscissor[3]);
-			R_RenderScene(fbo, depthtexture, colortexture);
+			R_RenderScene(p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction);
 
-			R_Mesh_CopyToTexture(p->texture_refraction, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
+			if (!p->fbo_refraction)
+				R_Mesh_CopyToTexture(p->texture_refraction, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
 			r_fb.water.hideplayer = false;
 		}
 		else if (p->materialflags & MATERIALFLAG_CAMERA)
@@ -5920,12 +5948,13 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 
 			r_fb.water.hideplayer = false;
 
-			R_ResetViewRendering3D(fbo, depthtexture, colortexture);
+			R_ResetViewRendering3D(p->fbo_camera, r_fb.water.depthtexture, p->texture_camera);
 			R_ClearScreen(r_refdef.fogenabled);
 			R_View_Update();
-			R_RenderScene(fbo, depthtexture, colortexture);
+			R_RenderScene(p->fbo_camera, r_fb.water.depthtexture, p->texture_camera);
 
-			R_Mesh_CopyToTexture(p->texture_camera, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
+			if (!p->fbo_camera)
+				R_Mesh_CopyToTexture(p->texture_camera, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
 			r_fb.water.hideplayer = false;
 		}
 
@@ -5934,7 +5963,8 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 	r_fb.water.renderingscene = false;
 	r_refdef.view = originalview;
 	R_ResetViewRendering3D(fbo, depthtexture, colortexture);
-	R_ClearScreen(r_refdef.fogenabled);
+	if (!r_fb.water.depthtexture)
+		R_ClearScreen(r_refdef.fogenabled);
 	R_View_Update();
 	goto finish;
 error:
@@ -5963,9 +5993,30 @@ finish:
 
 void R_Bloom_StartFrame(void)
 {
+	int i;
 	int bloomtexturewidth, bloomtextureheight, screentexturewidth, screentextureheight;
 	int viewwidth, viewheight;
-	textype_t textype;
+
+	r_fb.textype = TEXTYPE_COLORBUFFER;
+	switch (vid.renderpath)
+	{
+	case RENDERPATH_GL20:
+	case RENDERPATH_GLES2:
+		if (vid.support.ext_framebuffer_object)
+		{
+			if (r_viewfbo.integer == 2) r_fb.textype = TEXTYPE_COLORBUFFER16F;
+			if (r_viewfbo.integer == 3) r_fb.textype = TEXTYPE_COLORBUFFER32F;
+		}
+		break;
+	case RENDERPATH_GL11:
+	case RENDERPATH_GL13:
+	case RENDERPATH_GLES1:
+	case RENDERPATH_D3D9:
+	case RENDERPATH_D3D10:
+	case RENDERPATH_D3D11:
+	case RENDERPATH_SOFT:
+		break;
+	}
 
 	if (r_viewscale_fpsscaling.integer)
 	{
@@ -6025,93 +6076,93 @@ void R_Bloom_StartFrame(void)
 		for (bloomtextureheight  = 1;bloomtextureheight  < r_fb.bloomheight;bloomtextureheight  *= 2);
 	}
 
-	if ((r_hdr.integer || r_bloom.integer || (!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0))) && ((r_bloom_resolution.integer < 4 || r_bloom_blur.value < 1 || r_bloom_blur.value >= 512) || r_refdef.view.width > (int)vid.maxtexturesize_2d || r_refdef.view.height > (int)vid.maxtexturesize_2d))
+	if ((r_bloom.integer || (!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0))) && ((r_bloom_resolution.integer < 4 || r_bloom_blur.value < 1 || r_bloom_blur.value >= 512) || r_refdef.view.width > (int)vid.maxtexturesize_2d || r_refdef.view.height > (int)vid.maxtexturesize_2d))
 	{
-		Cvar_SetValueQuick(&r_hdr, 0);
 		Cvar_SetValueQuick(&r_bloom, 0);
 		Cvar_SetValueQuick(&r_motionblur, 0);
 		Cvar_SetValueQuick(&r_damageblur, 0);
 	}
 
-	if (!(r_glsl_postprocess.integer || (!R_Stereo_ColorMasking() && r_glsl_saturation.value != 1) || (v_glslgamma.integer && !vid_gammatables_trivial)) && !r_bloom.integer && !r_hdr.integer && (R_Stereo_Active() || (r_motionblur.value <= 0 && r_damageblur.value <= 0)) && r_viewfbo.integer < 1 && r_viewscale.value == 1.0f && !r_viewscale_fpsscaling.integer)
+	if (!(r_glsl_postprocess.integer || (!R_Stereo_ColorMasking() && r_glsl_saturation.value != 1) || (v_glslgamma.integer && !vid_gammatables_trivial)) && !r_bloom.integer && (R_Stereo_Active() || (r_motionblur.value <= 0 && r_damageblur.value <= 0)) && r_viewfbo.integer < 1 && r_viewscale.value == 1.0f && !r_viewscale_fpsscaling.integer)
 		screentexturewidth = screentextureheight = 0;
-	if (!r_hdr.integer && !r_bloom.integer)
+	if (!r_bloom.integer)
 		bloomtexturewidth = bloomtextureheight = 0;
 
-	textype = TEXTYPE_COLORBUFFER;
-	switch (vid.renderpath)
-	{
-	case RENDERPATH_GL20:
-	case RENDERPATH_GLES2:
-		if (vid.support.ext_framebuffer_object)
-		{
-			if (r_viewfbo.integer == 2) textype = TEXTYPE_COLORBUFFER16F;
-			if (r_viewfbo.integer == 3) textype = TEXTYPE_COLORBUFFER32F;
-		}
-		break;
-	case RENDERPATH_GL11:
-	case RENDERPATH_GL13:
-	case RENDERPATH_GLES1:
-	case RENDERPATH_D3D9:
-	case RENDERPATH_D3D10:
-	case RENDERPATH_D3D11:
-	case RENDERPATH_SOFT:
-		break;
-	}
-
 	// allocate textures as needed
 	if (r_fb.screentexturewidth != screentexturewidth
 	 || r_fb.screentextureheight != screentextureheight
 	 || r_fb.bloomtexturewidth != bloomtexturewidth
 	 || r_fb.bloomtextureheight != bloomtextureheight
-	 || r_fb.texturetype != textype
 	 || r_fb.viewfbo != r_viewfbo.integer)
 	{
-		if (r_fb.texture_bloom)
-			R_FreeTexture(r_fb.texture_bloom);
-		r_fb.texture_bloom = NULL;
-		if (r_fb.texture_screen)
-			R_FreeTexture(r_fb.texture_screen);
-		r_fb.texture_screen = NULL;
-		if (r_fb.fbo_framebuffer)
-			R_Mesh_DestroyFramebufferObject(r_fb.fbo_framebuffer);
-		r_fb.fbo_framebuffer = 0;
-		if (r_fb.texture_framebuffercolor)
-			R_FreeTexture(r_fb.texture_framebuffercolor);
-		r_fb.texture_framebuffercolor = NULL;
-		if (r_fb.texture_framebufferdepth)
-			R_FreeTexture(r_fb.texture_framebufferdepth);
-		r_fb.texture_framebufferdepth = NULL;
+		for (i = 0;i < (int)(sizeof(r_fb.bloomtexture)/sizeof(r_fb.bloomtexture[i]));i++)
+		{
+			if (r_fb.bloomtexture[i])
+				R_FreeTexture(r_fb.bloomtexture[i]);
+			r_fb.bloomtexture[i] = NULL;
+
+			if (r_fb.bloomfbo[i])
+				R_Mesh_DestroyFramebufferObject(r_fb.bloomfbo[i]);
+			r_fb.bloomfbo[i] = 0;
+		}
+
+		if (r_fb.fbo)
+			R_Mesh_DestroyFramebufferObject(r_fb.fbo);
+		r_fb.fbo = 0;
+
+		if (r_fb.colortexture)
+			R_FreeTexture(r_fb.colortexture);
+		r_fb.colortexture = NULL;
+
+		if (r_fb.depthtexture)
+			R_FreeTexture(r_fb.depthtexture);
+		r_fb.depthtexture = NULL;
+
+		if (r_fb.ghosttexture)
+			R_FreeTexture(r_fb.ghosttexture);
+		r_fb.ghosttexture = NULL;
+
 		r_fb.screentexturewidth = screentexturewidth;
 		r_fb.screentextureheight = screentextureheight;
+		r_fb.bloomtexturewidth = bloomtexturewidth;
+		r_fb.bloomtextureheight = bloomtextureheight;
+		r_fb.viewfbo = r_viewfbo.integer;
+
 		if (r_fb.screentexturewidth && r_fb.screentextureheight)
-			r_fb.texture_screen = R_LoadTexture2D(r_main_texturepool, "screen", r_fb.screentexturewidth, r_fb.screentextureheight, NULL, textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
-		if (r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object)
-		{
-			// FIXME: choose depth bits based on a cvar
-			r_fb.texture_framebufferdepth = R_LoadTextureShadowMap2D(r_main_texturepool, "framebufferdepth", r_fb.screentexturewidth, r_fb.screentextureheight, 24, false);
-			r_fb.texture_framebuffercolor = R_LoadTexture2D(r_main_texturepool, "framebuffercolor", r_fb.screentexturewidth, r_fb.screentextureheight, NULL, textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
-			r_fb.fbo_framebuffer = R_Mesh_CreateFramebufferObject(r_fb.texture_framebufferdepth, r_fb.texture_framebuffercolor, NULL, NULL, NULL);
-			R_Mesh_SetRenderTargets(r_fb.fbo_framebuffer, r_fb.texture_framebufferdepth, r_fb.texture_framebuffercolor, NULL, NULL, NULL);
-#ifndef USE_GLES2
-			// render depth into one texture and normalmap into the other
-			if (qglDrawBuffer)
+		{
+			if (r_motionblur.value > 0 || r_damageblur.value > 0)
+				r_fb.ghosttexture = R_LoadTexture2D(r_main_texturepool, "framebuffermotionblur", r_fb.screentexturewidth, r_fb.screentextureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
+			r_fb.colortexture = R_LoadTexture2D(r_main_texturepool, "framebuffercolor", r_fb.screentexturewidth, r_fb.screentextureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
+			if (r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object)
 			{
-				int status;
-				qglDrawBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR
-				qglReadBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR
-				status = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER);CHECKGLERROR
-				if (status != GL_FRAMEBUFFER_COMPLETE)
-					Con_Printf("R_Bloom_StartFrame: glCheckFramebufferStatusEXT returned %i\n", status);
-			}
+				// FIXME: choose depth bits based on a cvar
+				r_fb.depthtexture = R_LoadTextureShadowMap2D(r_main_texturepool, "framebufferdepth", r_fb.screentexturewidth, r_fb.screentextureheight, 24, false);
+				r_fb.fbo = R_Mesh_CreateFramebufferObject(r_fb.depthtexture, r_fb.colortexture, NULL, NULL, NULL);
+				R_Mesh_SetRenderTargets(r_fb.fbo, r_fb.depthtexture, r_fb.colortexture, NULL, NULL, NULL);
+#ifndef USE_GLES2
+				// render depth into one texture and color into the other
+				if (qglDrawBuffer)
+				{
+					int status;
+					qglDrawBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR
+					qglReadBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR
+					status = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER);CHECKGLERROR
+					if (status != GL_FRAMEBUFFER_COMPLETE)
+						Con_Printf("R_Bloom_StartFrame: glCheckFramebufferStatusEXT returned %i\n", status);
+				}
 #endif
+			}
 		}
-		r_fb.bloomtexturewidth = bloomtexturewidth;
-		r_fb.bloomtextureheight = bloomtextureheight;
+
 		if (r_fb.bloomtexturewidth && r_fb.bloomtextureheight)
-			r_fb.texture_bloom = R_LoadTexture2D(r_main_texturepool, "bloom", r_fb.bloomtexturewidth, r_fb.bloomtextureheight, NULL, textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
-		r_fb.viewfbo = r_viewfbo.integer;
-		r_fb.texturetype = textype;
+		{
+			for (i = 0;i < (int)(sizeof(r_fb.bloomtexture)/sizeof(r_fb.bloomtexture[i]));i++)
+			{
+				r_fb.bloomtexture[i] = R_LoadTexture2D(r_main_texturepool, "framebufferbloom", r_fb.bloomtexturewidth, r_fb.bloomtextureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
+				if (r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object)
+					r_fb.bloomfbo[i] = R_Mesh_CreateFramebufferObject(NULL, r_fb.bloomtexture[i], NULL, NULL, NULL);
+			}
+		}
 	}
 
 	// bloom texture is a different resolution
@@ -6168,26 +6219,32 @@ void R_Bloom_StartFrame(void)
 		break;
 	}
 
-	if ((r_hdr.integer || r_bloom.integer) && r_fb.bloomwidth)
-	{
-		r_fb.enabled = true;
-		r_fb.hdr = r_hdr.integer != 0 && !r_fb.fbo_framebuffer;
-	}
-
-	R_Viewport_InitOrtho(&r_fb.viewport, &identitymatrix, r_refdef.view.x, vid.height - r_fb.bloomheight - r_refdef.view.y, r_fb.bloomwidth, r_fb.bloomheight, 0, 0, 1, 1, -10, 100, NULL);
+	R_Viewport_InitOrtho(&r_fb.bloomviewport, &identitymatrix, r_refdef.view.x, (r_fb.bloomfbo[0] ? r_fb.bloomtextureheight : vid.height) - r_fb.bloomheight - r_refdef.view.y, r_fb.bloomwidth, r_fb.bloomheight, 0, 0, 1, 1, -10, 100, NULL);
 
-	if (r_fb.fbo_framebuffer)
+	if (r_fb.fbo)
 		r_refdef.view.clear = true;
 }
 
-void R_Bloom_CopyBloomTexture(float colorscale)
+void R_Bloom_MakeTexture(void)
 {
+	int x, range, dir;
+	float xoffset, yoffset, r, brighten;
+	rtexture_t *intex;
+	float colorscale = r_bloom_colorscale.value;
+
 	r_refdef.stats.bloom++;
 
+	if (!r_fb.fbo)
+	{
+		R_Mesh_CopyToTexture(r_fb.colortexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
+		r_refdef.stats.bloom_copypixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
+	}
+
 	// scale down screen texture to the bloom texture size
 	CHECKGLERROR
-	R_Mesh_SetRenderTargets(r_fb.fbo_framebuffer, r_fb.texture_framebufferdepth, r_fb.texture_framebuffercolor, NULL, NULL, NULL);
-	R_SetViewport(&r_fb.viewport);
+	r_fb.bloomindex = 0;
+	R_Mesh_SetRenderTargets(r_fb.bloomfbo[r_fb.bloomindex], NULL, r_fb.bloomtexture[r_fb.bloomindex], NULL, NULL, NULL);
+	R_SetViewport(&r_fb.bloomviewport);
 	GL_BlendFunc(GL_ONE, GL_ZERO);
 	GL_Color(colorscale, colorscale, colorscale, 1);
 	// D3D has upside down Y coords, the easiest way to flip this is to flip the screen vertices rather than the texcoords, so we just use a different array for that...
@@ -6208,66 +6265,57 @@ void R_Bloom_CopyBloomTexture(float colorscale)
 		break;
 	}
 	// TODO: do boxfilter scale-down in shader?
-	R_SetupShader_Generic(r_fb.texture_screen, NULL, GL_MODULATE, 1, false, true);
+	R_SetupShader_Generic(r_fb.colortexture, NULL, GL_MODULATE, 1, false, true);
 	R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
 	r_refdef.stats.bloom_drawpixels += r_fb.bloomwidth * r_fb.bloomheight;
 
-	// we now have a bloom image in the framebuffer
-	// copy it into the bloom image texture for later processing
-	R_Mesh_CopyToTexture(r_fb.texture_bloom, 0, 0, r_fb.viewport.x, r_fb.viewport.y, r_fb.viewport.width, r_fb.viewport.height);
-	r_refdef.stats.bloom_copypixels += r_fb.viewport.width * r_fb.viewport.height;
-}
-
-void R_Bloom_CopyHDRTexture(void)
-{
-	R_Mesh_CopyToTexture(r_fb.texture_bloom, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
-	r_refdef.stats.bloom_copypixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
-}
-
-void R_Bloom_MakeTexture(void)
-{
-	int x, range, dir;
-	float xoffset, yoffset, r, brighten;
-
-	r_refdef.stats.bloom++;
-
-	R_ResetViewRendering2D(r_fb.fbo_framebuffer, r_fb.texture_framebufferdepth, r_fb.texture_framebuffercolor);
-
-	// we have a bloom image in the framebuffer
-	CHECKGLERROR
-	R_SetViewport(&r_fb.viewport);
+	// we now have a properly scaled bloom image
+	if (!r_fb.bloomfbo[r_fb.bloomindex])
+	{
+		// copy it into the bloom texture
+		R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height);
+		r_refdef.stats.bloom_copypixels += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
+	}
 
+	// multiply bloom image by itself as many times as desired
 	for (x = 1;x < min(r_bloom_colorexponent.value, 32);)
 	{
+		intex = r_fb.bloomtexture[r_fb.bloomindex];
+		r_fb.bloomindex ^= 1;
+		R_Mesh_SetRenderTargets(r_fb.bloomfbo[r_fb.bloomindex], NULL, r_fb.bloomtexture[r_fb.bloomindex], NULL, NULL, NULL);
 		x *= 2;
 		r = bound(0, r_bloom_colorexponent.value / x, 1);
 		GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
 		GL_Color(r,r,r,1);
 		R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.bloomtexcoord2f);
-		R_SetupShader_Generic(r_fb.texture_bloom, NULL, GL_MODULATE, 1, false, true);
+		R_SetupShader_Generic(intex, NULL, GL_MODULATE, 1, false, true);
 		R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
 		r_refdef.stats.bloom_drawpixels += r_fb.bloomwidth * r_fb.bloomheight;
 
-		// copy the vertically blurred bloom view to a texture
-		R_Mesh_CopyToTexture(r_fb.texture_bloom, 0, 0, r_fb.viewport.x, r_fb.viewport.y, r_fb.viewport.width, r_fb.viewport.height);
-		r_refdef.stats.bloom_copypixels += r_fb.viewport.width * r_fb.viewport.height;
+		if (!r_fb.bloomfbo[r_fb.bloomindex])
+		{
+			// copy the darkened image to a texture
+			R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height);
+			r_refdef.stats.bloom_copypixels += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
+		}
 	}
 
 	range = r_bloom_blur.integer * r_fb.bloomwidth / 320;
 	brighten = r_bloom_brighten.value;
-	if (r_fb.hdr)
-		brighten *= r_hdr_range.value;
 	brighten = sqrt(brighten);
 	if(range >= 1)
 		brighten *= (3 * range) / (2 * range - 1); // compensate for the "dot particle"
-	R_SetupShader_Generic(r_fb.texture_bloom, NULL, GL_MODULATE, 1, false, true);
 
 	for (dir = 0;dir < 2;dir++)
 	{
+		intex = r_fb.bloomtexture[r_fb.bloomindex];
+		r_fb.bloomindex ^= 1;
+		R_Mesh_SetRenderTargets(r_fb.bloomfbo[r_fb.bloomindex], NULL, r_fb.bloomtexture[r_fb.bloomindex], NULL, NULL, NULL);
 		// blend on at multiple vertical offsets to achieve a vertical blur
 		// TODO: do offset blends using GLSL
 		// TODO instead of changing the texcoords, change the target positions to prevent artifacts at edges
 		GL_BlendFunc(GL_ONE, GL_ZERO);
+		R_SetupShader_Generic(intex, NULL, GL_MODULATE, 1, false, true);
 		for (x = -range;x <= range;x++)
 		{
 			if (!dir){xoffset = 0;yoffset = x;}
@@ -6298,72 +6346,15 @@ void R_Bloom_MakeTexture(void)
 			GL_BlendFunc(GL_ONE, GL_ONE);
 		}
 
-		// copy the vertically blurred bloom view to a texture
-		R_Mesh_CopyToTexture(r_fb.texture_bloom, 0, 0, r_fb.viewport.x, r_fb.viewport.y, r_fb.viewport.width, r_fb.viewport.height);
-		r_refdef.stats.bloom_copypixels += r_fb.viewport.width * r_fb.viewport.height;
+		if (!r_fb.bloomfbo[r_fb.bloomindex])
+		{
+			// copy the vertically or horizontally blurred bloom view to a texture
+			R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height);
+			r_refdef.stats.bloom_copypixels += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
+		}
 	}
 }
 
-void R_HDR_RenderBloomTexture(void)
-{
-	int oldwidth, oldheight;
-	float oldcolorscale;
-	qboolean oldwaterstate;
-
-	oldwaterstate = r_fb.water.enabled;
-	oldcolorscale = r_refdef.view.colorscale;
-	oldwidth = r_refdef.view.width;
-	oldheight = r_refdef.view.height;
-	r_refdef.view.width = r_fb.bloomwidth;
-	r_refdef.view.height = r_fb.bloomheight;
-
-	if(r_hdr.integer < 2)
-		r_fb.water.enabled = false;
-
-	// TODO: support GL_EXT_framebuffer_object rather than reusing the framebuffer?  it might improve SLI performance.
-	// TODO: add exposure compensation features
-	// TODO: add fp16 framebuffer support (using GL_EXT_framebuffer_object)
-
-	r_refdef.view.showdebug = false;
-	r_refdef.view.colorscale *= r_bloom_colorscale.value / bound(1, r_hdr_range.value, 16);
-
-	R_ResetViewRendering3D(r_fb.fbo_framebuffer, r_fb.texture_framebufferdepth, r_fb.texture_framebuffercolor);
-
-	R_ClearScreen(r_refdef.fogenabled);
-	if (r_timereport_active)
-		R_TimeReport("HDRclear");
-
-	R_View_Update();
-	if (r_timereport_active)
-		R_TimeReport("visibility");
-
-	// only do secondary renders with HDR if r_hdr is 2 or higher
-	r_fb.water.numwaterplanes = 0;
-	if (r_fb.water.enabled)
-		R_RenderWaterPlanes(r_fb.fbo_framebuffer, r_fb.texture_framebufferdepth, r_fb.texture_framebuffercolor);
-
-	r_refdef.view.showdebug = true;
-	R_RenderScene(r_fb.fbo_framebuffer, r_fb.texture_framebufferdepth, r_fb.texture_framebuffercolor);
-	r_fb.water.numwaterplanes = 0;
-
-	R_ResetViewRendering2D(r_fb.fbo_framebuffer, r_fb.texture_framebufferdepth, r_fb.texture_framebuffercolor);
-
-	R_Bloom_CopyHDRTexture();
-	R_Bloom_MakeTexture();
-
-	// restore the view settings
-	r_fb.water.enabled = oldwaterstate;
-	r_refdef.view.width = oldwidth;
-	r_refdef.view.height = oldheight;
-	r_refdef.view.colorscale = oldcolorscale;
-
-	R_ResetViewRendering3D(r_fb.fbo_framebuffer, r_fb.texture_framebufferdepth, r_fb.texture_framebuffercolor);
-
-	R_ClearScreen(r_refdef.fogenabled);
-	if (r_timereport_active)
-		R_TimeReport("viewclear");
-}
-
 static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture)
 {
 	unsigned int permutation;
@@ -6378,18 +6369,19 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
 	case RENDERPATH_SOFT:
 	case RENDERPATH_GLES2:
 		permutation =
-			  (r_fb.texture_bloom ? SHADERPERMUTATION_BLOOM : 0)
+			  (r_fb.bloomtexture[r_fb.bloomindex] ? SHADERPERMUTATION_BLOOM : 0)
 			| (r_refdef.viewblend[3] > 0 ? SHADERPERMUTATION_VIEWTINT : 0)
 			| ((v_glslgamma.value && !vid_gammatables_trivial) ? SHADERPERMUTATION_GAMMARAMPS : 0)
 			| (r_glsl_postprocess.integer ? SHADERPERMUTATION_POSTPROCESSING : 0)
 			| ((!R_Stereo_ColorMasking() && r_glsl_saturation.value != 1) ? SHADERPERMUTATION_SATURATION : 0);
 
-		if (r_fb.texture_screen)
+		if (r_fb.colortexture)
 		{
-			// make sure the buffer is available
-			if (r_bloom_blur.value < 1) { Cvar_SetValueQuick(&r_bloom_blur, 1); }
-
-			R_ResetViewRendering2D(fbo, depthtexture, colortexture);
+			if (!r_fb.fbo)
+			{
+				R_Mesh_CopyToTexture(r_fb.colortexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
+				r_refdef.stats.bloom_copypixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
+			}
 
 			if(!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0))
 			{
@@ -6409,10 +6401,10 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
 				// from the goal, pick an averaged value between goal and last value
 				cl.motionbluralpha = bound(0, (cl.time - cl.oldtime) / max(0.001, r_motionblur_averaging.value), 1);
 				blur_average = blur_average * (1 - cl.motionbluralpha) + blur_factor * cl.motionbluralpha;
-				
+
 				// enforce minimum amount of blur 
 				blur_factor = blur_average * (1 - r_motionblur_minblur.value) + r_motionblur_minblur.value;
-				
+
 				//Con_Printf("motionblur: direct factor: %f, averaged factor: %f, velocity: %f, mouse accel: %f \n", blur_factor, blur_average, blur_velocity, blur_mouseaccel);
 
 				// calculate values into a standard alpha
@@ -6425,13 +6417,14 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
 						/
 						max(0.0001, cl.time - cl.oldtime) // fps independent
 					  );
-				
+
 				// randomization for the blur value to combat persistent ghosting
 				cl.motionbluralpha *= lhrandom(1 - r_motionblur_randomize.value, 1 + r_motionblur_randomize.value);
 				cl.motionbluralpha = bound(0, cl.motionbluralpha, r_motionblur_maxblur.value);
-				
+
 				// apply the blur
-				if (cl.motionbluralpha > 0 && !r_refdef.envmap)
+				R_ResetViewRendering2D(fbo, depthtexture, colortexture);
+				if (cl.motionbluralpha > 0 && !r_refdef.envmap && r_fb.ghosttexture)
 				{
 					GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 					GL_Color(1, 1, 1, cl.motionbluralpha);
@@ -6451,21 +6444,24 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
 						R_Mesh_PrepareVertices_Generic_Arrays(4, r_d3dscreenvertex3f, NULL, r_fb.screentexcoord2f);
 						break;
 					}
-					R_SetupShader_Generic(r_fb.texture_screen, NULL, GL_MODULATE, 1, false, true);
+					R_SetupShader_Generic(r_fb.ghosttexture, NULL, GL_MODULATE, 1, false, true);
 					R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
 					r_refdef.stats.bloom_drawpixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
 				}
-				
-				// updates old view angles for next pass 
+
+				// updates old view angles for next pass
 				VectorCopy(cl.viewangles, blur_oldangles);
-			}
 
-			// copy view into the screen texture
-			R_Mesh_CopyToTexture(r_fb.texture_screen, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
-			r_refdef.stats.bloom_copypixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
+				// copy view into the ghost texture
+				if (!r_fb.ghosttexture)
+					r_fb.ghosttexture = R_LoadTexture2D(r_main_texturepool, "motionblurtexture", r_fb.screentexturewidth, r_fb.screentextureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCENEAREST | TEXF_CLAMP, -1, NULL);
+				R_Mesh_CopyToTexture(r_fb.ghosttexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
+				r_refdef.stats.bloom_copypixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
+			}
 		}
 		else
 		{
+			// no r_fb.colortexture means we're rendering to the real fb
 			// we may still have to do view tint...
 			if (r_refdef.viewblend[3] >= (1.0f / 256.0f))
 			{
@@ -6480,11 +6476,8 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
 			break; // no screen processing, no bloom, skip it
 		}
 
-		if (r_fb.texture_bloom && !r_fb.hdr)
+		if (r_fb.bloomtexture[0])
 		{
-			// render simple bloom effect
-			// copy the screen and shrink it and darken it for the bloom process
-			R_Bloom_CopyBloomTexture(r_bloom_colorscale.value);
 			// make the bloom texture
 			R_Bloom_MakeTexture();
 		}
@@ -6502,7 +6495,7 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
 		if (r_glsl_postprocess_uservec4_enable.integer)
 			sscanf(r_glsl_postprocess_uservec4.string, "%f %f %f %f", &uservecs[3][0], &uservecs[3][1], &uservecs[3][2], &uservecs[3][3]);
 
-		R_ResetViewRendering2D(0, NULL, NULL);
+		R_ResetViewRendering2D(0, NULL, NULL); // here we render to the real framebuffer!
 		GL_Color(1, 1, 1, 1);
 		GL_BlendFunc(GL_ONE, GL_ZERO);
 
@@ -6512,8 +6505,8 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
 		case RENDERPATH_GLES2:
 			R_Mesh_PrepareVertices_Mesh_Arrays(4, r_screenvertex3f, NULL, NULL, NULL, NULL, r_fb.screentexcoord2f, r_fb.bloomtexcoord2f);
 			R_SetupShader_SetPermutationGLSL(SHADERMODE_POSTPROCESS, permutation);
-			if (r_glsl_permutation->tex_Texture_First           >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First     , r_fb.texture_screen);
-			if (r_glsl_permutation->tex_Texture_Second          >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second    , r_fb.texture_bloom );
+			if (r_glsl_permutation->tex_Texture_First           >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First     , r_fb.colortexture);
+			if (r_glsl_permutation->tex_Texture_Second          >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second    , r_fb.bloomtexture[r_fb.bloomindex]);
 			if (r_glsl_permutation->tex_Texture_GammaRamps      >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps, r_texture_gammaramps       );
 			if (r_glsl_permutation->loc_ViewTintColor           >= 0) qglUniform4f(r_glsl_permutation->loc_ViewTintColor     , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]);
 			if (r_glsl_permutation->loc_PixelSize               >= 0) qglUniform2f(r_glsl_permutation->loc_PixelSize         , 1.0/r_fb.screentexturewidth, 1.0/r_fb.screentextureheight);
@@ -6530,8 +6523,8 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
 			// D3D has upside down Y coords, the easiest way to flip this is to flip the screen vertices rather than the texcoords, so we just use a different array for that...
 			R_Mesh_PrepareVertices_Mesh_Arrays(4, r_d3dscreenvertex3f, NULL, NULL, NULL, NULL, r_fb.screentexcoord2f, r_fb.bloomtexcoord2f);
 			R_SetupShader_SetPermutationHLSL(SHADERMODE_POSTPROCESS, permutation);
-			R_Mesh_TexBind(GL20TU_FIRST     , r_fb.texture_screen);
-			R_Mesh_TexBind(GL20TU_SECOND    , r_fb.texture_bloom );
+			R_Mesh_TexBind(GL20TU_FIRST     , r_fb.colortexture);
+			R_Mesh_TexBind(GL20TU_SECOND    , r_fb.bloomtexture);
 			R_Mesh_TexBind(GL20TU_GAMMARAMPS, r_texture_gammaramps       );
 			hlslPSSetParameter4f(D3DPSREGISTER_ViewTintColor        , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]);
 			hlslPSSetParameter2f(D3DPSREGISTER_PixelSize            , 1.0/r_fb.screentexturewidth, 1.0/r_fb.screentextureheight);
@@ -6553,8 +6546,8 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
 		case RENDERPATH_SOFT:
 			R_Mesh_PrepareVertices_Mesh_Arrays(4, r_screenvertex3f, NULL, NULL, NULL, NULL, r_fb.screentexcoord2f, r_fb.bloomtexcoord2f);
 			R_SetupShader_SetPermutationSoft(SHADERMODE_POSTPROCESS, permutation);
-			R_Mesh_TexBind(GL20TU_FIRST     , r_fb.texture_screen);
-			R_Mesh_TexBind(GL20TU_SECOND    , r_fb.texture_bloom );
+			R_Mesh_TexBind(GL20TU_FIRST     , r_fb.colortexture);
+			R_Mesh_TexBind(GL20TU_SECOND    , r_fb.bloomtexture[r_fb.bloomindex]);
 			R_Mesh_TexBind(GL20TU_GAMMARAMPS, r_texture_gammaramps       );
 			DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ViewTintColor     , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]);
 			DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelSize         , 1.0/r_fb.screentexturewidth, 1.0/r_fb.screentextureheight);
@@ -6903,9 +6896,9 @@ void R_RenderView(void)
 	R_Water_StartFrame();
 
 	// now we probably have an fbo to render into
-	fbo = r_fb.fbo_framebuffer;
-	depthtexture = r_fb.texture_framebufferdepth;
-	colortexture = r_fb.texture_framebuffercolor;
+	fbo = r_fb.fbo;
+	depthtexture = r_fb.depthtexture;
+	colortexture = r_fb.colortexture;
 
 	CHECKGLERROR
 	if (r_timereport_active)
@@ -6921,14 +6914,6 @@ void R_RenderView(void)
 	}
 	r_refdef.view.clear = true;
 
-	// this produces a bloom texture to be used in R_BlendView() later
-	if (r_fb.hdr)
-	{
-		R_HDR_RenderBloomTexture();
-		// we have to bump the texture frame again because r_refdef.view.colorscale is cached in the textures
-		r_textureframe++; // used only by R_GetCurrentTexture
-	}
-
 	r_refdef.view.showdebug = true;
 
 	R_View_Update();
@@ -6990,8 +6975,8 @@ extern cvar_t cl_locs_show;
 static void R_DrawLocs(void);
 static void R_DrawEntityBBoxes(void);
 static void R_DrawModelDecals(void);
-extern void R_DrawModelShadows(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture);
-extern void R_DrawModelShadowMaps(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture);
+extern void R_DrawModelShadows(void);
+extern void R_DrawModelShadowMaps(void);
 extern cvar_t cl_decals_newsystem;
 extern qboolean r_shadow_usingdeferredprepass;
 void R_RenderScene(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture)
@@ -7073,7 +7058,7 @@ void R_RenderScene(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture)
 	if (r_shadows.integer >= 2 && shadowmapping && r_refdef.lightmapintensity > 0)
 	{
 		R_ResetViewRendering3D(fbo, depthtexture, colortexture);
-		R_DrawModelShadowMaps(fbo, depthtexture, colortexture);
+		R_DrawModelShadowMaps();
 		R_ResetViewRendering3D(fbo, depthtexture, colortexture);
 		// don't let sound skip if going slow
 		if (r_refdef.scene.extraupdate)
@@ -7102,7 +7087,7 @@ void R_RenderScene(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture)
 	if ((r_shadows.integer == 1 || (r_shadows.integer > 0 && !shadowmapping)) && !r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0)
 	{
 		R_ResetViewRendering3D(fbo, depthtexture, colortexture);
-		R_DrawModelShadows(fbo, depthtexture, colortexture);
+		R_DrawModelShadows();
 		R_ResetViewRendering3D(fbo, depthtexture, colortexture);
 		// don't let sound skip if going slow
 		if (r_refdef.scene.extraupdate)
@@ -7123,7 +7108,7 @@ void R_RenderScene(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture)
 	if ((r_shadows.integer == 1 || (r_shadows.integer > 0 && !shadowmapping)) && r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0)
 	{
 		R_ResetViewRendering3D(fbo, depthtexture, colortexture);
-		R_DrawModelShadows(fbo, depthtexture, colortexture);
+		R_DrawModelShadows();
 		R_ResetViewRendering3D(fbo, depthtexture, colortexture);
 		// don't let sound skip if going slow
 		if (r_refdef.scene.extraupdate)
@@ -7232,8 +7217,6 @@ void R_RenderScene(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture)
 	// don't let sound skip if going slow
 	if (r_refdef.scene.extraupdate)
 		S_ExtraUpdate ();
-
-	R_ResetViewRendering2D(fbo, depthtexture, colortexture);
 }
 
 static const unsigned short bboxelements[36] =
diff --git a/menu.c b/menu.c
index c09abda4..f61dc5e8 100644
--- a/menu.c
+++ b/menu.c
@@ -1978,10 +1978,8 @@ extern cvar_t r_bloom_colorexponent;
 extern cvar_t r_bloom_blur;
 extern cvar_t r_bloom_brighten;
 extern cvar_t r_bloom_resolution;
-extern cvar_t r_hdr;
 extern cvar_t r_hdr_scenebrightness;
 extern cvar_t r_hdr_glowintensity;
-extern cvar_t r_hdr_range;
 extern cvar_t gl_picmip;
 
 static void M_Menu_Options_Graphics_AdjustSliders (int dir)
@@ -1999,10 +1997,8 @@ static void M_Menu_Options_Graphics_AdjustSliders (int dir)
 	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world,					!r_shadow_realtime_world.integer);
 	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world_lightmaps,		bound(0, r_shadow_realtime_world_lightmaps.value + dir * 0.1, 1));
 	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world_shadows,			!r_shadow_realtime_world_shadows.integer);
-	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_scenebrightness,                   bound(0.25, r_hdr_scenebrightness.value + dir * 0.125, 4));
 	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom,                                 !r_bloom.integer);
-	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr,                                   !r_hdr.integer);
-	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_range,                             bound(1, r_hdr_range.value + dir * 0.25, 16));
+	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_scenebrightness,                   bound(0.25, r_hdr_scenebrightness.value + dir * 0.125, 4));
 	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_glowintensity,                     bound(0, r_hdr_glowintensity.value + dir * 0.25, 4));
 	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorscale,                      bound(0.0625, r_bloom_colorscale.value + dir * 0.0625, 1));
 	else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorsubtract,                   bound(0, r_bloom_colorsubtract.value + dir * 0.0625, 1-0.0625));
@@ -2038,17 +2034,15 @@ static void M_Options_Graphics_Draw (void)
 	M_Options_PrintCheckbox("              RT World", true, r_shadow_realtime_world.integer);
 	M_Options_PrintSlider(  "    RT World Lightmaps", true, r_shadow_realtime_world_lightmaps.value, 0, 1);
 	M_Options_PrintCheckbox("       RT World Shadow", true, r_shadow_realtime_world_shadows.integer);
+	M_Options_PrintCheckbox("          Bloom Effect", true, r_bloom.integer);
 	M_Options_PrintSlider(  "      Scene Brightness", true, r_hdr_scenebrightness.value, 0.25, 4);
-	M_Options_PrintCheckbox("          Bloom Effect", !r_hdr.integer, r_bloom.integer);
-	M_Options_PrintCheckbox("      HDR Bloom Effect", true, r_hdr.integer);
-	M_Options_PrintSlider(  "     HDR Dynamic Range", r_hdr.integer, r_hdr_range.value, 1, 16);
-	M_Options_PrintSlider(  "    HDR Glow Intensity", r_hdr.integer, r_hdr_glowintensity.value, 0, 4);
-	M_Options_PrintSlider(  "     Bloom Color Scale", r_hdr.integer || r_bloom.integer, r_bloom_colorscale.value, 0.0625, 1);
-	M_Options_PrintSlider(  "  Bloom Color Subtract", r_hdr.integer || r_bloom.integer, r_bloom_colorsubtract.value, 0, 1-0.0625);
-	M_Options_PrintSlider(  "  Bloom Color Exponent", r_hdr.integer || r_bloom.integer, r_bloom_colorexponent.value, 1, 8);
-	M_Options_PrintSlider(  "       Bloom Intensity", r_hdr.integer || r_bloom.integer, r_bloom_brighten.value, 1, 4);
-	M_Options_PrintSlider(  "            Bloom Blur", r_hdr.integer || r_bloom.integer, r_bloom_blur.value, 1, 16);
-	M_Options_PrintSlider(  "      Bloom Resolution", r_hdr.integer || r_bloom.integer, r_bloom_resolution.value, 64, 2048);
+	M_Options_PrintSlider(  "       Glow Brightness", true, r_hdr_glowintensity.value, 0, 4);
+	M_Options_PrintSlider(  "     Bloom Color Scale", r_bloom.integer, r_bloom_colorscale.value, 0.0625, 1);
+	M_Options_PrintSlider(  "  Bloom Color Subtract", r_bloom.integer, r_bloom_colorsubtract.value, 0, 1-0.0625);
+	M_Options_PrintSlider(  "  Bloom Color Exponent", r_bloom.integer, r_bloom_colorexponent.value, 1, 8);
+	M_Options_PrintSlider(  "       Bloom Intensity", r_bloom.integer, r_bloom_brighten.value, 1, 4);
+	M_Options_PrintSlider(  "            Bloom Blur", r_bloom.integer, r_bloom_blur.value, 1, 16);
+	M_Options_PrintSlider(  "      Bloom Resolution", r_bloom.integer, r_bloom_resolution.value, 64, 2048);
 	M_Options_PrintCommand( "      Restart Renderer", true);
 }
 
diff --git a/r_shadow.c b/r_shadow.c
index 26cc9531..c5d14ec2 100644
--- a/r_shadow.c
+++ b/r_shadow.c
@@ -2143,7 +2143,7 @@ void R_Shadow_RenderMode_ShadowMap(int side, int clear, int size)
 	float nearclip, farclip, bias;
 	r_viewport_t viewport;
 	int flipped;
-	GLuint fbo = 0;
+	GLuint fbo2d = 0;
 	float clearcolor[4];
 	nearclip = r_shadow_shadowmapping_nearclip.value / rsurface.rtlight->radius;
 	farclip = 1.0f;
@@ -2163,14 +2163,14 @@ void R_Shadow_RenderMode_ShadowMap(int side, int clear, int size)
 		R_Shadow_MakeVSDCT();
 	if (!r_shadow_shadowmap2dtexture)
 		R_Shadow_MakeShadowMap(side, r_shadow_shadowmapmaxsize);
-	if (r_shadow_shadowmap2dtexture) fbo = r_shadow_fbo2d;
+	if (r_shadow_shadowmap2dtexture) fbo2d = r_shadow_fbo2d;
 	r_shadow_shadowmap_texturescale[0] = 1.0f / R_TextureWidth(r_shadow_shadowmap2dtexture);
 	r_shadow_shadowmap_texturescale[1] = 1.0f / R_TextureHeight(r_shadow_shadowmap2dtexture);
 	r_shadow_rendermode = R_SHADOW_RENDERMODE_SHADOWMAP2D;
 
 	R_Mesh_ResetTextureState();
 	R_Shadow_RenderMode_Reset();
-	R_Mesh_SetRenderTargets(fbo, r_shadow_shadowmap2dtexture, r_shadow_shadowmap2dcolortexture, NULL, NULL, NULL);
+	R_Mesh_SetRenderTargets(fbo2d, r_shadow_shadowmap2dtexture, r_shadow_shadowmap2dcolortexture, NULL, NULL, NULL);
 	R_SetupShader_DepthOrShadow(true);
 	GL_PolygonOffset(r_shadow_shadowmapping_polygonfactor.value, r_shadow_shadowmapping_polygonoffset.value);
 	GL_DepthMask(true);
@@ -2234,7 +2234,6 @@ init_done:
 void R_Shadow_RenderMode_Lighting(qboolean stenciltest, qboolean transparent, qboolean shadowmapping)
 {
 	R_Mesh_ResetTextureState();
-	R_Mesh_SetRenderTargets(r_shadow_fb_fbo, r_shadow_fb_depthtexture, r_shadow_fb_colortexture, NULL, NULL, NULL);
 	if (transparent)
 	{
 		r_shadow_lightscissor[0] = r_refdef.view.viewport.x;
@@ -4484,8 +4483,6 @@ void R_Shadow_DrawPrepass(void)
 			if (r_refdef.scene.lights[lnum]->draw)
 				R_Shadow_DrawLight(r_refdef.scene.lights[lnum]);
 
-	R_Mesh_SetRenderTargets(r_shadow_fb_fbo, r_shadow_fb_depthtexture, r_shadow_fb_colortexture, NULL, NULL, NULL);
-
 	R_Shadow_RenderMode_End();
 
 	if (r_timereport_active)
@@ -4545,14 +4542,14 @@ void R_Shadow_PrepareLights(int fbo, rtexture_t *depthtexture, rtexture_t *color
 			switch (vid.renderpath)
 			{
 			case RENDERPATH_D3D9:
-				r_shadow_prepassgeometrydepthcolortexture = R_LoadTexture2D(r_shadow_texturepool, "prepassgeometrydepthcolormap", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL);
+				r_shadow_prepassgeometrydepthcolortexture = R_LoadTexture2D(r_shadow_texturepool, "prepassgeometrydepthcolormap", vid.width, vid.height, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL);
 				break;
 			default:
 				break;
 			}
-			r_shadow_prepassgeometrynormalmaptexture = R_LoadTexture2D(r_shadow_texturepool, "prepassgeometrynormalmap", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL);
-			r_shadow_prepasslightingdiffusetexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingdiffuse", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL);
-			r_shadow_prepasslightingspeculartexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingspecular", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL);
+			r_shadow_prepassgeometrynormalmaptexture = R_LoadTexture2D(r_shadow_texturepool, "prepassgeometrynormalmap", vid.width, vid.height, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL);
+			r_shadow_prepasslightingdiffusetexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingdiffuse", vid.width, vid.height, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL);
+			r_shadow_prepasslightingspeculartexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingspecular", vid.width, vid.height, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL);
 
 			// set up the geometry pass fbo (depth + normalmap)
 			r_shadow_prepassgeometryfbo = R_Mesh_CreateFramebufferObject(r_shadow_prepassgeometrydepthtexture, r_shadow_prepassgeometrynormalmaptexture, NULL, NULL, NULL);
@@ -4807,7 +4804,7 @@ void R_DrawModelShadowMaps(int fbo, rtexture_t *depthtexture, rtexture_t *colort
 	case R_SHADOW_SHADOWMODE_SHADOWMAP2D:
 		if (!r_shadow_shadowmap2dtexture)
 			R_Shadow_MakeShadowMap(0, r_shadow_shadowmapmaxsize);
-		fbo2d = r_shadow_fbo2d;
+		fbo = r_shadow_fbo2d;
 		r_shadow_shadowmap_texturescale[0] = 1.0f / R_TextureWidth(r_shadow_shadowmap2dtexture);
 		r_shadow_shadowmap_texturescale[1] = 1.0f / R_TextureHeight(r_shadow_shadowmap2dtexture);
 		r_shadow_rendermode = R_SHADOW_RENDERMODE_SHADOWMAP2D;
diff --git a/render.h b/render.h
index a3fc44cc..890d838d 100644
--- a/render.h
+++ b/render.h
@@ -448,9 +448,12 @@ void R_SetupShader_DeferredLight(const rtlight_t *rtlight);
 
 typedef struct r_waterstate_waterplane_s
 {
-	rtexture_t *texture_refraction;
-	rtexture_t *texture_reflection;
-	rtexture_t *texture_camera;
+	rtexture_t *texture_refraction; // MATERIALFLAG_WATERSHADER or MATERIALFLAG_REFRACTION
+	rtexture_t *texture_reflection; // MATERIALFLAG_WATERSHADER or MATERIALFLAG_REFLECTION
+	rtexture_t *texture_camera; // MATERIALFLAG_CAMERA
+	int fbo_refraction;
+	int fbo_reflection;
+	int fbo_camera;
 	mplane_t plane;
 	int materialflags; // combined flags of all water surfaces on this plane
 	unsigned char pvsbits[(MAX_MAP_LEAFS+7)>>3]; // FIXME: buffer overflow on huge maps
@@ -470,6 +473,7 @@ typedef struct r_waterstate_s
 	int waterwidth, waterheight;
 	int texturewidth, textureheight;
 	int camerawidth, cameraheight;
+	rtexture_t *depthtexture;
 
 	int maxwaterplanes; // same as MAX_WATERPLANES
 	int numwaterplanes;
@@ -482,30 +486,28 @@ r_waterstate_t;
 
 typedef struct r_framebufferstate_s
 {
-	qboolean enabled;
-	qboolean hdr;
-
-	int bloomwidth, bloomheight;
+	int viewfbo; // copy of r_viewfbo cvar (to cause reallocation of textures if needed)
 
-	textype_t texturetype;
-	int viewfbo; // used to check if r_viewfbo cvar has changed
+	textype_t textype; // type of color buffer we're using (dependent on r_viewfbo cvar)
+	int fbo; // non-zero if r_viewfbo is enabled and working
+	int screentexturewidth, screentextureheight; // dimensions of texture
 
-	int fbo_framebuffer; // non-zero if r_viewfbo is enabled and working
-	rtexture_t *texture_framebuffercolor; // non-NULL if fbo_screen is non-zero
-	rtexture_t *texture_framebufferdepth; // non-NULL if fbo_screen is non-zero
-
-	int screentexturewidth, screentextureheight;
-	rtexture_t *texture_screen; /// \note also used for motion blur if enabled!
+	rtexture_t *colortexture; // non-NULL if fbo is non-zero
+	rtexture_t *depthtexture; // non-NULL if fbo is non-zero
+	rtexture_t *ghosttexture; // for r_motionblur (not recommended on multi-GPU hardware!)
+	rtexture_t *bloomtexture[2]; // for r_bloom, multi-stage processing
+	int bloomfbo[2]; // fbos for rendering into bloomtexture[]
+	int bloomindex; // which bloomtexture[] contains the final image
 
+	int bloomwidth, bloomheight;
 	int bloomtexturewidth, bloomtextureheight;
-	rtexture_t *texture_bloom;
 
 	// arrays for rendering the screen passes
-	float screentexcoord2f[8];
-	float bloomtexcoord2f[8];
-	float offsettexcoord2f[8];
+	float screentexcoord2f[8]; // texcoords for colortexture or ghosttexture
+	float bloomtexcoord2f[8]; // texcoords for bloomtexture[]
+	float offsettexcoord2f[8]; // temporary use while updating bloomtexture[]
 
-	r_viewport_t viewport;
+	r_viewport_t bloomviewport;
 
 	r_waterstate_t water;
 }
-- 
2.39.5