static void DrawVertexBuffer(Texture depthForParticlesRT, List<MyBillboard> billboards) { // This is important for optimalization (although I don't know when it can happen to have zero billboards), but // also that loop below needs it - as it assumes we are rendering at least one billboard. if (billboards.Count == 0) return; Device device = MyRender.GraphicsDevice; Surface oldTargets = null; DepthStencilState previousState = null; if (MyRender.Settings.VisualizeOverdraw) { oldTargets = device.GetRenderTarget(0); //We borrow lod0normals to render stencil MyRender.SetRenderTarget(MyRender.GetRenderTarget(MyRenderTargets.Auxiliary0), null); device.Clear(ClearFlags.Target | ClearFlags.Stencil, new ColorBGRA(0), 1.0f, 0); previousState = MyStateObjects.StencilMask_AlwaysIncrement_DepthStencilState; MyStateObjects.StencilMask_AlwaysIncrement_DepthStencilState.Apply(); } else { previousState = DepthStencilState.None; DepthStencilState.None.Apply(); } // Draw particles without culling. It's because how we calculate left/up vector, we can have problems in back camera. (yes, that can be solved, but why bother...) // Also I guess that drawing without culling may be faster - as GPU doesn't have to check it // No it's not, correct culling is faster: http://msdn.microsoft.com/en-us/library/windows/desktop/bb204882(v=vs.85).aspx RasterizerState.CullNone.Apply(); MyEffectTransparentGeometry effect = MyRender.GetEffect(MyEffects.TransparentGeometry) as MyEffectTransparentGeometry; effect.SetWorldMatrix(Matrix.Identity); Matrix viewMatrix = MyRenderCamera.ViewMatrixAtZero; effect.SetViewMatrix(ref viewMatrix); effect.SetProjectionMatrix(ref MyRenderCamera.ProjectionMatrix); Viewport originalViewport = MyRender.GraphicsDevice.Viewport; effect.SetDepthsRT(depthForParticlesRT); effect.SetHalfPixel(depthForParticlesRT.GetLevelDescription(0).Width, depthForParticlesRT.GetLevelDescription(0).Height); effect.SetScale(MyRender.GetScaleForViewport(depthForParticlesRT)); // Later we can interpolate between Main and Aux effect.SetEnvironmentMap(MyRender.GetRenderTargetCube(MyRenderTargets.EnvironmentCube)); //For struct size checks //int stride = MyVertexFormatTransparentGeometry.VertexDeclaration.VertexStride; //int s = Marshal.SizeOf(new MyVertexFormatTransparentGeometry()); // We iterate over all sorted billboards, and seach for when texture/shader has changed. // We try to draw as many billboards as possible (using the same texture), but because we are rendering billboards // sorted by depth, we still need to switch sometimes. Btw: I have observed, that most time consuming when drawing particles // is device.DrawUserPrimitives(), even if I call it for the whole list of billboards (without this optimization). I think, it's // because particles are pixel-bound (I do a lot of light calculation + there is blending, which is always slow). MyTransparentMaterial lastMaterial = MyTransparentMaterials.GetMaterial(billboards[0].Material); MyBillboard lastBillboard = billboards[0]; MyTransparentMaterial lastBlendMaterial = lastBillboard.BlendMaterial != null ? MyTransparentMaterials.GetMaterial(lastBillboard.BlendMaterial) : null; bool ignoreDepth = false; Matrix projectionMatrix = MyRenderCamera.ProjectionMatrix; Matrix invProjectionMatrix = Matrix.Invert(projectionMatrix); effect.SetInverseDefaultProjectionMatrix(ref invProjectionMatrix); if (lastBillboard.CustomViewProjection != -1) { SetupCustomViewProjection(effect, ref originalViewport, lastBillboard, ref ignoreDepth, ref projectionMatrix); } // 0.05% of billboard is blended const float softColorizeSize = 0.05f; device.VertexDeclaration = MyVertexFormatTransparentGeometry.VertexDeclaration; device.SetStreamSource(0, m_vertexBuffer, 0, MyVertexFormatTransparentGeometry.Stride); device.Indices = m_indexBuffer; MyRender.GetShadowRenderer().SetupShadowBaseEffect(effect); MyEffectTransparentGeometry effect2 = MyRender.GetEffect(MyEffects.TransparentGeometry) as MyEffectTransparentGeometry; effect2.SetShadowBias(0.001f); MyLights.UpdateEffectReflector(effect2.Reflector, false); MyLights.UpdateEffect(effect2, false); int geomCount = billboards.Count; int it = 0; int cnt = 0; while (geomCount > 0) { if (geomCount > RENDER_BUFFER_SIZE) { geomCount -= RENDER_BUFFER_SIZE; cnt = RENDER_BUFFER_SIZE; } else { cnt = geomCount; geomCount = 0; } int indexFrom = it * RENDER_BUFFER_SIZE + 1; cnt = cnt + indexFrom - 1; for (int i = indexFrom; i <= cnt; i++) { // We need texture from billboard that's before the current billboard (because we always render "what was") MyBillboard billboard = billboards[i - 1]; MyTransparentMaterial blendMaterialProperties = billboard.BlendMaterial != null ? MyTransparentMaterials.GetMaterial(billboard.BlendMaterial) : MyTransparentMaterials.GetMaterial(billboard.Material); MyTransparentMaterial lastBlendMaterialProperties = lastBlendMaterial == null ? blendMaterialProperties : lastBlendMaterial; bool colorizeChanged = EnableColorize && lastBillboard.EnableColorize != billboard.EnableColorize; bool nearChanged = lastBillboard.Near != billboard.Near; bool sizeChanged = EnableColorize && billboard.EnableColorize && lastBillboard.Size != billboard.Size; bool blendTextureChanged = false; bool projectionChanged = lastBillboard.CustomViewProjection != billboard.CustomViewProjection; bool cullStencilChanges = lastBillboard.CullWithStencil != billboard.CullWithStencil; if (lastBlendMaterial != (billboard.BlendMaterial != null ? MyTransparentMaterials.GetMaterial(billboard.BlendMaterial) : null) && billboard.BlendTextureRatio > 0) { if ((lastBlendMaterialProperties.UseAtlas) && (blendMaterialProperties.UseAtlas)) blendTextureChanged = false; else blendTextureChanged = true; } //bool blendTextureChanged = lastBlendTexture != billboard.BlendTexture; bool billboardChanged = colorizeChanged || sizeChanged || blendTextureChanged || nearChanged || projectionChanged || cullStencilChanges; MyTransparentMaterial actMaterialProperties = MyTransparentMaterials.GetMaterial(billboard.Material); MyTransparentMaterial lastMaterialProperties = lastMaterial; billboardChanged |= (actMaterialProperties.CanBeAffectedByOtherLights != lastMaterialProperties.CanBeAffectedByOtherLights) || (actMaterialProperties.IgnoreDepth != lastMaterialProperties.IgnoreDepth); if (projectionChanged) { SetupCustomViewProjection(effect, ref originalViewport, lastBillboard, ref ignoreDepth, ref projectionMatrix); } if (!billboardChanged) { if (MyTransparentMaterials.GetMaterial(billboard.Material) != lastMaterial) { if (actMaterialProperties.UseAtlas && lastMaterialProperties.UseAtlas) billboardChanged = false; else billboardChanged = true; } } // If texture is different than the last one, or if we reached end of billboards if ((i == cnt) || billboardChanged) { // We don't need to do this when we reach end of billboards - it's needed only if we do next iteration of possible billboards if ((i != cnt) || billboardChanged) { if ((i - indexFrom) > 0) { int firstIndex = (indexFrom - 1) * MyTransparentGeometryConstants.INDICES_PER_TRANSPARENT_GEOMETRY; //MyTransparentGeometryConstants.VERTICES_PER_TRANSPARENT_GEOMETRY; SetupCustomViewProjection(effect, ref originalViewport, lastBillboard, ref ignoreDepth, ref projectionMatrix); SetupEffect(ref lastMaterial, lastBlendMaterialProperties, EnableColorize && lastBillboard.EnableColorize, lastBillboard.Size * softColorizeSize, lastBillboard.Near, ignoreDepth, ref projectionMatrix); effect.Begin(); DrawBuffer(firstIndex, (i - indexFrom) * MyTransparentGeometryConstants.TRIANGLES_PER_TRANSPARENT_GEOMETRY); effect.End(); MyPerformanceCounter.PerCameraDrawWrite.BillboardsDrawCalls++; } lastMaterial = MyTransparentMaterials.GetMaterial(billboard.Material); lastBillboard = billboard; lastBlendMaterial = billboard.BlendMaterial != null ? MyTransparentMaterials.GetMaterial(billboard.BlendMaterial) : null; indexFrom = i; } if ((i == cnt) && (i - indexFrom + 1 != 0)) { lastMaterial = MyTransparentMaterials.GetMaterial(lastBillboard.Material); blendMaterialProperties = lastBillboard.BlendMaterial == null ? MyTransparentMaterials.GetMaterial(lastBillboard.Material) : MyTransparentMaterials.GetMaterial(lastBillboard.BlendMaterial); int firstIndex = (indexFrom - 1) * MyTransparentGeometryConstants.INDICES_PER_TRANSPARENT_GEOMETRY; SetupCustomViewProjection(effect, ref originalViewport, lastBillboard, ref ignoreDepth, ref projectionMatrix); SetupEffect(ref lastMaterial, blendMaterialProperties, EnableColorize && billboard.EnableColorize, lastBillboard.Size * softColorizeSize, lastBillboard.Near, ignoreDepth, ref projectionMatrix); if (billboard.CullWithStencil) { DepthStencilState.BackgroundObjects.Apply(); } effect.Begin(); DrawBuffer(firstIndex, (i - indexFrom + 1) * MyTransparentGeometryConstants.TRIANGLES_PER_TRANSPARENT_GEOMETRY); effect.End(); if (billboard.CullWithStencil) { previousState.Apply(); } MyPerformanceCounter.PerCameraDrawWrite.BillboardsDrawCalls++; } } } it++; } device.SetStreamSource(0, null, 0, 0); MyPerformanceCounter.PerCameraDrawWrite.BillboardsInFrustum += billboards.Count; // Visualize overdraw of particles. More overdraws = bigger performance issue. if (MyRender.Settings.VisualizeOverdraw) { if (m_overDrawColorsAnim == null) { m_overDrawColorsAnim = new MyAnimatedPropertyVector4(); m_overDrawColorsAnim.AddKey(0.0f, new Vector4(0.0f, 1.0f, 0.0f, 1.0f)); m_overDrawColorsAnim.AddKey(0.25f, new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); m_overDrawColorsAnim.AddKey(0.75f, new Vector4(0.0f, 0.0f, 1.0f, 1.0f)); m_overDrawColorsAnim.AddKey(1.0f, new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); } //Space without particles is black device.Clear(ClearFlags.Target, new ColorBGRA(0), 1.0f, 0); for (int referenceStencil = 1; referenceStencil < PARTICLES_OVERDRAW_MAX; referenceStencil++) { DepthStencilState ds = new DepthStencilState() { StencilEnable = true, ReferenceStencil = referenceStencil, StencilFunction = Compare.LessEqual, }; ds.Apply(false); float diff = (float)(referenceStencil - 1) / (PARTICLES_OVERDRAW_MAX - 1); Vector4 referenceColorV4; m_overDrawColorsAnim.GetInterpolatedValue<Vector4>(diff, out referenceColorV4); Color referenceColor = new Color(referenceColorV4); MyRender.BeginSpriteBatch(BlendState.Opaque, null, null); MyRender.DrawSprite(MyRender.BlankTexture, new Rectangle(0, 0, MyRenderCamera.Viewport.Width, MyRenderCamera.Viewport.Height), referenceColor); MyRender.EndSpriteBatch(); } DepthStencilState.None.Apply(); int leftStart = MyRenderCamera.Viewport.Width / 4; int topStart = (int)(MyRenderCamera.Viewport.Height * 0.75f); int size = MyRenderCamera.Viewport.Width - 2 * leftStart; int sizeY = (int)(MyRenderCamera.Viewport.Width / 32.0f); int sizeStep = size / PARTICLES_OVERDRAW_MAX; MyRender.BeginSpriteBatch(null, null, null); for (int i = 0; i < PARTICLES_OVERDRAW_MAX; i++) { float diff = (float)(i - 1) / (PARTICLES_OVERDRAW_MAX - 1); Vector4 referenceColorV4; m_overDrawColorsAnim.GetInterpolatedValue<Vector4>(diff, out referenceColorV4); Color referenceColor = new Color(referenceColorV4); MyRender.DrawSprite(MyRender.BlankTexture, new Rectangle(leftStart + i * sizeStep, topStart, sizeStep, sizeY), referenceColor); } MyDebugDraw.DrawText(new Vector2((float)leftStart, (float)(topStart + sizeY)), new System.Text.StringBuilder("1"), Color.White, 1.0f, false); MyDebugDraw.DrawText(new Vector2((float)leftStart + size, (float)(topStart + sizeY)), new System.Text.StringBuilder(">" + PARTICLES_OVERDRAW_MAX.ToString()), Color.White, 1.0f, false); MyRender.EndSpriteBatch(); device.SetRenderTarget(0, oldTargets); oldTargets.Dispose(); MyRender.Blit(MyRender.GetRenderTarget(MyRenderTargets.Auxiliary0), false); } // Restore to 'opaque', because that's the usual blend state BlendState.Opaque.Apply(); }
internal static void BeginSpriteBatch(BlendState blendState, DepthStencilState depthState, RasterizerState rasterizerState) { if (m_spriteBatchUsageCount == 0) { // Deferred means that draw call will be send to GPU not on every Draw(), but only at the End() or if we change // a texture between Begin/End. It's faster than Immediate mode. m_spriteBatch.Begin(SpriteSortMode.Deferred, blendState, VRageRender.Graphics.SamplerState.LinearClamp, depthState, rasterizerState); } m_spriteBatchUsageCount++; }