Client LuaCsForBarotrauma
SpriteRecorder.cs
1 using Microsoft.Xna.Framework;
2 using Microsoft.Xna.Framework.Graphics;
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Text;
7 
8 namespace Barotrauma
9 {
10  sealed class SpriteRecorder : ISpriteBatch, IDisposable
11  {
12  public readonly record struct Command(
13  Texture2D Texture,
14  VertexPositionColorTexture VertexBL,
15  VertexPositionColorTexture VertexBR,
16  VertexPositionColorTexture VertexTL,
17  VertexPositionColorTexture VertexTR,
18  float Depth,
19  Vector2 Min,
20  Vector2 Max,
21  int Index)
22  {
23  public static Vector2 GetMinPosition(params VertexPositionColorTexture[] vertices)
24  => new Vector2(
25  MathUtils.Min(vertices.Select(v => v.Position.X).ToArray()),
26  MathUtils.Min(vertices.Select(v => v.Position.Y).ToArray()));
27 
28  public static Vector2 GetMaxPosition(params VertexPositionColorTexture[] vertices)
29  => new Vector2(
30  MathUtils.Max(vertices.Select(v => v.Position.X).ToArray()),
31  MathUtils.Max(vertices.Select(v => v.Position.Y).ToArray()));
32 
33  public static Command FromTransform(
34  Texture2D texture,
35  Vector2 pos,
36  Rectangle srcRect,
37  Color color,
38  float rotationRad,
39  Vector2 origin,
40  Vector2 scale,
41  SpriteEffects effects,
42  float depth,
43  int index)
44  {
45  int srcRectLeft = srcRect.Left;
46  int srcRectRight = srcRect.Right;
47  int srcRectTop = srcRect.Top;
48  int srcRectBottom = srcRect.Bottom;
49  if (effects.HasFlag(SpriteEffects.FlipHorizontally))
50  {
51  (srcRectRight, srcRectLeft) = (srcRectLeft, srcRectRight);
52  }
53  if (effects.HasFlag(SpriteEffects.FlipVertically))
54  {
55  (srcRectBottom, srcRectTop) = (srcRectTop, srcRectBottom);
56  }
57 
58  float sin = (float)Math.Sin(rotationRad);
59  float cos = (float)Math.Cos(rotationRad);
60 
61  var size = srcRect.Size.ToVector2() * scale;
62 
63  Vector2 wAdd = new Vector2(size.X * cos, size.X * sin);
64  Vector2 hAdd = new Vector2(-size.Y * sin, size.Y * cos);
65  pos.X -= origin.X * scale.X * cos - origin.Y * scale.Y * sin;
66  pos.Y -= origin.Y * scale.Y * cos + origin.X * scale.X * sin;
67 
68  var vertexTl = new VertexPositionColorTexture
69  {
70  Color = color,
71  Position = new Vector3(pos.X, pos.Y, 0f),
72  TextureCoordinate = new Vector2((float)srcRectLeft / (float)texture.Width, (float)srcRectTop / (float)texture.Height)
73  };
74 
75  var vertexTr = new VertexPositionColorTexture
76  {
77  Color = color,
78  Position = new Vector3(pos.X + wAdd.X, pos.Y + wAdd.Y, 0f),
79  TextureCoordinate = new Vector2((float)srcRectRight / (float)texture.Width, (float)srcRectTop / (float)texture.Height)
80  };
81 
82  var vertexBl = new VertexPositionColorTexture
83  {
84  Color = color,
85  Position = new Vector3(pos.X + hAdd.X, pos.Y + hAdd.Y, 0f),
86  TextureCoordinate = new Vector2((float)srcRectLeft / (float)texture.Width, (float)srcRectBottom / (float)texture.Height)
87  };
88 
89  var vertexBr = new VertexPositionColorTexture
90  {
91  Color = color,
92  Position = new Vector3(pos.X + wAdd.X + hAdd.X, pos.Y + wAdd.Y + hAdd.Y, 0f),
93  TextureCoordinate = new Vector2((float)srcRectRight / (float)texture.Width, (float)srcRectBottom / (float)texture.Height)
94  };
95 
96  var min = GetMinPosition(
97  vertexTl,
98  vertexTr,
99  vertexBl,
100  vertexBr);
101 
102  var max = GetMaxPosition(
103  vertexTl,
104  vertexTr,
105  vertexBl,
106  vertexBr);
107 
108  return new Command(
109  texture,
110  vertexBl,
111  vertexBr,
112  vertexTl,
113  vertexTr,
114  depth,
115  min,
116  max,
117  index);
118  }
119 
120  public bool Overlaps(Command other)
121  {
122  return
123  Min.X <= other.Max.X && Max.X >= other.Min.X &&
124  Min.Y <= other.Max.Y && Max.Y >= other.Min.Y;
125  }
126  }
127 
128  private struct RecordedBuffer
129  {
130  public readonly Texture2D Texture;
131  public readonly VertexBuffer VertexBuffer;
132  public readonly int PolyCount;
133 
134  public RecordedBuffer(List<Command> commandList, int startIndex, int count)
135  {
136  Texture = commandList[startIndex].Texture;
137 
138  VertexBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, count * 4, BufferUsage.WriteOnly);
139  VertexPositionColorTexture[] vertices = new VertexPositionColorTexture[count * 4];
140  for (int i = 0; i < count; i++)
141  {
142  vertices[(i * 4) + 0] = commandList[startIndex + i].VertexBL;
143  vertices[(i * 4) + 1] = commandList[startIndex + i].VertexBR;
144  vertices[(i * 4) + 2] = commandList[startIndex + i].VertexTL;
145  vertices[(i * 4) + 3] = commandList[startIndex + i].VertexTR;
146  }
147  VertexBuffer.SetData(vertices);
148 
149  PolyCount = count * 2;
150  }
151  }
152 
153  public static BasicEffect BasicEffect = null;
154 
155  private readonly List<RecordedBuffer> recordedBuffers = new List<RecordedBuffer>();
156  private readonly List<Command> commandList = new List<Command>();
157  private SpriteSortMode currentSortMode;
158 
159  private IndexBuffer indexBuffer = null;
160  private int maxSpriteCount = 0;
161 
162  public volatile bool ReadyToRender = false;
163  private volatile bool isDisposed = false;
164 
165  public Vector2 Min { get; private set; }
166  public Vector2 Max { get; private set; }
167 
168  public void Begin(SpriteSortMode sortMode)
169  {
170  ReadyToRender = false;
171  currentSortMode = sortMode;
172  }
173 
174  private void AppendCommand(Command command)
175  {
176  if (isDisposed) { return; }
177 
178  if (commandList.Count == 0) { Min = command.Min; Max = command.Max; }
179  Min = new Vector2(Math.Min(command.Min.X, Min.X), Math.Min(command.Min.Y, Min.Y));
180  Max = new Vector2(Math.Max(command.Max.X, Max.X), Math.Max(command.Max.Y, Max.Y));
181 
182  commandList.Add(command);
183  }
184 
185  public void Draw(Texture2D texture, Vector2 pos, Rectangle? srcRect, Color color, float rotationRad, Vector2 origin, Vector2 scale, SpriteEffects effects, float depth)
186  {
187  if (isDisposed) { return; }
188 
189  var command = Command.FromTransform(texture, pos, srcRect ?? texture.Bounds, color, rotationRad, origin, scale, effects, depth, commandList.Count);
190  AppendCommand(command);
191  }
192 
193  public void Draw(Texture2D texture, VertexPositionColorTexture[] vertices, float layerDepth, int? count = null)
194  {
195  if (isDisposed) { return; }
196 
197  int iters = count ?? (vertices.Length / 4);
198  for (int i=0;i<iters;i++)
199  {
200  var subset = vertices[((i * 4) + 0)..((i * 4) + 4)];
201  var command = new Command(
202  texture,
203  subset[2],
204  subset[3],
205  subset[0],
206  subset[1],
207  layerDepth,
208  Command.GetMinPosition(subset),
209  Command.GetMaxPosition(subset),
210  commandList.Count);
211  AppendCommand(command);
212  }
213  }
214 
215  public void End()
216  {
217  if (isDisposed) { return; }
218  //sort commands according to the sorting
219  //mode given in the last Begin call
220  switch (currentSortMode)
221  {
222  case SpriteSortMode.FrontToBack:
223  commandList.Sort((c1, c2) =>
224  {
225  return c1.Depth < c2.Depth ? -1
226  : c1.Depth > c2.Depth ? 1
227  : c1.Index < c2.Index ? 1
228  : c1.Index > c2.Index ? -1
229  : 0;
230  });
231  break;
232  case SpriteSortMode.BackToFront:
233  commandList.Sort((c1, c2) =>
234  {
235  return c1.Depth < c2.Depth ? 1
236  : c1.Depth > c2.Depth ? -1
237  : c1.Index < c2.Index ? 1
238  : c1.Index > c2.Index ? -1
239  : 0;
240  });
241  break;
242  }
243 
244  //try to place commands of the same texture
245  //contiguously for optimal buffer generation
246  //while maintaining the same visual result
247  for (int i = 1; i < commandList.Count; i++)
248  {
249  if (commandList[i].Texture != commandList[i - 1].Texture)
250  {
251  for (int j = i - 1; j >= 0; j--)
252  {
253  if (commandList[j].Texture == commandList[i].Texture)
254  {
255  //no commands between i and j overlap with
256  //i, therefore we can safely sift i down to
257  //make a contiguous block
258  commandList.SiftElement(i, j + 1);
259  break;
260  }
261  else if (commandList[j].Overlaps(commandList[i]))
262  {
263  //an overlapping command was found, therefore
264  //attempting to sift this one down would change
265  //the visual result
266  break;
267  }
268  }
269  }
270  }
271 
272  if (isDisposed) { return; }
273  //each contiguous block of commands of the same texture
274  //requires a vertex buffer to be rendered
275  CrossThread.RequestExecutionOnMainThread(() =>
276  {
277  if (isDisposed) { return; }
278  if (commandList.Count == 0) { return; }
279  int startIndex = 0;
280  for (int i = 1; i < commandList.Count; i++)
281  {
282  if (commandList[i].Texture != commandList[startIndex].Texture)
283  {
284  maxSpriteCount = Math.Max(maxSpriteCount, i - startIndex);
285  recordedBuffers.Add(new RecordedBuffer(commandList, startIndex, i - startIndex));
286  startIndex = i;
287  }
288  }
289  recordedBuffers.Add(new RecordedBuffer(commandList, startIndex, commandList.Count - startIndex));
290  maxSpriteCount = Math.Max(maxSpriteCount, commandList.Count - startIndex);
291  });
292 
293  commandList.Clear();
294 
295  ReadyToRender = true;
296  }
297 
298  public void Render(Camera cam)
299  {
300  if (!ReadyToRender) { return; }
301  var gfxDevice = GameMain.Instance.GraphicsDevice;
302 
303  BasicEffect ??= new BasicEffect(gfxDevice);
304  BasicEffect.Projection = Matrix.CreateOrthographicOffCenter(new Rectangle(0, 0, cam.Resolution.X, cam.Resolution.Y), -1f, 1f);
305  BasicEffect.View = cam.Transform;
306  BasicEffect.World = Matrix.Identity;
307  BasicEffect.TextureEnabled = true;
308  BasicEffect.VertexColorEnabled = true;
309  BasicEffect.Alpha = 1f;
310 
311  int requiredIndexCount = maxSpriteCount * 6;
312  if (requiredIndexCount > 0 && (indexBuffer == null || indexBuffer.IndexCount < requiredIndexCount))
313  {
314  indexBuffer?.Dispose();
315  indexBuffer = new IndexBuffer(gfxDevice, IndexElementSize.SixteenBits, requiredIndexCount * 2, BufferUsage.WriteOnly);
316  ushort[] indices = new ushort[requiredIndexCount * 2];
317  for (int i = 0; i < indices.Length; i += 6)
318  {
319  indices[i + 0] = (ushort)((i / 6) * 4 + 1);
320  indices[i + 1] = (ushort)((i / 6) * 4 + 0);
321  indices[i + 2] = (ushort)((i / 6) * 4 + 2);
322  indices[i + 3] = (ushort)((i / 6) * 4 + 1);
323  indices[i + 4] = (ushort)((i / 6) * 4 + 2);
324  indices[i + 5] = (ushort)((i / 6) * 4 + 3);
325  }
326  indexBuffer.SetData(indices);
327  }
328 
329  gfxDevice.Indices = indexBuffer;
330  for (int i = 0; i < recordedBuffers.Count; i++)
331  {
332  gfxDevice.SetVertexBuffer(recordedBuffers[i].VertexBuffer);
333  BasicEffect.Texture = recordedBuffers[i].Texture;
334  BasicEffect.CurrentTechnique.Passes[0].Apply();
335  gfxDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, recordedBuffers[i].PolyCount);
336  }
337  }
338 
339  public void Dispose()
340  {
341  isDisposed = true;
342  foreach (var buffer in recordedBuffers)
343  {
344  buffer.VertexBuffer.Dispose();
345  }
346  recordedBuffers.Clear();
347  commandList.Clear();
348  indexBuffer?.Dispose(); indexBuffer = null;
349  ReadyToRender = false;
350  }
351  }
352 }
Matrix Transform
Definition: Camera.cs:136
Point Resolution
Definition: Camera.cs:120
static GameMain Instance
Definition: GameMain.cs:144
void Draw(Texture2D texture, VertexPositionColorTexture[] vertices, float layerDepth, int? count=null)
static BasicEffect BasicEffect
void Draw(Texture2D texture, Vector2 pos, Rectangle? srcRect, Color color, float rotationRad, Vector2 origin, Vector2 scale, SpriteEffects effects, float depth)
readonly record struct Command(Texture2D Texture, VertexPositionColorTexture VertexBL, VertexPositionColorTexture VertexBR, VertexPositionColorTexture VertexTL, VertexPositionColorTexture VertexTR, float Depth, Vector2 Min, Vector2 Max, int Index)
void Begin(SpriteSortMode sortMode)