Made RENDERPATH_GL20 require fbo support and always use it.
authorhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Fri, 13 Apr 2018 05:20:44 +0000 (05:20 +0000)
committerhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Fri, 13 Apr 2018 05:20:44 +0000 (05:20 +0000)
Implemented R_RenderTarget system for managing fbo and texture requests dynamically, reusing the same ones across frames - this means that the textures are now allocated much smaller and there is better handling of multiple CSQC viewports.

In the short-term this means that vid_samples antialiasing doesn't do anything, it will need to be reimplemented using fbo.

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@12390 d7cf8633-e32d-0410-b094-e92efae38249

cl_screen.c
client.h
gl_rmain.c
r_textures.h
render.h
vid_shared.c

index 858673f..7fb41d1 100644 (file)
@@ -777,6 +777,8 @@ const char *r_stat_name[r_stat_count] =
        "bloom",
        "bloom_copypixels",
        "bloom_drawpixels",
+       "rendertargets_used",
+       "rendertargets_pixels",
        "indexbufferuploadcount",
        "indexbufferuploadsize",
        "vertexbufferuploadcount",
@@ -1004,6 +1006,7 @@ static void R_TimeReport_EndFrame(void)
 "bouncegrid:%4i lights%6i particles%6i traces%6i hits%6i splats%6i bounces\n"
 "photon cache efficiency:%6i cached%6i traced%6ianimated\n"
 "%6i draws%8i vertices%8i triangles bloompixels%8i copied%8i drawn\n"
+"%3i rendertargets%8i pixels\n"
 "updated%5i indexbuffers%8i bytes%5i vertexbuffers%8i bytes\n"
 "animcache%5ib gpuskeletal%7i vertices (%7i with normals)\n"
 "fastbatch%5i count%5i surfaces%7i vertices %7i triangles\n"
@@ -1020,6 +1023,7 @@ static void R_TimeReport_EndFrame(void)
 , r_refdef.stats[r_stat_bouncegrid_lights], r_refdef.stats[r_stat_bouncegrid_particles], r_refdef.stats[r_stat_bouncegrid_traces], r_refdef.stats[r_stat_bouncegrid_hits], r_refdef.stats[r_stat_bouncegrid_splats], r_refdef.stats[r_stat_bouncegrid_bounces]
 , r_refdef.stats[r_stat_photoncache_cached], r_refdef.stats[r_stat_photoncache_traced], r_refdef.stats[r_stat_photoncache_animated]
 , r_refdef.stats[r_stat_draws], r_refdef.stats[r_stat_draws_vertices], r_refdef.stats[r_stat_draws_elements] / 3, r_refdef.stats[r_stat_bloom_copypixels], r_refdef.stats[r_stat_bloom_drawpixels]
+, r_refdef.stats[r_stat_rendertargets_used], r_refdef.stats[r_stat_rendertargets_pixels]
 , r_refdef.stats[r_stat_indexbufferuploadcount], r_refdef.stats[r_stat_indexbufferuploadsize], r_refdef.stats[r_stat_vertexbufferuploadcount], r_refdef.stats[r_stat_vertexbufferuploadsize]
 , r_refdef.stats[r_stat_animcache_skeletal_bones], r_refdef.stats[r_stat_animcache_shape_vertices], r_refdef.stats[r_stat_animcache_shade_vertices]
 , r_refdef.stats[r_stat_batch_fast_batches], r_refdef.stats[r_stat_batch_fast_surfaces], r_refdef.stats[r_stat_batch_fast_vertices], r_refdef.stats[r_stat_batch_fast_triangles]
@@ -1807,6 +1811,7 @@ static void R_Envmap_f (void)
        char filename[MAX_QPATH], basename[MAX_QPATH];
        unsigned char *buffer1;
        unsigned char *buffer2;
+       r_rendertarget_t *rt;
 
        if (Cmd_Argc() != 3)
        {
@@ -1848,6 +1853,8 @@ static void R_Envmap_f (void)
        buffer1 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 4);
        buffer2 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3);
 
+       // TODO: use TEXTYPE_COLORBUFFER16F and output to .exr files as well?
+       rt = R_RenderTarget_Get(size, size, TEXTYPE_DEPTHBUFFER24STENCIL8, true, TEXTYPE_COLORBUFFER, TEXTYPE_UNUSED, TEXTYPE_UNUSED, TEXTYPE_UNUSED);
        CL_UpdateEntityShading();
        for (j = 0;j < 12;j++)
        {
@@ -1856,7 +1863,7 @@ static void R_Envmap_f (void)
                r_refdef.view.quality = 1;
                r_refdef.view.clear = true;
                R_Mesh_Start();
-               R_RenderView(0, NULL, NULL, r_refdef.view.x, r_refdef.view.y, r_refdef.view.width, r_refdef.view.height);
+               R_RenderView(rt->fbo, rt->depthtexture, rt->colortexture[0], 0, 0, size, size);
                R_Mesh_Finish();
                SCR_ScreenShot(filename, buffer1, buffer2, 0, vid.height - (r_refdef.view.y + r_refdef.view.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false, false, false, false);
        }
@@ -2230,6 +2237,7 @@ static void SCR_DrawScreen (void)
        DrawQ_Finish();
 
        R_Mesh_Finish();
+       R_RenderTarget_FreeUnused(false);
 }
 
 typedef struct loadingscreenstack_s
index 6be9d5a..cbc0fc0 100644 (file)
--- a/client.h
+++ b/client.h
@@ -64,6 +64,8 @@ typedef enum r_stat_e
        r_stat_bloom,
        r_stat_bloom_copypixels,
        r_stat_bloom_drawpixels,
+       r_stat_rendertargets_used,
+       r_stat_rendertargets_pixels,
        r_stat_indexbufferuploadcount,
        r_stat_indexbufferuploadsize,
        r_stat_vertexbufferuploadcount,
index fbe5758..fa55fd4 100644 (file)
@@ -175,6 +175,7 @@ static cvar_t r_glsl = {CVAR_READONLY, "r_glsl", "1", "indicates whether the Ope
 
 cvar_t r_usedepthtextures = {CVAR_SAVE, "r_usedepthtextures", "1", "use depth texture instead of depth renderbuffer where possible, uses less video memory but may render slower (or faster) depending on hardware"};
 cvar_t r_viewfbo = {CVAR_SAVE, "r_viewfbo", "0", "enables use of an 8bit (1) or 16bit (2) or 32bit (3) per component float framebuffer render, which may be at a different resolution than the video mode"};
+cvar_t r_rendertarget_debug = {0, "r_rendertarget_debug", "-1", "replaces the view with the contents of the specified render target (by number - note that these can fluctuate depending on scene)"};
 cvar_t r_viewscale = {CVAR_SAVE, "r_viewscale", "1", "scaling factor for resolution of the fbo rendering method, must be > 0, can be above 1 for a costly antialiasing behavior, typical values are 0.5 for 1/4th as many pixels rendered, or 1 for normal rendering"};
 cvar_t r_viewscale_fpsscaling = {CVAR_SAVE, "r_viewscale_fpsscaling", "0", "change resolution based on framerate"};
 cvar_t r_viewscale_fpsscaling_min = {CVAR_SAVE, "r_viewscale_fpsscaling_min", "0.0625", "worst acceptable quality"};
@@ -212,7 +213,6 @@ cvar_t r_water_reflectdistort = {CVAR_SAVE, "r_water_reflectdistort", "0.01", "h
 cvar_t r_water_scissormode = {0, "r_water_scissormode", "3", "scissor (1) or cull (2) or both (3) water renders"};
 cvar_t r_water_lowquality = {0, "r_water_lowquality", "0", "special option to accelerate water rendering, 1 disables shadows and particles, 2 disables all dynamic lights"};
 cvar_t r_water_hideplayer = {CVAR_SAVE, "r_water_hideplayer", "0", "if set to 1 then player will be hidden in refraction views, if set to 2 then player will also be hidden in reflection views, player is always visible in camera views"};
-cvar_t r_water_fbo = {CVAR_SAVE, "r_water_fbo", "1", "enables use of render to texture for water effects, otherwise copy to texture is used (slower)"};
 
 cvar_t r_lerpsprites = {CVAR_SAVE, "r_lerpsprites", "0", "enables animation smoothing on sprites"};
 cvar_t r_lerpmodels = {CVAR_SAVE, "r_lerpmodels", "1", "enables animation smoothing on models"};
@@ -319,7 +319,6 @@ static int r_qwskincache_size;
 
 /// vertex coordinates for a quad that covers the screen exactly
 extern const float r_screenvertex3f[12];
-extern const float r_d3dscreenvertex3f[12];
 const float r_screenvertex3f[12] =
 {
        0, 0, 0,
@@ -327,13 +326,6 @@ const float r_screenvertex3f[12] =
        1, 1, 0,
        0, 1, 0
 };
-const float r_d3dscreenvertex3f[12] =
-{
-       0, 1, 0,
-       1, 1, 0,
-       1, 0, 0,
-       0, 0, 0
-};
 
 void R_ModulateColors(float *in, float *out, int verts, float r, float g, float b)
 {
@@ -2608,13 +2600,13 @@ void R_SetupShader_Surface(const float rtlightambient[3], const float rtlightdif
                if (rsurface.rtlight                                  ) R_Mesh_TexBind(GL20TU_ATTENUATION       , r_shadow_attenuationgradienttexture                 );
                if (rsurfacepass == RSURFPASS_BACKGROUND)
                {
-                       R_Mesh_TexBind(GL20TU_REFRACTION        , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black);
-                       if(mode == SHADERMODE_GENERIC) R_Mesh_TexBind(GL20TU_FIRST             , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black);
-                       R_Mesh_TexBind(GL20TU_REFLECTION        , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black);
+                       R_Mesh_TexBind(GL20TU_REFRACTION        , waterplane->rt_refraction ? waterplane->rt_refraction->colortexture[0] : r_texture_black);
+                       if(mode == SHADERMODE_GENERIC) R_Mesh_TexBind(GL20TU_FIRST             , waterplane->rt_camera ? waterplane->rt_camera->colortexture[0] : r_texture_black);
+                       R_Mesh_TexBind(GL20TU_REFLECTION        , waterplane->rt_reflection ? waterplane->rt_reflection->colortexture[0] : r_texture_black);
                }
                else
                {
-                       if (permutation & SHADERPERMUTATION_REFLECTION        ) R_Mesh_TexBind(GL20TU_REFLECTION        , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black);
+                       if (permutation & SHADERPERMUTATION_REFLECTION        ) R_Mesh_TexBind(GL20TU_REFLECTION        , waterplane->rt_reflection ? waterplane->rt_reflection->colortexture[0] : r_texture_black);
                }
 //             if (rsurfacepass == RSURFPASS_DEFERREDLIGHT           ) R_Mesh_TexBind(GL20TU_SCREENNORMALMAP   , r_shadow_prepassgeometrynormalmaptexture            );
                if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP  ) R_Mesh_TexBind(GL20TU_SCREENDIFFUSE     , r_shadow_prepasslightingdiffusetexture              );
@@ -2791,13 +2783,13 @@ void R_SetupShader_Surface(const float rtlightambient[3], const float rtlightdif
                if (r_glsl_permutation->tex_Texture_Attenuation     >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Attenuation      , r_shadow_attenuationgradienttexture                 );
                if (rsurfacepass == RSURFPASS_BACKGROUND)
                {
-                       if (r_glsl_permutation->tex_Texture_Refraction  >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Refraction        , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black);
-                       if (r_glsl_permutation->tex_Texture_First       >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First             , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black);
-                       if (r_glsl_permutation->tex_Texture_Reflection  >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Reflection        , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black);
+                       if (r_glsl_permutation->tex_Texture_Refraction  >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Refraction        , waterplane->rt_refraction ? waterplane->rt_refraction->colortexture[0] : r_texture_black);
+                       if (r_glsl_permutation->tex_Texture_First       >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First             , waterplane->rt_camera ? waterplane->rt_camera->colortexture[0] : r_texture_black);
+                       if (r_glsl_permutation->tex_Texture_Reflection  >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Reflection        , waterplane->rt_reflection ? waterplane->rt_reflection->colortexture[0] : r_texture_black);
                }
                else
                {
-                       if (r_glsl_permutation->tex_Texture_Reflection >= 0 && waterplane) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Reflection        , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black);
+                       if (r_glsl_permutation->tex_Texture_Reflection >= 0 && waterplane) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Reflection        , waterplane->rt_reflection ? waterplane->rt_reflection->colortexture[0] : r_texture_black);
                }
                if (r_glsl_permutation->tex_Texture_ScreenNormalMap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenNormalMap   , r_shadow_prepassgeometrynormalmaptexture            );
                if (r_glsl_permutation->tex_Texture_ScreenDiffuse   >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenDiffuse     , r_shadow_prepasslightingdiffusetexture              );
@@ -2938,13 +2930,13 @@ void R_SetupShader_Surface(const float rtlightambient[3], const float rtlightdif
                if (rsurface.rtlight                                  ) R_Mesh_TexBind(GL20TU_ATTENUATION       , r_shadow_attenuationgradienttexture                 );
                if (rsurfacepass == RSURFPASS_BACKGROUND)
                {
-                       R_Mesh_TexBind(GL20TU_REFRACTION        , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black);
-                       if(mode == SHADERMODE_GENERIC) R_Mesh_TexBind(GL20TU_FIRST             , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black);
-                       R_Mesh_TexBind(GL20TU_REFLECTION        , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black);
+                       R_Mesh_TexBind(GL20TU_REFRACTION        , waterplane->rt_refraction ? waterplane->rt_refraction->colortexture[0] : r_texture_black);
+                       if(mode == SHADERMODE_GENERIC) R_Mesh_TexBind(GL20TU_FIRST             , waterplane->rt_camera ? waterplane->rt_camera->colortexture[0] : r_texture_black);
+                       R_Mesh_TexBind(GL20TU_REFLECTION        , waterplane->rt_reflection ? waterplane->rt_reflection->colortexture[0] : r_texture_black);
                }
                else
                {
-                       if (permutation & SHADERPERMUTATION_REFLECTION        ) R_Mesh_TexBind(GL20TU_REFLECTION        , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black);
+                       if (permutation & SHADERPERMUTATION_REFLECTION        ) R_Mesh_TexBind(GL20TU_REFLECTION        , waterplane->rt_reflection ? waterplane->rt_reflection->colortexture[0] : r_texture_black);
                }
 //             if (rsurfacepass == RSURFPASS_DEFERREDLIGHT           ) R_Mesh_TexBind(GL20TU_SCREENNORMALMAP   , r_shadow_prepassgeometrynormalmaptexture            );
                if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP  ) R_Mesh_TexBind(GL20TU_SCREENDIFFUSE     , r_shadow_prepasslightingdiffusetexture              );
@@ -4068,6 +4060,7 @@ static void gl_main_start(void)
        r_texture_gammaramps = NULL;
        //r_texture_fogintensity = NULL;
        memset(&r_fb, 0, sizeof(r_fb));
+       Mem_ExpandableArray_NewArray(&r_fb.rendertargets, r_main_mempool, sizeof(r_rendertarget_t), 128);
        r_glsl_permutation = NULL;
        memset(r_glsl_permutationhash, 0, sizeof(r_glsl_permutationhash));
        Mem_ExpandableArray_NewArray(&r_glsl_permutationarray, r_main_mempool, sizeof(r_glsl_permutation_t), 256);
@@ -4107,6 +4100,8 @@ static void gl_main_start(void)
 
 static void gl_main_shutdown(void)
 {
+       R_RenderTarget_FreeUnused(true);
+       Mem_ExpandableArray_FreeArray(&r_fb.rendertargets);
        R_AnimCache_Free();
        R_FrameData_Reset();
        R_BufferData_Reset();
@@ -4315,6 +4310,7 @@ void GL_Main_Init(void)
        Cvar_RegisterVariable(&gl_combine);
        Cvar_RegisterVariable(&r_usedepthtextures);
        Cvar_RegisterVariable(&r_viewfbo);
+       Cvar_RegisterVariable(&r_rendertarget_debug);
        Cvar_RegisterVariable(&r_viewscale);
        Cvar_RegisterVariable(&r_viewscale_fpsscaling);
        Cvar_RegisterVariable(&r_viewscale_fpsscaling_min);
@@ -4353,7 +4349,6 @@ void GL_Main_Init(void)
        Cvar_RegisterVariable(&r_water_scissormode);
        Cvar_RegisterVariable(&r_water_lowquality);
        Cvar_RegisterVariable(&r_water_hideplayer);
-       Cvar_RegisterVariable(&r_water_fbo);
 
        Cvar_RegisterVariable(&r_lerpsprites);
        Cvar_RegisterVariable(&r_lerpmodels);
@@ -5734,12 +5729,105 @@ void R_RenderView_UpdateViewVectors(void)
        Matrix4x4_Invert_Full(&r_refdef.view.inverse_matrix, &r_refdef.view.matrix);
 }
 
+void R_RenderTarget_FreeUnused(qboolean force)
+{
+       int i, j, end;
+       end = Mem_ExpandableArray_IndexRange(&r_fb.rendertargets);
+       for (i = 0; i < end; i++)
+       {
+               r_rendertarget_t *r = (r_rendertarget_t *)Mem_ExpandableArray_RecordAtIndex(&r_fb.rendertargets, i);
+               // free resources for rendertargets that have not been used for a while
+               // (note: this check is run after the frame render, so any targets used
+               // this frame will not be affected even at low framerates)
+               if (r && (realtime - r->lastusetime > 0.2 || force))
+               {
+                       if (r->fbo)
+                               R_Mesh_DestroyFramebufferObject(r->fbo);
+                       for (j = 0; j < sizeof(r->colortexture) / sizeof(r->colortexture[0]); j++)
+                               if (r->colortexture[j])
+                                       R_FreeTexture(r->colortexture[j]);
+                       if (r->depthtexture)
+                               R_FreeTexture(r->depthtexture);
+                       Mem_ExpandableArray_FreeRecord(&r_fb.rendertargets, r);
+               }
+       }
+}
+
+static void R_CalcTexCoordsForView(float x, float y, float w, float h, float tw, float th, float *texcoord2f)
+{
+       float iw = 1.0f / tw, ih = 1.0f / th, x1, y1, x2, y2;
+       switch (vid.renderpath)
+       {
+       case RENDERPATH_D3D9:
+               x1 = (x + 0.5f) * iw;
+               x2 = (x + 0.5f + w) * iw;
+               y1 = (y + 0.5f) * ih;
+               y2 = (y + 0.5f + h) * ih;
+               break;
+       default:
+               x1 = x * iw;
+               x2 = (x + w) * iw;
+               y1 = (th - y) * ih;
+               y2 = (th - y - h) * ih;
+               break;
+       }
+       texcoord2f[0] = x1;
+       texcoord2f[2] = x2;
+       texcoord2f[4] = x2;
+       texcoord2f[6] = x1;
+       texcoord2f[1] = y1;
+       texcoord2f[3] = y1;
+       texcoord2f[5] = y2;
+       texcoord2f[7] = y2;
+}
+
+r_rendertarget_t *R_RenderTarget_Get(int texturewidth, int textureheight, textype_t depthtextype, qboolean depthisrenderbuffer, textype_t colortextype0, textype_t colortextype1, textype_t colortextype2, textype_t colortextype3)
+{
+       int i, j, end;
+       r_rendertarget_t *r = NULL;
+       char vabuf[256];
+       // first try to reuse an existing slot if possible
+       end = Mem_ExpandableArray_IndexRange(&r_fb.rendertargets);
+       for (i = 0; i < end; i++)
+       {
+               r = (r_rendertarget_t *)Mem_ExpandableArray_RecordAtIndex(&r_fb.rendertargets, i);
+               if (r && r->lastusetime != realtime && r->texturewidth == texturewidth && r->textureheight == textureheight && r->depthtextype == depthtextype && r->colortextype[0] == colortextype0 && r->colortextype[1] == colortextype1 && r->colortextype[2] == colortextype2 && r->colortextype[3] == colortextype3)
+                       break;
+       }
+       if (i == end)
+       {
+               // no unused exact match found, so we have to make one in the first unused slot
+               r = (r_rendertarget_t *)Mem_ExpandableArray_AllocRecord(&r_fb.rendertargets);
+               r->texturewidth = texturewidth;
+               r->textureheight = textureheight;
+               r->colortextype[0] = colortextype0;
+               r->colortextype[1] = colortextype1;
+               r->colortextype[2] = colortextype2;
+               r->colortextype[3] = colortextype3;
+               r->depthtextype = depthtextype;
+               r->depthisrenderbuffer = depthisrenderbuffer;
+               for (j = 0; j < 4; j++)
+                       if (r->colortextype[j])
+                               r->colortexture[j] = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "rendertarget%i_%i_type%i", i, j, (int)r->colortextype[j]), r->texturewidth, r->textureheight, NULL, r->colortextype[j], TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
+               if (r->depthtextype)
+               {
+                       if (r->depthisrenderbuffer)
+                               r->depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, va(vabuf, sizeof(vabuf), "renderbuffer%i_depth_type%i", i, (int)r->depthtextype), r->texturewidth, r->textureheight, r->depthtextype);
+                       else
+                               r->depthtexture = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "rendertarget%i_depth_type%i", i, j, (int)r->depthtextype), r->texturewidth, r->textureheight, NULL, r->depthtextype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
+               }
+               r->fbo = R_Mesh_CreateFramebufferObject(r->depthtexture, r->colortexture[0], r->colortexture[1], r->colortexture[2], r->colortexture[3]);
+       }
+       r_refdef.stats[r_stat_rendertargets_used]++;
+       r_refdef.stats[r_stat_rendertargets_pixels] += r->texturewidth * r->textureheight;
+       r->lastusetime = realtime;
+       R_CalcTexCoordsForView(0, 0, r->texturewidth, r->textureheight, r->texturewidth, r->textureheight, r->texcoord2f);
+       return r;
+}
+
 static void R_Water_StartFrame(void)
 {
-       int i;
-       int waterwidth, waterheight, texturewidth, textureheight, camerawidth, cameraheight;
-       r_waterstate_waterplane_t *p;
-       qboolean usewaterfbo = (r_viewfbo.integer >= 1 || r_water_fbo.integer >= 1) && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2;
+       int waterwidth, waterheight;
 
        if (vid.width > (int)vid.maxtexturesize_2d || vid.height > (int)vid.maxtexturesize_2d)
                return;
@@ -5761,76 +5849,25 @@ static void R_Water_StartFrame(void)
 
        // set waterwidth and waterheight to the water resolution that will be
        // used (often less than the screen resolution for faster rendering)
-       R_GetScaledViewSize(bound(1, vid.width * r_water_resolutionmultiplier.value, vid.width), bound(1, vid.height * r_water_resolutionmultiplier.value, vid.height), &waterwidth, &waterheight);
+       waterwidth = (int)bound(1, r_refdef.view.width * r_water_resolutionmultiplier.value, r_refdef.view.width);
+       waterheight = (int)bound(1, r_refdef.view.height * r_water_resolutionmultiplier.value, r_refdef.view.height);
+       R_GetScaledViewSize(waterwidth, waterheight, &waterwidth, &waterheight);
 
-       // calculate desired texture sizes
-       // can't use water if the card does not support the texture size
        if (!r_water.integer || r_showsurfaces.integer)
-               texturewidth = textureheight = waterwidth = waterheight = camerawidth = cameraheight = 0;
-       else if (vid.support.arb_texture_non_power_of_two)
-       {
-               texturewidth = waterwidth;
-               textureheight = waterheight;
-               camerawidth = waterwidth;
-               cameraheight = waterheight;
-       }
-       else
-       {
-               for (texturewidth   = 1;texturewidth     <  waterwidth ;texturewidth   *= 2);
-               for (textureheight  = 1;textureheight    <  waterheight;textureheight  *= 2);
-               for (camerawidth    = 1;camerawidth  * 2 <= waterwidth ;camerawidth    *= 2);
-               for (cameraheight   = 1;cameraheight * 2 <= waterheight;cameraheight   *= 2);
-       }
-
-       // allocate textures as needed
-       if (r_fb.water.texturewidth != texturewidth || r_fb.water.textureheight != textureheight || r_fb.water.camerawidth != camerawidth || r_fb.water.cameraheight != cameraheight || (r_fb.depthtexture && !usewaterfbo))
-       {
-               r_fb.water.maxwaterplanes = MAX_WATERPLANES;
-               for (i = 0, p = r_fb.water.waterplanes;i < r_fb.water.maxwaterplanes;i++, p++)
-               {
-                       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;
-               r_fb.water.textureheight = textureheight;
-               r_fb.water.camerawidth = camerawidth;
-               r_fb.water.cameraheight = cameraheight;
-       }
-
-       if (r_fb.water.texturewidth)
-       {
-               int scaledwidth, scaledheight;
-
-               r_fb.water.enabled = true;
-
-               // water resolution is usually reduced
-               r_fb.water.waterwidth = (int)bound(1, r_refdef.view.width * r_water_resolutionmultiplier.value, r_refdef.view.width);
-               r_fb.water.waterheight = (int)bound(1, r_refdef.view.height * r_water_resolutionmultiplier.value, r_refdef.view.height);
-               R_GetScaledViewSize(r_fb.water.waterwidth, r_fb.water.waterheight, &scaledwidth, &scaledheight);
-
-               // set up variables that will be used in shader setup
-               r_fb.water.screenscale[0] = 0.5f * (float)scaledwidth / (float)r_fb.water.texturewidth;
-               r_fb.water.screenscale[1] = 0.5f * (float)scaledheight / (float)r_fb.water.textureheight;
-               r_fb.water.screencenter[0] = 0.5f * (float)scaledwidth / (float)r_fb.water.texturewidth;
-               r_fb.water.screencenter[1] = 0.5f * (float)scaledheight / (float)r_fb.water.textureheight;
-       }
+               waterwidth = waterheight = 0;
+
+       // set up variables that will be used in shader setup
+       r_fb.water.waterwidth = waterwidth;
+       r_fb.water.waterheight = waterheight;
+       r_fb.water.texturewidth = waterwidth;
+       r_fb.water.textureheight = waterheight;
+       r_fb.water.camerawidth = waterwidth;
+       r_fb.water.cameraheight = waterheight;
+       r_fb.water.screenscale[0] = 0.5f;
+       r_fb.water.screenscale[1] = 0.5f;
+       r_fb.water.screencenter[0] = 0.5f;
+       r_fb.water.screencenter[1] = 0.5f;
+       r_fb.water.enabled = waterwidth != 0;
 
        r_fb.water.maxwaterplanes = MAX_WATERPLANES;
        r_fb.water.numwaterplanes = 0;
@@ -5953,21 +5990,17 @@ void R_Water_AddWaterPlane(msurface_t *surface, int entno)
 extern cvar_t r_drawparticles;
 extern cvar_t r_drawdecals;
 
-static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, int x, int y, int width, int height)
+static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, int viewx, int viewy, int viewwidth, int viewheight)
 {
        int myscissor[4];
        r_refdef_view_t originalview;
        r_refdef_view_t myview;
        int planeindex, qualityreduction = 0, old_r_dynamic = 0, old_r_shadows = 0, old_r_worldrtlight = 0, old_r_dlight = 0, old_r_particles = 0, old_r_decals = 0;
-       int waterx, watery;
        r_waterstate_waterplane_t *p;
        vec3_t visorigin;
-       qboolean usewaterfbo = (r_viewfbo.integer >= 1 || r_water_fbo.integer >= 1) && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2;
-       char vabuf[1024];
+       r_rendertarget_t *rt;
 
        originalview = r_refdef.view;
-       waterx = usewaterfbo ? 0 : x;
-       watery = usewaterfbo ? 0 : y;
 
        // lowquality hack, temporarily shut down some cvars and restore afterwards
        qualityreduction = r_water_lowquality.integer;
@@ -5993,61 +6026,16 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
                }
        }
 
-       // make sure enough textures are allocated
-       for (planeindex = 0, p = r_fb.water.waterplanes;planeindex < r_fb.water.numwaterplanes;planeindex++, p++)
+       for (planeindex = 0, p = r_fb.water.waterplanes; planeindex < r_fb.water.numwaterplanes; planeindex++, p++)
        {
-               if (r_water_cameraentitiesonly.value != 0 && !p->camera_entity)
-                       continue;
-               if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION))
-               {
-                       if (!p->texture_refraction)
-                               p->texture_refraction = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "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 (usewaterfbo)
-                       {
-                               if (r_fb.water.depthtexture == NULL)
-                                       r_fb.water.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, TEXTYPE_DEPTHBUFFER24STENCIL8);
-                               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(vabuf, sizeof(vabuf), "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 (usewaterfbo)
-                       {
-                               if (r_fb.water.depthtexture == NULL)
-                                       r_fb.water.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, TEXTYPE_DEPTHBUFFER24STENCIL8);
-                               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(vabuf, sizeof(vabuf), "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 (usewaterfbo)
-                       {
-                               if (r_fb.water.depthtexture == NULL)
-                                       r_fb.water.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, TEXTYPE_DEPTHBUFFER24STENCIL8);
-                               if (p->fbo_reflection == 0)
-                                       p->fbo_reflection = R_Mesh_CreateFramebufferObject(r_fb.water.depthtexture, p->texture_reflection, NULL, NULL, NULL);
-                       }
-               }
+               p->rt_reflection = NULL;
+               p->rt_refraction = NULL;
+               p->rt_camera = NULL;
        }
 
        // render views
        r_refdef.view = originalview;
        r_refdef.view.showdebug = false;
-       r_refdef.view.x = waterx;
-       r_refdef.view.y = watery;
        r_refdef.view.width = r_fb.water.waterwidth;
        r_refdef.view.height = r_fb.water.waterheight;
        r_refdef.view.useclipplane = true;
@@ -6057,20 +6045,27 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
        {
                if (r_water_cameraentitiesonly.value != 0 && !p->camera_entity)
                        continue;
+
                if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION))
                {
+                       rt = R_RenderTarget_Get(r_fb.water.waterwidth, r_fb.water.waterheight, TEXTYPE_DEPTHBUFFER24STENCIL8, true, r_fb.rt_screen->colortextype[0], TEXTYPE_UNUSED, TEXTYPE_UNUSED, TEXTYPE_UNUSED);
+                       if (rt->colortexture[0] == NULL || rt->depthtexture == NULL)
+                               goto error;
                        r_refdef.view = myview;
+                       Matrix4x4_Reflect(&r_refdef.view.matrix, p->plane.normal[0], p->plane.normal[1], p->plane.normal[2], p->plane.dist, -2);
+                       Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, r_refdef.view.origin);
                        if(r_water_scissormode.integer)
                        {
-                               R_SetupView(true, p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection, waterx, watery, r_fb.water.waterwidth, r_fb.water.waterheight);
-                               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
+                               R_SetupView(true, rt->fbo, rt->depthtexture, rt->colortexture[0], 0, 0, r_fb.water.waterwidth, r_fb.water.waterheight);
+                               if (R_ScissorForBBox(p->mins, p->maxs, myscissor))
+                               {
+                                       p->rt_reflection = NULL;
+                                       p->rt_refraction = NULL;
+                                       p->rt_camera = NULL;
+                                       continue;
+                               }
                        }
 
-                       // render reflected scene and copy into texture
-                       Matrix4x4_Reflect(&r_refdef.view.matrix, p->plane.normal[0], p->plane.normal[1], p->plane.normal[2], p->plane.dist, -2);
-                       // update the r_refdef.view.origin because otherwise the sky renders at the wrong location (amongst other problems)
-                       Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, r_refdef.view.origin);
                        r_refdef.view.clipplane = p->plane;
                        // reverse the cullface settings for this render
                        r_refdef.view.cullface_front = GL_FRONT;
@@ -6085,12 +6080,10 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
                        }
 
                        r_fb.water.hideplayer = ((r_water_hideplayer.integer >= 2) && !chase_active.integer);
-                       R_ResetViewRendering3D(p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection, waterx, watery, r_fb.water.waterwidth, r_fb.water.waterheight);
-                       if (p->fbo_reflection)
-                               GL_ScissorTest(false);
+                       R_ResetViewRendering3D(rt->fbo, rt->depthtexture, rt->colortexture[0], 0, 0, rt->texturewidth, rt->textureheight);
+                       GL_ScissorTest(false);
                        R_ClearScreen(r_refdef.fogenabled);
-                       if (p->fbo_reflection)
-                               GL_ScissorTest(true);
+                       GL_ScissorTest(true);
                        if(r_water_scissormode.integer & 2)
                                R_View_UpdateWithScissor(myscissor);
                        else
@@ -6098,23 +6091,30 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
                        R_AnimCache_CacheVisibleEntities();
                        if(r_water_scissormode.integer & 1)
                                GL_Scissor(myscissor[0], myscissor[1], myscissor[2], myscissor[3]);
-                       R_RenderScene(p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection, waterx, watery, r_fb.water.waterwidth, r_fb.water.waterheight);
+                       R_RenderScene(rt->fbo, rt->depthtexture, rt->colortexture[0], 0, 0, rt->texturewidth, rt->textureheight);
 
-                       if (!p->fbo_reflection)
-                               R_Mesh_CopyToTexture(p->texture_reflection, 0, 0, waterx, watery, r_fb.water.waterwidth, r_fb.water.waterheight);
                        r_fb.water.hideplayer = false;
+                       p->rt_reflection = rt;
                }
 
                // render the normal view scene and copy into texture
                // (except that a clipping plane should be used to hide everything on one side of the water, and the viewer's weapon model should be omitted)
                if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION))
                {
+                       rt = R_RenderTarget_Get(r_fb.water.waterwidth, r_fb.water.waterheight, TEXTYPE_DEPTHBUFFER24STENCIL8, true, r_fb.rt_screen->colortextype[0], TEXTYPE_UNUSED, TEXTYPE_UNUSED, TEXTYPE_UNUSED);
+                       if (rt->colortexture[0] == NULL || rt->depthtexture == NULL)
+                               goto error;
                        r_refdef.view = myview;
                        if(r_water_scissormode.integer)
                        {
-                               R_SetupView(true, p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction, waterx, watery, r_fb.water.waterwidth, r_fb.water.waterheight);
-                               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
+                               R_SetupView(true, rt->fbo, rt->depthtexture, rt->colortexture[0], 0, 0, r_fb.water.waterwidth, r_fb.water.waterheight);
+                               if (R_ScissorForBBox(p->mins, p->maxs, myscissor))
+                               {
+                                       p->rt_reflection = NULL;
+                                       p->rt_refraction = NULL;
+                                       p->rt_camera = NULL;
+                                       continue;
+                               }
                        }
 
                        r_fb.water.hideplayer = ((r_water_hideplayer.integer >= 1) && !chase_active.integer);
@@ -6138,12 +6138,10 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 
                        PlaneClassify(&r_refdef.view.clipplane);
 
-                       R_ResetViewRendering3D(p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction, waterx, watery, r_fb.water.waterwidth, r_fb.water.waterheight);
-                       if (p->fbo_refraction)
-                               GL_ScissorTest(false);
+                       R_ResetViewRendering3D(rt->fbo, rt->depthtexture, rt->colortexture[0], 0, 0, rt->texturewidth, rt->textureheight);
+                       GL_ScissorTest(false);
                        R_ClearScreen(r_refdef.fogenabled);
-                       if (p->fbo_refraction)
-                               GL_ScissorTest(true);
+                       GL_ScissorTest(true);
                        if(r_water_scissormode.integer & 2)
                                R_View_UpdateWithScissor(myscissor);
                        else
@@ -6151,22 +6149,22 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
                        R_AnimCache_CacheVisibleEntities();
                        if(r_water_scissormode.integer & 1)
                                GL_Scissor(myscissor[0], myscissor[1], myscissor[2], myscissor[3]);
-                       R_RenderScene(p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction, waterx, watery, r_fb.water.waterwidth, r_fb.water.waterheight);
+                       R_RenderScene(rt->fbo, rt->depthtexture, rt->colortexture[0], 0, 0, rt->texturewidth, rt->textureheight);
 
-                       if (!p->fbo_refraction)
-                               R_Mesh_CopyToTexture(p->texture_refraction, 0, 0, waterx, watery, r_fb.water.waterwidth, r_fb.water.waterheight);
                        r_fb.water.hideplayer = false;
+                       p->rt_refraction = rt;
                }
                else if (p->materialflags & MATERIALFLAG_CAMERA)
                {
+                       rt = R_RenderTarget_Get(r_fb.water.waterwidth, r_fb.water.waterheight, TEXTYPE_DEPTHBUFFER24STENCIL8, true, r_fb.rt_screen->colortextype[0], TEXTYPE_UNUSED, TEXTYPE_UNUSED, TEXTYPE_UNUSED);
+                       if (rt->colortexture[0] == NULL || rt->depthtexture == NULL)
+                               goto error;
                        r_refdef.view = myview;
 
                        r_refdef.view.clipplane = p->plane;
                        VectorNegate(r_refdef.view.clipplane.normal, r_refdef.view.clipplane.normal);
                        r_refdef.view.clipplane.dist = -r_refdef.view.clipplane.dist;
 
-                       r_refdef.view.x = waterx;
-                       r_refdef.view.y = watery;
                        r_refdef.view.width = r_fb.water.camerawidth;
                        r_refdef.view.height = r_fb.water.cameraheight;
                        r_refdef.view.frustum_x = 1; // tan(45 * M_PI / 180.0);
@@ -6202,28 +6200,23 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
 
                        r_fb.water.hideplayer = false;
 
-                       R_ResetViewRendering3D(p->fbo_camera, r_fb.water.depthtexture, p->texture_camera, waterx, watery, r_fb.water.camerawidth, r_fb.water.cameraheight);
-                       if (p->fbo_camera)
-                               GL_ScissorTest(false);
+                       R_ResetViewRendering3D(rt->fbo, rt->depthtexture, rt->colortexture[0], 0, 0, rt->texturewidth, rt->textureheight);
+                       GL_ScissorTest(false);
                        R_ClearScreen(r_refdef.fogenabled);
-                       if (p->fbo_camera)
-                               GL_ScissorTest(true);
+                       GL_ScissorTest(true);
                        R_View_Update();
                        R_AnimCache_CacheVisibleEntities();
-                       R_RenderScene(p->fbo_camera, r_fb.water.depthtexture, p->texture_camera, waterx, watery, r_fb.water.camerawidth, r_fb.water.cameraheight);
+                       R_RenderScene(rt->fbo, rt->depthtexture, rt->colortexture[0], 0, 0, rt->texturewidth, rt->textureheight);
 
-                       if (!p->fbo_camera)
-                               R_Mesh_CopyToTexture(p->texture_camera, 0, 0, waterx, watery, r_fb.water.camerawidth, r_fb.water.cameraheight);
                        r_fb.water.hideplayer = false;
+                       p->rt_camera = rt;
                }
 
        }
        if(vid.renderpath==RENDERPATH_SOFT) DPSOFTRAST_ClipPlane(0, 0, 0, 1);
        r_fb.water.renderingscene = false;
        r_refdef.view = originalview;
-       R_ResetViewRendering3D(fbo, depthtexture, colortexture, x, y, width, height);
-       if (!r_fb.water.depthtexture)
-               R_ClearScreen(r_refdef.fogenabled);
+       R_ResetViewRendering3D(fbo, depthtexture, colortexture, viewx, viewy, viewwidth, viewheight);
        R_View_Update();
        R_AnimCache_CacheVisibleEntities();
        goto finish;
@@ -6253,21 +6246,23 @@ finish:
 
 static void R_Bloom_StartFrame(void)
 {
-       int i;
        int bloomtexturewidth, bloomtextureheight, screentexturewidth, screentextureheight;
        int viewwidth, viewheight;
-       qboolean useviewfbo = r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2;
        textype_t textype = TEXTYPE_COLORBUFFER;
 
+       // clear the pointers to rendertargets from last frame as they're stale
+       r_fb.rt_screen = NULL;
+       r_fb.rt_bloom = NULL;
+
        switch (vid.renderpath)
        {
        case RENDERPATH_GL20:
                r_fb.usedepthtextures = r_usedepthtextures.integer != 0;
-               if (vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two)
-               {
-                       if (r_viewfbo.integer == 2) textype = TEXTYPE_COLORBUFFER16F;
-                       if (r_viewfbo.integer == 3) textype = TEXTYPE_COLORBUFFER32F;
-               }
+               if (r_viewfbo.integer == 2) textype = TEXTYPE_COLORBUFFER16F;
+               if (r_viewfbo.integer == 3) textype = TEXTYPE_COLORBUFFER32F;
+               // for simplicity, bloom requires FBO render to texture, which basically all video drivers support now
+               if (!vid.support.ext_framebuffer_object)
+                       return;
                break;
        case RENDERPATH_GL11:
        case RENDERPATH_GL13:
@@ -6303,21 +6298,6 @@ static void R_Bloom_StartFrame(void)
 
        R_GetScaledViewSize(r_refdef.view.width, r_refdef.view.height, &viewwidth, &viewheight);
 
-       switch(vid.renderpath)
-       {
-       case RENDERPATH_GL20:
-       case RENDERPATH_D3D9:
-       case RENDERPATH_D3D10:
-       case RENDERPATH_D3D11:
-       case RENDERPATH_SOFT:
-       case RENDERPATH_GLES2:
-               break;
-       case RENDERPATH_GL11:
-       case RENDERPATH_GL13:
-       case RENDERPATH_GLES1:
-               return;
-       }
-
        // set bloomwidth and bloomheight to the bloom resolution that will be
        // used (often less than the screen resolution for faster rendering)
        r_fb.bloomwidth = bound(1, r_bloom_resolution.integer, vid.width);
@@ -6327,20 +6307,10 @@ static void R_Bloom_StartFrame(void)
        r_fb.bloomheight = bound(1, r_fb.bloomheight, (int)vid.maxtexturesize_2d);
 
        // calculate desired texture sizes
-       if (vid.support.arb_texture_non_power_of_two)
-       {
-               screentexturewidth = vid.width;
-               screentextureheight = vid.height;
-               bloomtexturewidth = r_fb.bloomwidth;
-               bloomtextureheight = r_fb.bloomheight;
-       }
-       else
-       {
-               for (screentexturewidth  = 1;screentexturewidth  < vid.width       ;screentexturewidth  *= 2);
-               for (screentextureheight = 1;screentextureheight < vid.height      ;screentextureheight *= 2);
-               for (bloomtexturewidth   = 1;bloomtexturewidth   < r_fb.bloomwidth ;bloomtexturewidth   *= 2);
-               for (bloomtextureheight  = 1;bloomtextureheight  < r_fb.bloomheight;bloomtextureheight  *= 2);
-       }
+       screentexturewidth = viewwidth;
+       screentextureheight = viewheight;
+       bloomtexturewidth = r_fb.bloomwidth;
+       bloomtextureheight = r_fb.bloomheight;
 
        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))
        {
@@ -6349,55 +6319,15 @@ static void R_Bloom_StartFrame(void)
                Cvar_SetValueQuick(&r_damageblur, 0);
        }
 
-       if (!((r_glsl_postprocess.integer || r_fxaa.integer) || (!R_Stereo_ColorMasking() && r_glsl_saturation.value != 1) || !vid_gammatables_trivial)
-        && !r_bloom.integer
-        && (R_Stereo_Active() || (r_motionblur.value <= 0 && r_damageblur.value <= 0))
-        && !useviewfbo
-        && r_viewscale.value == 1.0f
-        && !r_viewscale_fpsscaling.integer)
-               screentexturewidth = screentextureheight = 0;
-       if (!r_bloom.integer)
-               bloomtexturewidth = bloomtextureheight = 0;
-
-       // allocate textures as needed
-       if (r_fb.screentexturewidth != screentexturewidth
-        || r_fb.screentextureheight != screentextureheight
-        || r_fb.bloomtexturewidth != bloomtexturewidth
-        || r_fb.bloomtextureheight != bloomtextureheight
-        || r_fb.textype != textype
-        || useviewfbo != (r_fb.fbo != 0))
+       // allocate motionblur ghost texture if needed - this is the only persistent texture and is only useful on the main view
+       if (r_refdef.view.ismain && (r_fb.screentexturewidth != screentexturewidth || r_fb.screentextureheight != screentextureheight || r_fb.textype != textype))
        {
-               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.textype = textype;
 
                if (r_fb.screentexturewidth && r_fb.screentextureheight)
@@ -6405,171 +6335,71 @@ static void R_Bloom_StartFrame(void)
                        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.ghosttexture_valid = false;
-                       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 (useviewfbo)
-                       {
-                               r_fb.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "framebufferdepth", r_fb.screentexturewidth, r_fb.screentextureheight, TEXTYPE_DEPTHBUFFER24STENCIL8);
-                               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);
-                       }
-               }
-
-               if (r_fb.bloomtexturewidth && r_fb.bloomtextureheight)
-               {
-                       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 (useviewfbo)
-                                       r_fb.bloomfbo[i] = R_Mesh_CreateFramebufferObject(NULL, r_fb.bloomtexture[i], NULL, NULL, NULL);
-                       }
                }
        }
 
-       // bloom texture is a different resolution
-       r_fb.bloomwidth = bound(1, r_bloom_resolution.integer, r_refdef.view.width);
-       r_fb.bloomheight = r_fb.bloomwidth * r_refdef.view.height / r_refdef.view.width;
-       r_fb.bloomheight = bound(1, r_fb.bloomheight, r_refdef.view.height);
-       r_fb.bloomwidth = bound(1, r_fb.bloomwidth, r_fb.bloomtexturewidth);
-       r_fb.bloomheight = bound(1, r_fb.bloomheight, r_fb.bloomtextureheight);
-
-       // set up a texcoord array for the full resolution screen image
-       // (we have to keep this around to copy back during final render)
-       r_fb.screentexcoord2f[0] = 0;
-       r_fb.screentexcoord2f[1] = 1.0f;
-       r_fb.screentexcoord2f[2] = (float)viewwidth     / (float)r_fb.screentexturewidth;
-       r_fb.screentexcoord2f[3] = 1.0f;
-       r_fb.screentexcoord2f[4] = (float)viewwidth     / (float)r_fb.screentexturewidth;
-       r_fb.screentexcoord2f[5] = 1.0f - (float)viewheight / (float)r_fb.screentextureheight;
-       r_fb.screentexcoord2f[6] = 0;
-       r_fb.screentexcoord2f[7] = 1.0f - (float)viewheight / (float)r_fb.screentextureheight;
-
-       // set up a texcoord array for the reduced resolution bloom image
-       // (which will be additive blended over the screen image)
-       r_fb.bloomtexcoord2f[0] = 0;
-       r_fb.bloomtexcoord2f[1] = (float)r_fb.bloomheight / (float)r_fb.bloomtextureheight;
-       r_fb.bloomtexcoord2f[2] = (float)r_fb.bloomwidth  / (float)r_fb.bloomtexturewidth;
-       r_fb.bloomtexcoord2f[3] = (float)r_fb.bloomheight / (float)r_fb.bloomtextureheight;
-       r_fb.bloomtexcoord2f[4] = (float)r_fb.bloomwidth  / (float)r_fb.bloomtexturewidth;
-       r_fb.bloomtexcoord2f[5] = 0;
-       r_fb.bloomtexcoord2f[6] = 0;
-       r_fb.bloomtexcoord2f[7] = 0;
-
-       switch(vid.renderpath)
+       if (r_bloom.integer)
        {
-       case RENDERPATH_GL11:
-       case RENDERPATH_GL13:
-       case RENDERPATH_GL20:
-       case RENDERPATH_SOFT:
-       case RENDERPATH_GLES1:
-       case RENDERPATH_GLES2:
-               break;
-       case RENDERPATH_D3D9:
-       case RENDERPATH_D3D10:
-       case RENDERPATH_D3D11:
-               for (i = 0;i < 4;i++)
-               {
-                       r_fb.screentexcoord2f[i*2+0] += 0.5f / (float)r_fb.screentexturewidth;
-                       r_fb.screentexcoord2f[i*2+1] += 0.5f / (float)r_fb.screentextureheight;
-                       r_fb.bloomtexcoord2f[i*2+0] += 0.5f / (float)r_fb.bloomtexturewidth;
-                       r_fb.bloomtexcoord2f[i*2+1] += 0.5f / (float)r_fb.bloomtextureheight;
-               }
-               break;
+               // bloom texture is a different resolution
+               r_fb.bloomwidth = bound(1, r_bloom_resolution.integer, r_refdef.view.width);
+               r_fb.bloomheight = r_fb.bloomwidth * r_refdef.view.height / r_refdef.view.width;
+               r_fb.bloomheight = bound(1, r_fb.bloomheight, r_refdef.view.height);
        }
+       else
+               r_fb.bloomwidth = r_fb.bloomheight = 0;
 
-       R_Viewport_InitOrtho(&r_fb.bloomviewport, &identitymatrix, r_fb.fbo ? 0 : r_refdef.view.x, r_fb.fbo ? 0 : r_refdef.view.y, r_fb.bloomwidth, r_fb.bloomheight, 0, 0, 1, 1, -10, 100, NULL);
+       r_fb.rt_screen = R_RenderTarget_Get(screentexturewidth, screentextureheight, TEXTYPE_DEPTHBUFFER24STENCIL8, true, textype, TEXTYPE_UNUSED, TEXTYPE_UNUSED, TEXTYPE_UNUSED);
 
-       if (r_fb.fbo)
-               r_refdef.view.clear = true;
+       r_refdef.view.clear = true;
 }
 
 static void R_Bloom_MakeTexture(void)
 {
        int x, range, dir;
        float xoffset, yoffset, r, brighten;
-       rtexture_t *intex;
        float colorscale = r_bloom_colorscale.value;
+       r_viewport_t bloomviewport;
+       r_rendertarget_t *prev, *cur;
+       textype_t textype = r_fb.rt_screen->colortextype[0];
 
        r_refdef.stats[r_stat_bloom]++;
-    
-#if 0
-    // this copy is unnecessary since it happens in R_BlendView already
-       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[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
-       }
-#endif
+
+       R_Viewport_InitOrtho(&bloomviewport, &identitymatrix, 0, 0, r_fb.bloomwidth, r_fb.bloomheight, 0, 0, 1, 1, -10, 100, NULL);
 
        // scale down screen texture to the bloom texture size
        CHECKGLERROR
-       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);
+       prev = r_fb.rt_screen;
+       cur = R_RenderTarget_Get(r_fb.bloomwidth, r_fb.bloomheight, TEXTYPE_UNUSED, false, textype, TEXTYPE_UNUSED, TEXTYPE_UNUSED, TEXTYPE_UNUSED);
+       R_Mesh_SetRenderTargets(cur->fbo, NULL, cur->colortexture[0], NULL, NULL, NULL);
+       R_SetViewport(&bloomviewport);
        GL_CullFace(GL_NONE);
        GL_DepthTest(false);
        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...
-       switch(vid.renderpath)
-       {
-       case RENDERPATH_GL11:
-       case RENDERPATH_GL13:
-       case RENDERPATH_GL20:
-       case RENDERPATH_GLES1:
-       case RENDERPATH_GLES2:
-       case RENDERPATH_SOFT:
-               R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.screentexcoord2f);
-               break;
-       case RENDERPATH_D3D9:
-       case RENDERPATH_D3D10:
-       case RENDERPATH_D3D11:
-               R_Mesh_PrepareVertices_Generic_Arrays(4, r_d3dscreenvertex3f, NULL, r_fb.screentexcoord2f);
-               break;
-       }
+       R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, prev->texcoord2f);
        // TODO: do boxfilter scale-down in shader?
-       R_SetupShader_Generic(r_fb.colortexture, NULL, GL_MODULATE, 1, false, true, true);
+       R_SetupShader_Generic(prev->colortexture[0], NULL, GL_MODULATE, 1, false, true, true);
        R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
        r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight;
-
        // 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[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
-       }
 
-       // multiply bloom image by itself as many times as desired
+       // multiply bloom image by itself as many times as desired to darken it
+       // TODO: if people actually use this it could be done more quickly in the previous shader pass
        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);
+               prev = cur;
+               cur = R_RenderTarget_Get(r_fb.bloomwidth, r_fb.bloomheight, TEXTYPE_UNUSED, false, textype, TEXTYPE_UNUSED, TEXTYPE_UNUSED, TEXTYPE_UNUSED);
+               R_Mesh_SetRenderTargets(cur->fbo, NULL, cur->colortexture[0], NULL, NULL, NULL);
                x *= 2;
                r = bound(0, r_bloom_colorexponent.value / x, 1); // always 0.5 to 1
-               if (!r_fb.bloomfbo[r_fb.bloomindex])
-               {
-                       GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR); // square it and multiply by two
-                       GL_Color(r,r,r,1); // apply fix factor
-               }
-               else
-               {
-                       if(x <= 2)
-                               GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 128);
-                       GL_BlendFunc(GL_SRC_COLOR, GL_ZERO); // square it
-                       GL_Color(1,1,1,1); // no fix factor supported here
-               }
-               R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.bloomtexcoord2f);
-               R_SetupShader_Generic(intex, NULL, GL_MODULATE, 1, false, true, false);
+               if(x <= 2)
+                       GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 128);
+               GL_BlendFunc(GL_SRC_COLOR, GL_ZERO); // square it
+               GL_Color(1,1,1,1); // no fix factor supported here
+               R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, prev->texcoord2f);
+               R_SetupShader_Generic(prev->colortexture[0], NULL, GL_MODULATE, 1, false, true, false);
                R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
                r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight;
-
-               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[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
-               }
        }
 
        range = r_bloom_blur.integer * r_fb.bloomwidth / 320;
@@ -6580,29 +6410,29 @@ static void R_Bloom_MakeTexture(void)
 
        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);
+               prev = cur;
+               cur = R_RenderTarget_Get(r_fb.bloomwidth, r_fb.bloomheight, TEXTYPE_UNUSED, false, textype, TEXTYPE_UNUSED, TEXTYPE_UNUSED, TEXTYPE_UNUSED);
+               R_Mesh_SetRenderTargets(cur->fbo, NULL, cur->colortexture[0], 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, false);
+               R_SetupShader_Generic(prev->colortexture[0], NULL, GL_MODULATE, 1, false, true, false);
                for (x = -range;x <= range;x++)
                {
                        if (!dir){xoffset = 0;yoffset = x;}
                        else {xoffset = x;yoffset = 0;}
-                       xoffset /= (float)r_fb.bloomtexturewidth;
-                       yoffset /= (float)r_fb.bloomtextureheight;
+                       xoffset /= (float)prev->texturewidth;
+                       yoffset /= (float)prev->textureheight;
                        // compute a texcoord array with the specified x and y offset
-                       r_fb.offsettexcoord2f[0] = xoffset+r_fb.bloomtexcoord2f[0];
-                       r_fb.offsettexcoord2f[1] = yoffset+r_fb.bloomtexcoord2f[1];
-                       r_fb.offsettexcoord2f[2] = xoffset+r_fb.bloomtexcoord2f[2];
-                       r_fb.offsettexcoord2f[3] = yoffset+r_fb.bloomtexcoord2f[3];
-                       r_fb.offsettexcoord2f[4] = xoffset+r_fb.bloomtexcoord2f[4];
-                       r_fb.offsettexcoord2f[5] = yoffset+r_fb.bloomtexcoord2f[5];
-                       r_fb.offsettexcoord2f[6] = xoffset+r_fb.bloomtexcoord2f[6];
-                       r_fb.offsettexcoord2f[7] = yoffset+r_fb.bloomtexcoord2f[7];
+                       r_fb.offsettexcoord2f[0] = xoffset+prev->texcoord2f[0];
+                       r_fb.offsettexcoord2f[1] = yoffset+prev->texcoord2f[1];
+                       r_fb.offsettexcoord2f[2] = xoffset+prev->texcoord2f[2];
+                       r_fb.offsettexcoord2f[3] = yoffset+prev->texcoord2f[3];
+                       r_fb.offsettexcoord2f[4] = xoffset+prev->texcoord2f[4];
+                       r_fb.offsettexcoord2f[5] = yoffset+prev->texcoord2f[5];
+                       r_fb.offsettexcoord2f[6] = xoffset+prev->texcoord2f[6];
+                       r_fb.offsettexcoord2f[7] = yoffset+prev->texcoord2f[7];
                        // this r value looks like a 'dot' particle, fading sharply to
                        // black at the edges
                        // (probably not realistic but looks good enough)
@@ -6610,27 +6440,27 @@ static void R_Bloom_MakeTexture(void)
                        //r = brighten/(range*2+1);
                        r = brighten / (range * 2 + 1);
                        if(range >= 1)
-                               r *= (1 - x*x/(float)(range*range));
+                               r *= (1 - x*x/(float)((range+1)*(range+1)));
+                       if (r <= 0)
+                               continue;
                        GL_Color(r, r, r, 1);
                        R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.offsettexcoord2f);
                        R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
                        r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight;
                        GL_BlendFunc(GL_ONE, GL_ONE);
                }
-
-               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[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
-               }
        }
+
+       // now we have the bloom image, so keep track of it
+       r_fb.rt_bloom = cur;
 }
 
 static void R_BlendView(int viewfbo, rtexture_t *viewdepthtexture, rtexture_t *viewcolortexture, int viewx, int viewy, int viewwidth, int viewheight)
 {
        dpuint64 permutation;
        float uservecs[4][4];
+       rtexture_t *viewtexture;
+       rtexture_t *bloomtexture;
 
        R_EntityMatrix(&identitymatrix);
 
@@ -6643,113 +6473,74 @@ static void R_BlendView(int viewfbo, rtexture_t *viewdepthtexture, rtexture_t *v
        case RENDERPATH_SOFT:
        case RENDERPATH_GLES2:
                permutation =
-                         (r_fb.bloomtexture[r_fb.bloomindex] ? SHADERPERMUTATION_BLOOM : 0)
+                         (r_fb.bloomwidth ? SHADERPERMUTATION_BLOOM : 0)
                        | (r_refdef.viewblend[3] > 0 ? SHADERPERMUTATION_VIEWTINT : 0)
                        | (!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.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[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
-                       }
-
-                       if(!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0) && r_fb.ghosttexture)
-                       {
-                               // declare variables
-                               float blur_factor, blur_mouseaccel, blur_velocity;
-                               static float blur_average; 
-                               static vec3_t blur_oldangles; // used to see how quickly the mouse is moving
-
-                               // set a goal for the factoring
-                               blur_velocity = bound(0, (VectorLength(cl.movement_velocity) - r_motionblur_velocityfactor_minspeed.value) 
-                                       / max(1, r_motionblur_velocityfactor_maxspeed.value - r_motionblur_velocityfactor_minspeed.value), 1);
-                               blur_mouseaccel = bound(0, ((fabs(VectorLength(cl.viewangles) - VectorLength(blur_oldangles)) * 10) - r_motionblur_mousefactor_minspeed.value) 
-                                       / max(1, r_motionblur_mousefactor_maxspeed.value - r_motionblur_mousefactor_minspeed.value), 1);
-                               blur_factor = ((blur_velocity * r_motionblur_velocityfactor.value) 
-                                       + (blur_mouseaccel * r_motionblur_mousefactor.value));
-
-                               // 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
-                               cl.motionbluralpha = 1 - exp(-
-                                               (
-                                                (r_motionblur.value * blur_factor / 80)
-                                                +
-                                                (r_damageblur.value * (cl.cshifts[CSHIFT_DAMAGE].percent / 1600))
-                                               )
-                                               /
-                                               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
-                               R_ResetViewRendering2D(viewfbo, viewdepthtexture, viewcolortexture, viewx, viewy, viewwidth, viewheight);
-                               if (cl.motionbluralpha > 0 && !r_refdef.envmap && r_fb.ghosttexture_valid)
-                               {
-                                       GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-                                       GL_Color(1, 1, 1, cl.motionbluralpha);
-                                       switch(vid.renderpath)
-                                       {
-                                       case RENDERPATH_GL11:
-                                       case RENDERPATH_GL13:
-                                       case RENDERPATH_GL20:
-                                       case RENDERPATH_GLES1:
-                                       case RENDERPATH_GLES2:
-                                       case RENDERPATH_SOFT:
-                                               R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.screentexcoord2f);
-                                               break;
-                                       case RENDERPATH_D3D9:
-                                       case RENDERPATH_D3D10:
-                                       case RENDERPATH_D3D11:
-                                               R_Mesh_PrepareVertices_Generic_Arrays(4, r_d3dscreenvertex3f, NULL, r_fb.screentexcoord2f);
-                                               break;
-                                       }
-                                       R_SetupShader_Generic(r_fb.ghosttexture, NULL, GL_MODULATE, 1, false, true, true);
-                                       R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
-                                       r_refdef.stats[r_stat_bloom_drawpixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
-                               }
-
-                               // updates old view angles for next pass
-                               VectorCopy(cl.viewangles, blur_oldangles);
-
-                               // copy view into the ghost texture
-                               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[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
-                               r_fb.ghosttexture_valid = true;
-                       }
-               }
-               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))
+               if(r_refdef.view.ismain && !R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0) && r_fb.ghosttexture)
+               {
+                       // declare variables
+                       float blur_factor, blur_mouseaccel, blur_velocity;
+                       static float blur_average; 
+                       static vec3_t blur_oldangles; // used to see how quickly the mouse is moving
+
+                       // set a goal for the factoring
+                       blur_velocity = bound(0, (VectorLength(cl.movement_velocity) - r_motionblur_velocityfactor_minspeed.value) 
+                               / max(1, r_motionblur_velocityfactor_maxspeed.value - r_motionblur_velocityfactor_minspeed.value), 1);
+                       blur_mouseaccel = bound(0, ((fabs(VectorLength(cl.viewangles) - VectorLength(blur_oldangles)) * 10) - r_motionblur_mousefactor_minspeed.value) 
+                               / max(1, r_motionblur_mousefactor_maxspeed.value - r_motionblur_mousefactor_minspeed.value), 1);
+                       blur_factor = ((blur_velocity * r_motionblur_velocityfactor.value) 
+                               + (blur_mouseaccel * r_motionblur_mousefactor.value));
+
+                       // 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
+                       cl.motionbluralpha = 1 - exp(-
+                                       (
+                                               (r_motionblur.value * blur_factor / 80)
+                                               +
+                                               (r_damageblur.value * (cl.cshifts[CSHIFT_DAMAGE].percent / 1600))
+                                       )
+                                       /
+                                       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
+                       R_ResetViewRendering2D(viewfbo, viewdepthtexture, viewcolortexture, viewx, viewy, viewwidth, viewheight);
+                       if (cl.motionbluralpha > 0 && !r_refdef.envmap && r_fb.ghosttexture_valid)
                        {
-                               // apply a color tint to the whole view
-                               R_ResetViewRendering2D(0, NULL, NULL, viewx, viewy, viewwidth, viewheight);
-                               GL_Color(r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]);
-                               R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, NULL);
-                               R_SetupShader_Generic_NoTexture(false, true);
                                GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+                               GL_Color(1, 1, 1, cl.motionbluralpha);
+                               R_CalcTexCoordsForView(0, 0, viewwidth, viewheight, viewwidth, viewheight, r_fb.ghosttexcoord2f);
+                               R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.ghosttexcoord2f);
+                               R_SetupShader_Generic(r_fb.ghosttexture, NULL, GL_MODULATE, 1, false, true, true);
                                R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
+                               r_refdef.stats[r_stat_bloom_drawpixels] += viewwidth * viewheight;
                        }
-                       break; // no screen processing, no bloom, skip it
+
+                       // updates old view angles for next pass
+                       VectorCopy(cl.viewangles, blur_oldangles);
+
+                       // copy view into the ghost texture
+                       R_Mesh_CopyToTexture(r_fb.ghosttexture, 0, 0, viewx, viewy, viewwidth, viewheight);
+                       r_refdef.stats[r_stat_bloom_copypixels] += viewwidth * viewheight;
+                       r_fb.ghosttexture_valid = true;
                }
 
-               if (r_fb.bloomtexture[0])
+               if (r_fb.bloomwidth)
                {
                        // make the bloom texture
                        R_Bloom_MakeTexture();
@@ -6773,14 +6564,27 @@ static void R_BlendView(int viewfbo, rtexture_t *viewdepthtexture, rtexture_t *v
                GL_Color(1, 1, 1, 1);
                GL_BlendFunc(GL_ONE, GL_ZERO);
 
+               viewtexture = r_fb.rt_screen->colortexture[0];
+               bloomtexture = r_fb.rt_bloom ? r_fb.rt_bloom->colortexture[0] : NULL;
+
+               if (r_rendertarget_debug.integer >= 0)
+               {
+                       r_rendertarget_t *rt = (r_rendertarget_t *)Mem_ExpandableArray_RecordAtIndex(&r_fb.rendertargets, r_rendertarget_debug.integer);
+                       if (rt && rt->colortexture[0])
+                       {
+                               viewtexture = rt->colortexture[0];
+                               bloomtexture = NULL;
+                       }
+               }
+
+               R_Mesh_PrepareVertices_Mesh_Arrays(4, r_screenvertex3f, NULL, NULL, NULL, NULL, r_fb.rt_screen->texcoord2f, bloomtexture ? r_fb.rt_bloom->texcoord2f : NULL);
                switch(vid.renderpath)
                {
                case RENDERPATH_GL20:
                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.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_First           >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First     , viewtexture);
+                       if (r_glsl_permutation->tex_Texture_Second          >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second    , bloomtexture);
                        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);
@@ -6794,11 +6598,9 @@ static void R_BlendView(int viewfbo, rtexture_t *viewdepthtexture, rtexture_t *v
                        break;
                case RENDERPATH_D3D9:
 #ifdef SUPPORTD3D
-                       // 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.colortexture);
-                       R_Mesh_TexBind(GL20TU_SECOND    , r_fb.bloomtexture[r_fb.bloomindex]);
+                       R_Mesh_TexBind(GL20TU_FIRST     , viewtexture);
+                       R_Mesh_TexBind(GL20TU_SECOND    , 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);
@@ -6818,10 +6620,9 @@ static void R_BlendView(int viewfbo, rtexture_t *viewdepthtexture, rtexture_t *v
                        Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
                        break;
                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.colortexture);
-                       R_Mesh_TexBind(GL20TU_SECOND    , r_fb.bloomtexture[r_fb.bloomindex]);
+                       R_Mesh_TexBind(GL20TU_FIRST     , viewtexture);
+                       R_Mesh_TexBind(GL20TU_SECOND    , bloomtexture);
                        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);
@@ -7151,7 +6952,7 @@ void R_RenderView(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, i
                r_fb.water.enabled = false;
                r_fb.water.numwaterplanes = 0;
 
-               R_RenderScene(fbo, depthtexture, colortexture, x, y, width, height);
+               R_RenderScene(0, NULL, NULL, r_refdef.view.x, r_refdef.view.y, r_refdef.view.width, r_refdef.view.height);
 
                r_refdef.view.matrix = originalmatrix;
 
@@ -7176,19 +6977,19 @@ void R_RenderView(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, i
 
        R_Shadow_UpdateWorldLightSelection();
 
-       // this will set up r_fb.fbo
+       // this will set up r_fb.rt_screen
        R_Bloom_StartFrame();
 
        // apply bloom brightness offset
-       if(r_fb.bloomtexture[0])
+       if(r_fb.rt_bloom)
                r_refdef.view.colorscale *= r_bloom_scenebrightness.value;
 
        // R_Bloom_StartFrame probably set up an fbo for us to render into, it will be rendered to the window later in R_BlendView
-       if (r_fb.fbo)
+       if (r_fb.rt_screen)
        {
-               viewfbo = r_fb.fbo;
-               viewdepthtexture = r_fb.depthtexture;
-               viewcolortexture = r_fb.colortexture;
+               viewfbo = r_fb.rt_screen->fbo;
+               viewdepthtexture = r_fb.rt_screen->depthtexture;
+               viewcolortexture = r_fb.rt_screen->colortexture[0];
                viewx = 0;
                viewy = 0;
                viewwidth = width;
@@ -7205,7 +7006,7 @@ void R_RenderView(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, i
 
        // clear the whole fbo every frame - otherwise the driver will consider
        // it to be an inter-frame texture and stall in multi-gpu configurations
-       if (viewfbo)
+       if (r_fb.rt_screen)
                GL_ScissorTest(false);
        R_ClearScreen(r_refdef.fogenabled);
        if (r_timereport_active)
@@ -7233,7 +7034,7 @@ void R_RenderView(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, i
 
        // for the actual view render we use scissoring a fair amount, so scissor
        // test needs to be on
-       if (viewfbo)
+       if (r_fb.rt_screen)
                GL_ScissorTest(true);
        GL_Scissor(viewx, viewy, viewwidth, viewheight);
        R_RenderScene(viewfbo, viewdepthtexture, viewcolortexture, viewx, viewy, viewwidth, viewheight);
@@ -7242,8 +7043,6 @@ void R_RenderView(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, i
        // postprocess uses textures that are not aligned with the viewport we're rendering, so no scissoring
        GL_ScissorTest(false);
 
-       // R_BlendView will render the viewfbo image into the provided fbo using
-       // the postprocess shader (including gamma correction and sRGB)
        R_BlendView(fbo, depthtexture, colortexture, x, y, width, height);
        if (r_timereport_active)
                R_TimeReport("blendview");
index e06f035..11dc7ad 100644 (file)
@@ -39,6 +39,9 @@
 
 typedef enum textype_e
 {
+       // placeholder for unused textures in r_rendertarget_t
+       TEXTYPE_UNUSED,
+
        // 8bit paletted
        TEXTYPE_PALETTE,
        // 32bit RGBA
index 81f3a54..5ae6304 100644 (file)
--- a/render.h
+++ b/render.h
@@ -501,14 +501,41 @@ void R_SetupShader_DepthOrShadow(qboolean notrippy, qboolean depthrgb, qboolean
 void R_SetupShader_Surface(const float ambientcolor[3], const float diffusecolor[3], const float specularcolor[3], rsurfacepass_t rsurfacepass, int texturenumsurfaces, const msurface_t **texturesurfacelist, void *waterplane, qboolean notrippy);
 void R_SetupShader_DeferredLight(const rtlight_t *rtlight);
 
+typedef struct r_rendertarget_s {
+       // texcoords for sampling from the viewport (clockwise: 0,0 1,0 1,1 0,1)
+       float texcoord2f[8];
+       // textures are this size and type
+       int texturewidth;
+       int textureheight;
+       // TEXTYPE for each color target - usually TEXTYPE_COLORBUFFER16F
+       textype_t colortextype[4];
+       // TEXTYPE for depth target - usually TEXTYPE_DEPTHBUFFER24 or TEXTYPE_SHADOWMAP24_COMP
+       textype_t depthtextype;
+       // if true the depth target will be a renderbuffer rather than a texture (still rtexture_t though)
+       qboolean depthisrenderbuffer;
+       // framebuffer object referencing the textures
+       int fbo;
+       // there can be up to 4 color targets and 1 depth target, the depthtexture
+       // may be a real texture (readable) or just a renderbuffer (not readable,
+       // but potentially faster)
+       rtexture_t *colortexture[4];
+       rtexture_t *depthtexture;
+       // a rendertarget will not be reused in the same frame (realtime == lastusetime),
+       // on a new frame, matching rendertargets will be reused (texturewidth, textureheight, number of color and depth textures and their types),
+       // when a new frame arrives the rendertargets can be reused by requests for matching texturewidth,textureheight and fbo configuration (the number of color and depth textures), when a rendertarget is not reused for > 200ms (realtime - lastusetime > 0.2) the rendertarget's resources will be freed (fbo, textures) and it can be reused for any target in future frames
+       double lastusetime;
+} r_rendertarget_t;
+
+// called each frame after render to delete render targets that have not been used for a while
+void R_RenderTarget_FreeUnused(qboolean force);
+// returns a rendertarget, creates rendertarget if needed or intelligently reuses targets across frames if they match and have not been used already this frame
+r_rendertarget_t *R_RenderTarget_Get(int texturewidth, int textureheight, textype_t depthtextype, qboolean depthisrenderbuffer, textype_t colortextype0, textype_t colortextype1, textype_t colortextype2, textype_t colortextype3);
+
 typedef struct r_waterstate_waterplane_s
 {
-       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;
+       r_rendertarget_t *rt_refraction; // MATERIALFLAG_WATERSHADER or MATERIALFLAG_REFRACTION
+       r_rendertarget_t *rt_reflection; // MATERIALFLAG_WATERSHADER or MATERIALFLAG_REFLECTION
+       r_rendertarget_t *rt_camera; // MATERIALFLAG_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
@@ -523,7 +550,6 @@ 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;
@@ -542,30 +568,27 @@ r_waterstate_t;
 typedef struct r_framebufferstate_s
 {
        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
 
-       rtexture_t *colortexture; // non-NULL if fbo is non-zero
-       rtexture_t *depthtexture; // non-NULL if fbo is non-zero
+       // rt_* fields are per-RenderView so we reset them in R_Bloom_StartFrame
+       r_rendertarget_t *rt_screen;
+       r_rendertarget_t *rt_bloom;
+
        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
+       float ghosttexcoord2f[8]; // for r_motionblur
 
        int bloomwidth, bloomheight;
-       int bloomtexturewidth, bloomtextureheight;
 
        // arrays for rendering the screen passes
-       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 bloomviewport;
-
        r_waterstate_t water;
 
        qboolean ghosttexture_valid; // don't draw garbage on first frame with motionblur
        qboolean usedepthtextures; // use depth texture instead of depth renderbuffer (faster if you need to read it later anyway)
+
+       // rendertargets (fbo and viewport), these can be reused across frames
+       memexpandablearray_t rendertargets;
 }
 r_framebufferstate_t;
 
index 26b69ad..0c9ee5e 100644 (file)
@@ -1148,7 +1148,7 @@ void VID_CheckExtensions(void)
        vid.texunits = vid.teximageunits = vid.texarrayunits = 1;
        if (vid.support.arb_multitexture)
                qglGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint*)&vid.texunits);
-       if (vid_gl20.integer && vid.support.gl20shaders)
+       if (vid_gl20.integer && vid.support.gl20shaders && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two)
        {
                qglGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint*)&vid.texunits);
                qglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, (int *)&vid.teximageunits);CHECKGLERROR
@@ -1156,7 +1156,7 @@ void VID_CheckExtensions(void)
                vid.texunits = bound(4, vid.texunits, MAX_TEXTUREUNITS);
                vid.teximageunits = bound(16, vid.teximageunits, MAX_TEXTUREUNITS);
                vid.texarrayunits = bound(8, vid.texarrayunits, MAX_TEXTUREUNITS);
-               Con_DPrintf("Using GL2.0 rendering path - %i texture matrix, %i texture images, %i texcoords%s\n", vid.texunits, vid.teximageunits, vid.texarrayunits, vid.support.ext_framebuffer_object ? ", shadowmapping supported" : "");
+               Con_DPrintf("Using GL2.0+fbo rendering path - %i texture matrix, %i texture images, %i texcoords%s\n", vid.texunits, vid.teximageunits, vid.texarrayunits, vid.support.ext_framebuffer_object ? ", shadowmapping supported" : "");
                vid.renderpath = RENDERPATH_GL20;
                vid.sRGBcapable2D = false;
                vid.sRGBcapable3D = true;
@@ -1813,7 +1813,7 @@ static int VID_Mode(int fullscreen, int width, int height, int bpp, float refres
                        (vid_sRGB_fallback.integer >= 3) // force fallback
                        ||
                        (vid_sRGB_fallback.integer >= 2 && // fallback if framebuffer is 8bit
-                               !(r_viewfbo.integer >= 2 && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2))
+                               r_viewfbo.integer < 2)
                )
                        vid.sRGB2D = vid.sRGB3D = false;