I ran into a problem with my 2D Deferred Lighting implementation and I hope you can help me.
For the 2D game I'm currently programming I decided to go with Deferred Lighting, because it is supposed to be easy to implement and fast.
I used this tutorial as a starting point and adapted it to my needs.
It's all working fine and looks amazing except that I get a sever drop in framerate as soon as I enable the lighting.
From about 2500 FPS (CPU-Bound/ 80% GPU load) it falls down to 1000 FPS(GPU-Bound/70% CPU load). Of course 1000 FPS is still quite high,
but considering my system (and the 2500 FPS without lighting) it's actually not that much.
I did some profiling and testing and it showed, that about 50% of processor time is spend in EndDraw(), this usually means, that the graphics card is overstrained and needs some time to catch up. I can't explain why that is, because after all I'm just rendering 9 lights to a 1280x720 texture and I've seen Deferred Rendering Engines with over 100 active lights.
This is how my code currently looks like:
Rendering:
private int RenderLights(List<Light> _lights, ref RenderTarget2D _lightingRT)
{
int lightsRendered = 0;
Device.BlendState = BlendState.Additive;
Device.SetRenderTarget(_lightingRT);
Device.Clear(Color.Black);
// For every light inside the current scene
foreach (Light light in _lights)
{
//simple early out condition (rough not in sight or not active)
if ((light.Position - (Graphics.Instance.MainCamera.Position + Graphics.Instance.Resolution / 2)).Length() >
(Graphics.Instance.Resolution / 2).Length() + light.Distance || !light.IsActive)
continue;
lightEffect.CurrentTechnique = lightEffect.Techniques["DeferredLight"];
//Set light parameters
lightEffect.Parameters["lightStrength"].SetValue(light.Intensity);
lightEffect.Parameters["lightColor"].SetValue(light.Color.ToVector3());
lightEffect.Parameters["lightRadius"].SetValue(light.Distance);
if (light is Spotlight)
{
lightEffect.Parameters["isSpotlight"].SetValue(true);
lightEffect.Parameters["angleCos"].SetValue((float)Math.Cos((light as Spotlight).Angle));
lightEffect.Parameters["lightNormal"].SetValue( Vector2.Transform(new Vector2(1, 0),
Matrix.CreateRotationZ(light.gameObject.transform.Rotation)));
}
else
lightEffect.Parameters["isSpotlight"].SetValue(false);
lightEffect.Parameters["screenWidth"].SetValue(Graphics.Instance.Resolution.X);
lightEffect.Parameters["screenHeight"].SetValue(Graphics.Instance.Resolution.Y);
lightEffect.Parameters["lightPosition"].SetValue(Graphics.Instance.MainCamera.WorldToScreen(light.Position));
//Apply Pass
lightEffect.CurrentTechnique.Passes[0].Apply();
// Draw the full screen Quad
Device.SetVertexBuffer(vertexBuffer);
Device.DrawUserPrimitives(PrimitiveType.TriangleStrip, vertices, 0, 2);
lightsRendered++;
}
// Deactivate alpha blending
Device.BlendState = BlendState.Opaque;
// Deactive the rander targets to resolve them
//Device.SetRenderTarget(null);
return lightsRendered;
}
And the shader:
float screenWidth;
float screenHeight;
float lightStrength;
float lightRadius;
float2 lightPosition;
float3 lightColor;
bool isSpotlight;
float angleCos;
float2 lightNormal;
void VertexToPixelShader(inout float2 texCoord: TEXCOORD0, inout float4 Position : POSITION)
{
}
float4 LightShader(float2 TexCoord : TEXCOORD0) : COLOR0
{
float2 pixelPosition;
pixelPosition.x = screenWidth * TexCoord.x;
pixelPosition.y = screenHeight * TexCoord.y;
float3 shading;
float2 lightDirection = pixelPosition - lightPosition;
float distance = length(lightPosition - pixelPosition);
//early out if the pixel is out of range
if(distance > lightRadius)
{
return float4(0,0,0,0);
}
float coneAttenuation = saturate(1.0f - distance / lightRadius);
if(isSpotlight)
{
float dotP = dot(lightNormal, normalize(lightDirection));
coneAttenuation *= saturate(dotP - ((1-dotP)/(1-angleCos) * angleCos));
}
shading = pow(coneAttenuation, 2) * lightColor * lightStrength;
return float4(shading.r, shading.g, shading.b, 1.0f);
}
technique DeferredLight
{
pass Pass1
{
VertexShader = compile vs_2_0 VertexToPixelShader();
PixelShader = compile ps_2_0 LightShader();
}
}
My first guess was, that the lighting shader is just too slow, so I made it to instantly return solld black. But that didn't change anything. The framerate stayed exactly the same.
I discovered that as soon as I don't apply the shader's pass (comment "lightEffect.CurrentTechnique.Passes[0].Apply();" out) it runs fine, but of course without the lighting.
I even did some profiling with PIX and looked at the time one light draw call takes. With my own shader, which just returns black it were about
200,000 ns, with the default one 130,000 ns. That difference could explain why the framerate drops, but I don't understand, why the default shader is so much faster than just returning black.
Does anyone have an idea of what might causing the heavy load for the graphics card?
Thanks, bonus.2113