Introduction
This blog post will look at how to calculate overdraw using the stencil buffer and why, in my case, I wish to do so.
For my dissertation project I am conducting a study to find a ‘best-fit’ approach to rendering transparency in deferred shading. Deferred shading, in brief, is an alternative to forward shading where instead of shading each fragment with all the lights in the scene we store fragment data in an intermediate buffer (the G-Buffer). Each light is applied to the scene as a geometric entity (full-screen quad for global lighting, cone frustum for spotlight). Doing so decouples the rendering of geometry from the shading of fragments, lending a linear cost to each dynamic light as opposed to exponential in forward shading.
Importantly the data for only one surface is stored at each fragment; this means fragments that would eventually be occluded by fragments with lower Z depth are never shaded, preventing wasted GPU computation. Where a previously shaded fragment is replaced by another, this is called overdraw. It represents a problem in forward shading where, without complex depth sorting, many wasted calculations are made. When you begin to render transparency in deferred shading we don’t replace the fragment data entirely but we do have to blend, this in itself is an expensive process.
So in order to draw solid results from my project I have opted to account for the impact of overdraw. To do so I am using the stencil buffer to track how many times a fragment is rendered to and subsequently analysing that data.
Method
Presuming the stencil buffer is allocated and attached to the framebuffer; clear it with 0′s and enable the stencil test. We set the stencil test to pass every time with GL_ALWAYS. Then set the stencil operations; keeping the current value on stencil fail, and incrementing (without saturation) on Z pass AND fail.
glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS,0,0);// Always pass the test glStencilOp( GL_KEEP,// If it fails stencil test keep the current value GL_INCR,// If it fails depth test increment with saturation GL_INCR);// If it passes depth test increment with saturation // Render your scene glDisable(GL_STENCIL_TEST);
Analysis
For the purposes of my project I require quantitative data that can be recorded. Some developers redraw the scene based on stencil values [1]. For my analysis I read out the stencil buffer in to application memory using the following code;
GLuint* stencilBuffer = (GLuint*)malloc(sizeof(GLuint)*(ScreenWidth*ScreenHeight)); glReadPixels(0,0,ScreenWidth,ScreenHeight,GL_STENCIL_INDEX,GL_UNSIGNED_INT,stencilBuffer);
I will be using a percentage to analyse the amount of overdraw. This can be found by dividing the number of pixels that have been drawn to multiple times by the total number of pixels in the framebuffer or alternatively the total number of pixels drawn to in the frame.
const int NUM_PIXELS = ScreenWidth * ScreenHeight;
unsigned int total_pixels_drawn = 0;
unsigned int num_pixels_overdrawn = 0;
for(int i=0; i<NUM_PIXELS; ++i)
{
GLuint curr_pix = buffer[i];
if(curr_pix > 0)
{
total_pixels_drawn++;
}
if(curr_pix > 1)
{
num_pixels_overdrawn++;
}
}
float overdraw = (num_pixels_overdrawn / static_cast<float>(NUM_PIXELS)) * 100.0f;
Method
I hope you found this post useful, if you have any questions or feedback please get in touch. I am always happy to discuss my work. Upcoming posts will include the low down on deferred shading to give a little context to the brief explanation above and perhaps some further discussions on other projects I have going on ( 3D morphing for one :S ).
Thanks for reading!
Russ
References
[1] http://excamera.com/articles/2/index.html