PageRenderTime 23ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/Utilities/Graphics/VertexFormat.cs

#
C# | 655 lines | 379 code | 41 blank | 235 comment | 32 complexity | 2e3d55d54743701f61164965b1b42f51 MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.IO;
  3. using Delta.Utilities.Helpers;
  4. using NUnit.Framework;
  5. namespace Delta.Utilities.Graphics
  6. {
  7. /// <summary>
  8. /// Vertex format for GeometryData, check out VertexFormatType for details!
  9. /// </summary>
  10. /// <remarks>
  11. /// Interesting papers and sites about vertex compression:
  12. /// http://blogs.msdn.com/b/shawnhar/archive/2010/11/19/compressed-vertex-data.aspx
  13. /// http://gsdwrdx.tistory.com/92
  14. /// http://stackoverflow.com/questions/1762866/using-gl-short-instead-of-gl-float-in-an-opengl-es-vertex-array
  15. /// http://wiki.gamedev.net/index.php/D3DBook:(Lighting)_Per-Pixel_Lighting
  16. /// http://www.cs.jhu.edu/~jab/publications/vertexcompression.pdf
  17. /// http://www.graphcomp.com/info/specs/ibm/cgd.html
  18. /// http://irrlicht.sourceforge.net/docu/_i_animated_mesh_m_d3_8h_source.html
  19. /// http://viola.usc.edu/Publication/PDF/selected/2000_JVCIR_Cheang.pdf
  20. /// http://www.cse.ohio-state.edu/~hwshen/Su01_888/deering.pdf
  21. /// http://www.cg.tuwien.ac.at/studentwork/VisFoSe98/lm/
  22. /// http://personal.redestb.es/jmovill/opengl/openglonwin-15.html
  23. /// https://www.opengl.org/sdk/docs/man3/xhtml/glVertexAttribPointer.xml
  24. /// Seems like support for 16 bit floats is sometimes there, but we cannot
  25. /// guarantee that it works on all platforms, so we cannot use it :(
  26. /// Instead we use shorts to compress data into 16 bits as well, but this
  27. /// has sometimes some additional overhead in the vertex shader.
  28. /// For example Position3DCompressed | TextureUVCompressed |
  29. /// NormalCompressed | TangentCompressed | SkinWeightCompressed |
  30. /// SkinIndexCompressed is just 6+4+6+6+2+4 = 28 bytes instead of
  31. /// 12+8+12+12+4+4 = 52 bytes uncompressed :)
  32. /// <para />
  33. /// Note: When we use different sizes, we must make sure that we align all
  34. /// our components to their native alignment, e.g. floats are on 4-byte
  35. /// boundaries, shorts are on 2-byte boundaries. If we don't do this it will
  36. /// tank our performance. It might be helpful to mentally map it by typing
  37. /// out your attribute ordering as a struct definition so we can sanity
  38. /// check our layout and alignment (this is not easy). Also:
  39. /// * making sure your data is stripped to share vertices
  40. /// * using a texture atlas to reduce texture swaps (see Image)
  41. /// <para />
  42. /// Optimizing Vertex Data
  43. /// Each API call results in a certain amount of overhead that is caused by
  44. /// translating the standard API calls into commands understood by the
  45. /// hardware. The amount of overhead can be reduced by minimizing the
  46. /// translation work required by making sure that the data submitted to the
  47. /// API is already in a hardware friendly format. This can be achieved by
  48. /// following the simple recommendation when submitting vertex data: submit
  49. /// strip-ordered indexed triangles with per vertex data interleaved.
  50. /// <para />
  51. /// Indexed triangles (glDrawElements) is preferred over non-indexed strips
  52. /// (glDrawArrays). Indexing allows for a higher amount of vertex reuse. The
  53. /// same vertex in strips can not be used for more than 3 triangles; indexed
  54. /// triangles does not have this constraint. Further, OpenGL ES requires one
  55. /// draw call per strip. If you want to collapse draw calls for multiple
  56. /// strips into one, you will have to add degenerate strips in between to
  57. /// introduce discontinuities. This extra work is not required using indexed
  58. /// triangles. With strip-ordered indexed triangles, multiple disconnected
  59. /// strips can be drawn in a single draw call, while avoiding the cost of
  60. /// inserting elements to create degenerate triangles.
  61. /// <para />
  62. /// Sorting the vertex data (either using indexed triangles or non-indexed
  63. /// strips) is very important. Sorting allows you to generate more triangles
  64. /// per vertex submitted. For peak performance, sort index triangles so that
  65. /// connected indexed triangles can build strips. This improves memory
  66. /// access patterns and the usage of the various data caches.
  67. /// <para />
  68. /// Use interleaved or tightly packed vertex arrays. OpenGL ES allows vertex
  69. /// data elements to be spread out over memory thus resulting in scatter
  70. /// reads to fetch the required data. On the other hand it is also possible
  71. /// to interleave the data such that it is already together in memory.
  72. /// Interleaving of the per vertex elements improves the memory efficiency
  73. /// and is more logical for the processing that needs to be done. For
  74. /// optimal performance, interleave the individual components in an order of
  75. /// Position, Normal, Color, TexCoord0, TexCoord1, PointSize, Weight,
  76. /// MatrixIndex. Memory bandwidth is limited, so try to use the smallest
  77. /// acceptable type for each component. Specify vertex colors using 4
  78. /// unsigned byte values. Specify texture coordinates with 2 or 4 unsigned
  79. /// byte or short values, instead of floating-point values, if you can.
  80. /// </remarks>
  81. public sealed class VertexFormat : ISaveLoadBinary
  82. {
  83. #region Constants
  84. /// <summary>
  85. /// Version number for this VertexFormat. If this goes above 1, we need
  86. /// to support loading older versions as well. Saving is always going
  87. /// to be the latest version (this one).
  88. /// </summary>
  89. private const int VersionNumber = 1;
  90. /// <summary>
  91. /// Empty vertex format, currently only used for OpenGL ES 1.1, where we
  92. /// need to reset the vertex format after rendering else we are in a
  93. /// messed up state and problems in follow up code can happen. This is
  94. /// not required for any other OpenGL or Graphics implementation.
  95. /// </summary>
  96. public static readonly VertexFormat None =
  97. new VertexFormat(new VertexElement[]
  98. {
  99. });
  100. /// <summary>
  101. /// Used mostly for simple 2D and UI data (basic shader, no extra features)
  102. /// </summary>
  103. public static readonly VertexFormat Position2DTextured =
  104. new VertexFormat(new[]
  105. {
  106. new VertexElement(VertexElementType.Position2D),
  107. new VertexElement(VertexElementType.TextureUV),
  108. });
  109. /// <summary>
  110. /// This vertex format is used mostly for simple 3D meshes, e.g.
  111. /// Mesh.CreateSphere (uncompressed 3+2 floats = 20 bytes, compressed 10)
  112. /// </summary>
  113. public static readonly VertexFormat Position3DTextured =
  114. new VertexFormat(new[]
  115. {
  116. new VertexElement(VertexElementType.Position3D),
  117. new VertexElement(VertexElementType.TextureUV),
  118. });
  119. /// <summary>
  120. /// Simple format for 2D points and colored vertices, used for lines.
  121. /// </summary>
  122. public static readonly VertexFormat Position2DColor =
  123. new VertexFormat(new[]
  124. {
  125. new VertexElement(VertexElementType.Position2D),
  126. new VertexElement(VertexElementType.Color),
  127. });
  128. /// <summary>
  129. /// Simple format for textured 2D drawing with vertex colors. Mostly
  130. /// used for effects because each vertex can have different colors.
  131. /// </summary>
  132. public static readonly VertexFormat Position2DColorTextured =
  133. new VertexFormat(new[]
  134. {
  135. new VertexElement(VertexElementType.Position2D),
  136. new VertexElement(VertexElementType.Color),
  137. new VertexElement(VertexElementType.TextureUV),
  138. });
  139. /// <summary>
  140. /// Simple format for 3D points and colored vertices, used for 3D lines.
  141. /// </summary>
  142. public static readonly VertexFormat Position3DColor =
  143. new VertexFormat(new[]
  144. {
  145. new VertexElement(VertexElementType.Position3D),
  146. new VertexElement(VertexElementType.Color),
  147. });
  148. /// <summary>
  149. /// This vertex format is used mostly for simple 3D meshes, e.g.
  150. /// Mesh.CreateSphere (uncompressed 24 bytes, compressed 16)
  151. /// </summary>
  152. public static readonly VertexFormat Position3DColorTextured =
  153. new VertexFormat(new[]
  154. {
  155. new VertexElement(VertexElementType.Position3D),
  156. new VertexElement(VertexElementType.Color),
  157. new VertexElement(VertexElementType.TextureUV),
  158. });
  159. /// <summary>
  160. /// 3D position, uv and lightmap textured vertex format for simple 3D
  161. /// geometry using light maps.
  162. /// </summary>
  163. public static readonly VertexFormat Position3DTexturedLightMap =
  164. new VertexFormat(new[]
  165. {
  166. new VertexElement(VertexElementType.Position3D),
  167. new VertexElement(VertexElementType.TextureUV),
  168. new VertexElement(VertexElementType.LightMapUV),
  169. });
  170. /// <summary>
  171. /// Position textured skinned
  172. /// </summary>
  173. public static readonly VertexFormat PositionTexturedSkinned =
  174. new VertexFormat(new[]
  175. {
  176. new VertexElement(VertexElementType.Position3D),
  177. new VertexElement(VertexElementType.TextureUV),
  178. // Skin data are always handled as compressed
  179. new VertexElement(VertexElementType.SkinIndices, true),
  180. new VertexElement(VertexElementType.SkinWeights, true),
  181. });
  182. /// <summary>
  183. /// Position skinned
  184. /// </summary>
  185. public static readonly VertexFormat PositionSkinned =
  186. new VertexFormat(new[]
  187. {
  188. new VertexElement(VertexElementType.Position3D),
  189. // Skin data are always handled as compressed
  190. new VertexElement(VertexElementType.SkinIndices, true),
  191. new VertexElement(VertexElementType.SkinWeights, true),
  192. });
  193. /// <summary>
  194. /// Position normal textured skinned
  195. /// </summary>
  196. public static readonly VertexFormat PositionNormalTexturedSkinned =
  197. new VertexFormat(new[]
  198. {
  199. new VertexElement(VertexElementType.Position3D),
  200. new VertexElement(VertexElementType.Normal),
  201. new VertexElement(VertexElementType.TextureUV),
  202. // Skin data are always handled as compressed
  203. new VertexElement(VertexElementType.SkinIndices, true),
  204. new VertexElement(VertexElementType.SkinWeights, true),
  205. });
  206. #endregion
  207. #region Elements (Public)
  208. /// <summary>
  209. /// Elements
  210. /// </summary>
  211. public VertexElement[] Elements
  212. {
  213. get;
  214. private set;
  215. }
  216. #endregion
  217. #region LengthInBytes (Public)
  218. /// <summary>
  219. /// Get length in bytes, cached in GeometryData as this is used quite often
  220. /// and never changes.
  221. /// </summary>
  222. public int LengthInBytes
  223. {
  224. get;
  225. private set;
  226. }
  227. #endregion
  228. #region IsCompressed (Public)
  229. /// <summary>
  230. /// Is data in this vertex format compressed? Only will return true if
  231. /// all elements are compressed that can be compressed, e.g. position, uv
  232. /// or normal data. Color or skinning data is always in the same format
  233. /// (compressed), it will not be used for this check. Calculated in the
  234. /// constructor.
  235. /// </summary>
  236. public bool IsCompressed
  237. {
  238. get;
  239. private set;
  240. }
  241. #endregion
  242. #region HasNormals (Public)
  243. /// <summary>
  244. /// Has normals? Used to check if we need to compare normals for
  245. /// optimizing meshes.
  246. /// </summary>
  247. public bool HasNormals
  248. {
  249. get
  250. {
  251. for (int index = 0; index < Elements.Length; index++)
  252. {
  253. if (Elements[index].Type ==
  254. VertexElementType.Normal)
  255. {
  256. return true;
  257. }
  258. }
  259. return false;
  260. }
  261. }
  262. #endregion
  263. #region HasTangents (Public)
  264. /// <summary>
  265. /// Has tangents? Used to check if we need to generate tangents when
  266. /// importing meshes.
  267. /// </summary>
  268. public bool HasTangents
  269. {
  270. get
  271. {
  272. for (int index = 0; index < Elements.Length; index++)
  273. {
  274. if (Elements[index].Type ==
  275. VertexElementType.Tangent)
  276. {
  277. return true;
  278. }
  279. }
  280. return false;
  281. }
  282. }
  283. #endregion
  284. #region HasLightmapUVs (Public)
  285. /// <summary>
  286. /// Has lightmap UV texture coordinates? Good check for merging meshes.
  287. /// </summary>
  288. public bool HasLightmapUVs
  289. {
  290. get
  291. {
  292. for (int index = 0; index < Elements.Length; index++)
  293. {
  294. if (Elements[index].Type ==
  295. VertexElementType.LightMapUV)
  296. {
  297. return true;
  298. }
  299. }
  300. return false;
  301. }
  302. }
  303. #endregion
  304. #region Constructors
  305. /// <summary>
  306. /// Create vertex format
  307. /// </summary>
  308. /// <param name="setElements">Set elements</param>
  309. public VertexFormat(VertexElement[] setElements)
  310. {
  311. if (setElements == null) // ||
  312. //now allowed for None above: setElements.Length == 0)
  313. {
  314. throw new InvalidOperationException("You have to specify vertex" +
  315. " elements to define a VertexFormat");
  316. }
  317. // Set all elements, not allowed to be modified outside this constructor
  318. Elements = setElements;
  319. // Get length of the format in bytes and also calculate the offsets.
  320. LengthInBytes = 0;
  321. VertexElementType lastType = (VertexElementType)MathHelper.InvalidIndex;
  322. IsCompressed = false;
  323. for (int num = 0; num < Elements.Length; num++)
  324. {
  325. Elements[num].Offset = LengthInBytes;
  326. LengthInBytes += Elements[num].Size;
  327. // Ignore color and skinning elements, they are always compressed
  328. VertexElementType elementType = Elements[num].Type;
  329. if (elementType != VertexElementType.Color &&
  330. elementType != VertexElementType.SkinIndices &&
  331. elementType != VertexElementType.SkinWeights)
  332. {
  333. IsCompressed = Elements[num].IsCompressed;
  334. }
  335. // Show warning if user specified a strange format, we want to have
  336. // vertex elements sorted this way: Position, Normal, Color, TexCoords,
  337. // PointSize, Weight, MatrixIndex. Since VertexElementType is sorted
  338. // this way we just need to check if a type was going backwards.
  339. if (elementType < lastType)
  340. {
  341. Log.Warning("Your vertex format seems to be not optimal, the type " +
  342. elementType + " should be before the last type=" + lastType +
  343. ". Check out the VertexFormatType and the wiki for details: " +
  344. this);
  345. }
  346. lastType = elementType;
  347. }
  348. }
  349. /// <summary>
  350. /// Create vertex format from binary stream, will load all elements.
  351. /// </summary>
  352. public VertexFormat(BinaryReader reader)
  353. {
  354. Load(reader);
  355. }
  356. #endregion
  357. #region ISaveLoadBinary Members
  358. /// <summary>
  359. /// Load VertexFormat from a binary data stream.
  360. /// </summary>
  361. public void Load(BinaryReader reader)
  362. {
  363. // We currently only support our version, if more versions are added,
  364. // we need to do different loading code depending on the version here.
  365. int version = reader.ReadInt32();
  366. if (version != VersionNumber)
  367. {
  368. Log.InvalidVersionWarning("VertexFormat", version, VersionNumber);
  369. return;
  370. }
  371. IsCompressed = false;
  372. // Read all data in the way it was saved above
  373. int numberOfElements = reader.ReadInt32();
  374. Elements = new VertexElement[numberOfElements];
  375. for (int num = 0; num < numberOfElements; num++)
  376. {
  377. Elements[num].Load(reader);
  378. if (Elements[num].Type != VertexElementType.Color &&
  379. Elements[num].Type != VertexElementType.SkinIndices &&
  380. Elements[num].Type != VertexElementType.SkinWeights)
  381. {
  382. IsCompressed = Elements[num].IsCompressed;
  383. }
  384. }
  385. LengthInBytes = reader.ReadInt32();
  386. }
  387. /// <summary>
  388. /// Save VertexFormat, will save all vertex elements in here.
  389. /// </summary>
  390. public void Save(BinaryWriter writer)
  391. {
  392. writer.Write(VersionNumber);
  393. writer.Write(Elements.Length);
  394. foreach (VertexElement element in Elements)
  395. {
  396. element.Save(writer);
  397. }
  398. writer.Write(LengthInBytes);
  399. }
  400. #endregion
  401. #region op_Equality (Operator)
  402. /// <summary>
  403. /// Op equality
  404. /// </summary>
  405. /// <param name="a">A</param>
  406. /// <param name="b">B</param>
  407. public static bool operator ==(VertexFormat a, VertexFormat b)
  408. {
  409. if (a is VertexFormat)
  410. {
  411. return a.Equals(b);
  412. }
  413. return false;
  414. }
  415. #endregion
  416. #region op_Inequality (Operator)
  417. /// <summary>
  418. /// Op inequality
  419. /// </summary>
  420. /// <param name="a">A</param>
  421. /// <param name="b">B</param>
  422. public static bool operator !=(VertexFormat a, VertexFormat b)
  423. {
  424. if (a is VertexFormat)
  425. {
  426. return a.Equals(b) == false;
  427. }
  428. return true;
  429. }
  430. #endregion
  431. #region Equals (Public)
  432. /// <summary>
  433. /// Equals, used to check if two VertexFormats are the same.
  434. /// </summary>
  435. /// <param name="obj">Object</param>
  436. public override bool Equals(object obj)
  437. {
  438. // In the case we check against another vertex format
  439. if (obj is VertexFormat)
  440. {
  441. VertexFormat otherFormat = (VertexFormat)obj;
  442. // Then check first if the data length matches
  443. if (LengthInBytes == otherFormat.LengthInBytes &&
  444. // And if the number of elements matches
  445. Elements.Length == otherFormat.Elements.Length)
  446. {
  447. // Now check if every element of the other vertex format matches
  448. // really with our elements (incl. the same order)
  449. for (int index = 0; index < otherFormat.Elements.Length; index++)
  450. {
  451. // If one element doesn't match (we only need to check type and
  452. // size, all other properties will always be the same anyway if
  453. // type and size already match).
  454. if (otherFormat.Elements[index].Type !=
  455. Elements[index].Type) // ||
  456. //ignore size, else compression compare does not work:
  457. //otherFormat.Elements[index].Size != Elements[index].Size)
  458. {
  459. // Then the other format isn't the same obviously
  460. return false;
  461. }
  462. }
  463. // Every element matches, we can savely say this is the same format
  464. return true;
  465. }
  466. }
  467. // Else it can't be equal (obj is not even VertexFormat)
  468. return false;
  469. }
  470. #endregion
  471. #region GetHashCode (Public)
  472. /// <summary>
  473. /// Get hash code
  474. /// </summary>
  475. public override int GetHashCode()
  476. {
  477. return base.GetHashCode();
  478. }
  479. #endregion
  480. #region GetElementIndex (Public)
  481. /// <summary>
  482. /// Does this vertex format contain a specific vertex element type?
  483. /// Will return the index if found, else InvalidIndex. Index can be used
  484. /// to learn more about this element (e.g. IsCompressed, Offset, Size).
  485. /// </summary>
  486. public int GetElementIndex(VertexElementType checkType)
  487. {
  488. int index = 0;
  489. foreach (VertexElement element in Elements)
  490. {
  491. if (element.Type == checkType)
  492. {
  493. return index;
  494. }
  495. index++;
  496. }
  497. // Not found?
  498. return MathHelper.InvalidIndex;
  499. }
  500. #endregion
  501. #region ToString (Public)
  502. /// <summary>
  503. /// To string
  504. /// </summary>
  505. public override string ToString()
  506. {
  507. return "VertexFormat with " + Elements.Write() +
  508. ", LengthInBytes=" + LengthInBytes;
  509. }
  510. #endregion
  511. /// <summary>
  512. /// Tests
  513. /// </summary>
  514. internal class VertexFormatTests
  515. {
  516. #region LengthInBytes
  517. /// <summary>
  518. /// Checks if LengthInBytes returns the correct size.
  519. /// </summary>
  520. [Test]
  521. public void LengthInBytes()
  522. {
  523. // A vertex with a 3D position and UV coordinates has 5 floats.
  524. Assert.Equal(Position3DTextured.LengthInBytes, 5 * 4);
  525. // A vertex with a 2D position and UV coordinates has 4 floats.
  526. Assert.Equal(Position2DTextured.LengthInBytes, 4 * 4);
  527. // A vertex with compressed 2D position and UV coordinates has 4 shorts
  528. VertexFormat compressedPos2DTextured =
  529. new VertexFormat(new[]
  530. {
  531. new VertexElement(VertexElementType.Position2D, true),
  532. new VertexElement(VertexElementType.TextureUV, true),
  533. });
  534. Assert.Equal(compressedPos2DTextured.LengthInBytes, 4 * 2);
  535. // 7 floats for 3D position and UV and LightMap coordinates
  536. Assert.Equal(Position3DTexturedLightMap.LengthInBytes,
  537. 7 * 4);
  538. // 15 floats for 3D position and UV and LightMap coordinates with
  539. // normal and tangent vector data and skinning too (thats 60 bytes!)
  540. VertexFormat Pos3DNormalTangentLightMapSkinned =
  541. new VertexFormat(new[]
  542. {
  543. new VertexElement(VertexElementType.Position3D, false),
  544. new VertexElement(VertexElementType.Normal, false),
  545. new VertexElement(VertexElementType.Tangent, false),
  546. new VertexElement(VertexElementType.TextureUV, false),
  547. new VertexElement(VertexElementType.LightMapUV, false),
  548. // Skin data are always handled as compressed
  549. new VertexElement(VertexElementType.SkinIndices, true),
  550. new VertexElement(VertexElementType.SkinWeights, true),
  551. });
  552. Assert.Equal(Pos3DNormalTangentLightMapSkinned.LengthInBytes, 15 * 4);
  553. // 32 (8+4+4+4+4+4+4) bytes for compressed 3D position and UV and
  554. // LightMap UV with normal and tangent vector data and skinning too.
  555. VertexFormat compressedPos3DNormalTangentLightMapSkinned =
  556. new VertexFormat(new[]
  557. {
  558. new VertexElement(VertexElementType.Position3D, true),
  559. new VertexElement(VertexElementType.Normal, true),
  560. new VertexElement(VertexElementType.Tangent, true),
  561. new VertexElement(VertexElementType.TextureUV, true),
  562. new VertexElement(VertexElementType.LightMapUV, true),
  563. new VertexElement(VertexElementType.SkinIndices, true),
  564. new VertexElement(VertexElementType.SkinWeights, true),
  565. });
  566. Assert.Equal(compressedPos3DNormalTangentLightMapSkinned.LengthInBytes,
  567. 32);
  568. }
  569. #endregion
  570. #region GetElementIndex
  571. /// <summary>
  572. /// Get element index
  573. /// </summary>
  574. [Test]
  575. public void GetElementIndex()
  576. {
  577. // Use a simple vertex format
  578. VertexFormat format = Position3DTextured;
  579. Assert.Equal(format.Elements[0].Offset, 0);
  580. Assert.Equal(format.Elements[1].Offset, 12);
  581. int pos2DIndex = format.GetElementIndex(VertexElementType.Position2D);
  582. int uvIndex = format.GetElementIndex(VertexElementType.TextureUV);
  583. Assert.Equal(pos2DIndex, -1);
  584. Assert.Equal(uvIndex, 1);
  585. Assert.Equal(format.Elements[uvIndex].Offset, 12);
  586. }
  587. #endregion
  588. #region SaveAndLoad
  589. /// <summary>
  590. /// Save and load
  591. /// </summary>
  592. [Test]
  593. public void SaveAndLoad()
  594. {
  595. // Write vertex format into a memory stream
  596. MemoryStream memStream = new MemoryStream();
  597. BinaryWriter writer = new BinaryWriter(memStream);
  598. Position2DColor.Save(writer);
  599. // And read it out again (into a new stream)
  600. memStream = new MemoryStream(memStream.GetBuffer());
  601. BinaryReader reader = new BinaryReader(memStream);
  602. VertexFormat loadedFormat = new VertexFormat(reader);
  603. // Check some element data
  604. Assert.Equal(loadedFormat.LengthInBytes,
  605. Position2DColor.LengthInBytes);
  606. Assert.Equal(loadedFormat.Elements[0].Type,
  607. Position2DColor.Elements[0].Type);
  608. Assert.Equal(loadedFormat.Elements[1].Offset,
  609. Position2DColor.Elements[1].Offset);
  610. }
  611. #endregion
  612. }
  613. }
  614. }