Skip to content

Commit 9bbacaa

Browse files
committed
Implement custom SpriteBatch replacement for batching terrain tiles
1 parent 6d56d49 commit 9bbacaa

File tree

6 files changed

+248
-13
lines changed

6 files changed

+248
-13
lines changed

src/TSMapEditor/Constants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace TSMapEditor
44
{
55
public static class Constants
66
{
7-
public const string ReleaseVersion = "1.6.0";
7+
public const string ReleaseVersion = "1.6.1";
88

99
public static int CellSizeX = 48;
1010
public static int CellSizeY = 24;

src/TSMapEditor/Content/Shaders/PalettedTerrainDraw.fx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,34 @@ SamplerState PaletteSampler
3434
MagFilter = Point;
3535
};
3636

37+
// Vertex shader input
38+
struct VertexShaderInput
39+
{
40+
float3 Position : POSITION;
41+
float4 Color : COLOR0;
42+
float2 TextureCoordinates : TEXCOORD0;
43+
};
44+
45+
matrix WorldViewProj : register(c0);
46+
3747
struct VertexShaderOutput
3848
{
3949
float4 Position : SV_POSITION;
4050
float4 Color : COLOR0;
4151
float2 TextureCoordinates : TEXCOORD0;
4252
};
4353

54+
55+
VertexShaderOutput MainVS(VertexShaderInput input)
56+
{
57+
VertexShaderOutput output;
58+
output.Position = mul(float4(input.Position, 1.0), WorldViewProj);
59+
output.Color = input.Color;
60+
output.TextureCoordinates = input.TextureCoordinates;
61+
return output;
62+
}
63+
64+
4465
struct PixelShaderOutput
4566
{
4667
float4 color : SV_Target0;
@@ -92,6 +113,7 @@ technique SpriteDrawing
92113
{
93114
pass P0
94115
{
116+
VertexShader = compile VS_SHADERMODEL MainVS();
95117
PixelShader = compile PS_SHADERMODEL MainPS();
96118
}
97119
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace TSMapEditor.Rendering.Batching
2+
{
3+
class RenderingConstants
4+
{
5+
public const int MaxVertices = 65535;
6+
public const int VerticesPerQuad = 4;
7+
public const int IndexesPerQuad = 6;
8+
public const int TrianglesPerQuad = 2;
9+
}
10+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using Microsoft.Xna.Framework;
2+
using Microsoft.Xna.Framework.Graphics;
3+
using System;
4+
using System.Collections.Generic;
5+
6+
namespace TSMapEditor.Rendering.Batching
7+
{
8+
/// <summary>
9+
/// Custom SpriteBatch replacement for more efficient rendering of terrain tiles.
10+
/// </summary>
11+
public class TerrainBatcher
12+
{
13+
private GraphicsDevice _graphicsDevice;
14+
private Effect _effect;
15+
16+
/// <summary>
17+
/// Pool for created batches.
18+
/// Pooling these means that these big objects don't need to be reconstructed each time a batch is drawn.
19+
/// </summary>
20+
private Queue<TextureBatch<VertexPositionColorTexture>> inactiveBatches = new Queue<TextureBatch<VertexPositionColorTexture>>();
21+
22+
/// <summary>
23+
/// Batches that are currently queued for drawing.
24+
/// </summary>
25+
private List<TextureBatch<VertexPositionColorTexture>> queuedBatches = new List<TextureBatch<VertexPositionColorTexture>>();
26+
27+
private EffectParameter mainTextureParameter;
28+
private EffectParameter worldViewProjParameter;
29+
private DepthStencilState depthStencilState;
30+
31+
private bool beginCalled = false;
32+
33+
public TerrainBatcher(GraphicsDevice graphicsDevice, Effect effect, DepthStencilState depthStencilState)
34+
{
35+
_graphicsDevice = graphicsDevice;
36+
_effect = effect;
37+
this.depthStencilState = depthStencilState;
38+
39+
mainTextureParameter = _effect.Parameters["MainTexture"];
40+
worldViewProjParameter = _effect.Parameters["WorldViewProj"];
41+
}
42+
43+
public void Begin()
44+
{
45+
if (beginCalled)
46+
throw new InvalidOperationException("End needs to be called between two successive calls to Begin.");
47+
48+
beginCalled = true;
49+
}
50+
51+
public void Draw(Texture2D texture, Rectangle destinationRect, Rectangle? sourceRect, Color color, float depth)
52+
{
53+
Rectangle src = sourceRect ?? new Rectangle(0, 0, texture.Width, texture.Height);
54+
55+
float u1 = (float)src.X / texture.Width;
56+
float v1 = (float)src.Y / texture.Height;
57+
float u2 = (float)(src.X + src.Width) / texture.Width;
58+
float v2 = (float)(src.Y + src.Height) / texture.Height;
59+
60+
float x = destinationRect.X;
61+
float y = destinationRect.Y;
62+
float w = destinationRect.Width;
63+
float h = destinationRect.Height;
64+
65+
TextureBatch<VertexPositionColorTexture> batch = null;
66+
for (int i = 0; i < queuedBatches.Count; i++)
67+
{
68+
if (queuedBatches[i].Texture == texture && !queuedBatches[i].IsFull())
69+
batch = queuedBatches[i];
70+
}
71+
72+
if (batch == null)
73+
{
74+
if (!inactiveBatches.TryDequeue(out batch))
75+
{
76+
batch = new TextureBatch<VertexPositionColorTexture>();
77+
}
78+
79+
queuedBatches.Add(batch);
80+
batch.Texture = texture;
81+
}
82+
83+
int vertexOffset = batch.QuadCount * RenderingConstants.VerticesPerQuad;
84+
85+
if (vertexOffset >= batch.Vertices.Length)
86+
{
87+
batch.ExtendArrays();
88+
}
89+
90+
int indexOffset = batch.QuadCount * RenderingConstants.IndexesPerQuad;
91+
92+
unsafe
93+
{
94+
// 4 vertices of the quad
95+
fixed (VertexPositionColorTexture* p = &batch.Vertices[vertexOffset])
96+
{
97+
p[0] = new VertexPositionColorTexture { Position = new Vector3(x, y, depth), TextureCoordinate = new Vector2(u1, v1), Color = color };
98+
p[1] = new VertexPositionColorTexture { Position = new Vector3(x + w, y, depth), TextureCoordinate = new Vector2(u2, v1), Color = color };
99+
p[2] = new VertexPositionColorTexture { Position = new Vector3(x, y + h, depth), TextureCoordinate = new Vector2(u1, v2), Color = color };
100+
p[3] = new VertexPositionColorTexture { Position = new Vector3(x + w, y + h, depth), TextureCoordinate = new Vector2(u2, v2), Color = color };
101+
}
102+
103+
// 2 triangles (indices)
104+
fixed (short* p = &batch.Indexes[indexOffset])
105+
{
106+
p[0] = (short)(vertexOffset + 0);
107+
p[1] = (short)(vertexOffset + 1);
108+
p[2] = (short)(vertexOffset + 2);
109+
110+
p[3] = (short)(vertexOffset + 2);
111+
p[4] = (short)(vertexOffset + 1);
112+
p[5] = (short)(vertexOffset + 3);
113+
}
114+
}
115+
116+
batch.QuadCount++;
117+
}
118+
119+
public void End()
120+
{
121+
// Build orthographic projection
122+
Matrix projection = Matrix.CreateOrthographicOffCenter(
123+
0, _graphicsDevice.Viewport.Width,
124+
_graphicsDevice.Viewport.Height, 0,
125+
0, -1);
126+
127+
// Upload it to the shader
128+
worldViewProjParameter.SetValue(projection);
129+
130+
for (int i = 0; i < queuedBatches.Count; i++)
131+
{
132+
var batch = queuedBatches[i];
133+
mainTextureParameter.SetValue(batch.Texture);
134+
135+
foreach (EffectPass pass in _effect.CurrentTechnique.Passes)
136+
{
137+
pass.Apply();
138+
139+
_graphicsDevice.DrawUserIndexedPrimitives(
140+
PrimitiveType.TriangleList,
141+
batch.Vertices, 0, batch.QuadCount * RenderingConstants.VerticesPerQuad,
142+
batch.Indexes, 0, batch.QuadCount * RenderingConstants.TrianglesPerQuad,
143+
VertexPositionColorTexture.VertexDeclaration
144+
);
145+
}
146+
}
147+
148+
for (int i = 0; i < queuedBatches.Count; i++)
149+
{
150+
queuedBatches[i].Clear();
151+
inactiveBatches.Enqueue(queuedBatches[i]);
152+
}
153+
154+
queuedBatches.Clear();
155+
beginCalled = false;
156+
}
157+
}
158+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Microsoft.Xna.Framework.Graphics;
2+
using System;
3+
4+
namespace TSMapEditor.Rendering.Batching
5+
{
6+
class TextureBatch<T> where T : struct, IVertexType
7+
{
8+
public Texture2D Texture;
9+
public T[] Vertices = new T[16];
10+
public short[] Indexes = new short[24];
11+
12+
public int QuadCount;
13+
14+
public void Clear()
15+
{
16+
Texture = null;
17+
QuadCount = 0;
18+
}
19+
20+
public void ExtendArrays()
21+
{
22+
if (IsFull())
23+
throw new InvalidOperationException("Cannot extend a texture batch that is already full.");
24+
25+
// Double array sizes
26+
var newVertexArray = new T[Math.Min(Vertices.Length * 2, RenderingConstants.MaxVertices)];
27+
Array.Copy(Vertices, newVertexArray, Vertices.Length);
28+
Vertices = newVertexArray;
29+
30+
var newIndexArray = new short[Indexes.Length * 2];
31+
Array.Copy(Indexes, newIndexArray, Indexes.Length);
32+
Indexes = newIndexArray;
33+
}
34+
35+
public bool IsFull() => QuadCount + RenderingConstants.VerticesPerQuad >= RenderingConstants.MaxVertices / RenderingConstants.VerticesPerQuad;
36+
}
37+
}

src/TSMapEditor/Rendering/MapView.cs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using TSMapEditor.GameMath;
1212
using TSMapEditor.Models;
1313
using TSMapEditor.Models.Enums;
14+
using TSMapEditor.Rendering.Batching;
1415
using TSMapEditor.Rendering.ObjectRenderers;
1516
using TSMapEditor.Settings;
1617
using TSMapEditor.UI;
@@ -115,6 +116,8 @@ public MapView(WindowManager windowManager, Map map, TheaterGraphics theaterGrap
115116
private Effect alphaMapDrawEffect; // Effect for rendering the alpha light map
116117
private Effect alphaImageToAlphaMapEffect; // Effect for rendering a single alpha image to the alpha light map
117118

119+
private TerrainBatcher terrainBatcher;
120+
118121
private bool mapInvalidated;
119122
private bool cameraMoved;
120123
private bool minimapNeedsRefresh;
@@ -182,6 +185,7 @@ public void Initialize()
182185

183186
RefreshRenderTargets();
184187
CreateDepthStencilStates();
188+
InitBatchers();
185189

186190
Map.LocalSizeChanged += (s, e) => InvalidateMap();
187191
Map.MapHeightChanged += (s, e) => InvalidateMap();
@@ -336,6 +340,11 @@ private void CreateDepthStencilStates()
336340
}
337341
}
338342

343+
private void InitBatchers()
344+
{
345+
terrainBatcher = new TerrainBatcher(GraphicsDevice, palettedTerrainDrawEffect, depthRenderStencilState);
346+
}
347+
339348
private RenderDependencies CreateRenderDependencies()
340349
{
341350
return new RenderDependencies(Map, TheaterGraphics, EditorState, windowManager.GraphicsDevice, objectSpriteRecord, palettedColorDrawEffect, Camera, GetCameraRightXCoord, GetCameraBottomYCoord);
@@ -382,14 +391,17 @@ public void DrawVisibleMapPortion()
382391
// Draw terrain tiles in batched mode for performance if we can.
383392
// In Marble Madness mode we currently need to mix and match paletted and non-paletted graphics, so there's no avoiding immediate mode.
384393
SetTerrainEffectParams(TheaterGraphics.TheaterPalette.GetTexture());
385-
var palettedTerrainDrawSettings = new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.Opaque, null, depthRenderStencilState, null, palettedTerrainDrawEffect);
386-
Renderer.PushSettings(palettedTerrainDrawSettings);
394+
395+
terrainBatcher.Begin();
387396
DoForVisibleCells(DrawTerrainTileAndRegisterObjects);
397+
terrainBatcher.End();
388398

389399
// We do not need to write to the depth render target when drawing smudges and flat overlays.
390400
// Swap to using only the main map render target.
391401
// At this point of drawing, depth testing is done on depth buffer embedded in the main map render target.
392402
Renderer.PopRenderTarget();
403+
404+
var palettedTerrainDrawSettings = new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.Opaque, null, depthRenderStencilState, null, palettedTerrainDrawEffect);
393405
Renderer.PushRenderTarget(mapRenderTarget, palettedTerrainDrawSettings);
394406

395407
// Smudges can be drawn as part of regular terrain.
@@ -703,15 +715,15 @@ public void DrawTerrainTile(MapTile tile)
703715

704716
if (subTileIndex >= tileImage.TMPImages.Length)
705717
{
706-
Renderer.DrawString(subTileIndex.ToString(), 0, new Vector2(drawPoint.X, drawPoint.Y), Color.Red);
718+
// Renderer.DrawString(subTileIndex.ToString(), 0, new Vector2(drawPoint.X, drawPoint.Y), Color.Red);
707719
return;
708720
}
709721

710722
MGTMPImage tmpImage = tileImage.TMPImages[subTileIndex];
711723

712724
if (tmpImage == null || tmpImage.Texture == null)
713725
{
714-
Renderer.DrawString(subTileIndex.ToString(), 0, new Vector2(drawPoint.X, drawPoint.Y), Color.Red);
726+
// Renderer.DrawString(subTileIndex.ToString(), 0, new Vector2(drawPoint.X, drawPoint.Y), Color.Red);
715727
return;
716728
}
717729

@@ -740,8 +752,9 @@ public void DrawTerrainTile(MapTile tile)
740752
}
741753
}
742754

743-
Renderer.DrawTexture(textureToDraw, new Rectangle(drawX, drawY,
744-
Constants.CellSizeX, Constants.CellSizeY), sourceRectangle, color, 0f, Vector2.Zero, SpriteEffects.None, depth);
755+
terrainBatcher.Draw(textureToDraw,
756+
new Rectangle(drawX, drawY, Constants.CellSizeX, Constants.CellSizeY),
757+
sourceRectangle, color, depth);
745758

746759
if (tmpImage.TmpImage.HasExtraData())
747760
{
@@ -752,12 +765,7 @@ public void DrawTerrainTile(MapTile tile)
752765
tmpImage.ExtraSourceRectangle.Width,
753766
tmpImage.ExtraSourceRectangle.Height);
754767

755-
Renderer.DrawTexture(tmpImage.Texture,
756-
exDrawRectangle,
757-
tmpImage.ExtraSourceRectangle,
758-
color,
759-
0f,
760-
Vector2.Zero, SpriteEffects.None, depth);
768+
terrainBatcher.Draw(tmpImage.Texture, exDrawRectangle, tmpImage.ExtraSourceRectangle, color, depth);
761769
}
762770
}
763771

0 commit comments

Comments
 (0)