Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AtlasTexLibrary.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Drawing;
5 using System.Collections.Generic;
6 using System.Runtime.InteropServices;
7 
8 using SiliconStudio.Core;
9 using SiliconStudio.Core.Diagnostics;
10 using SiliconStudio.TextureConverter.Requests;
11 
12 namespace SiliconStudio.TextureConverter.TexLibraries
13 {
14  /// <summary>
15  /// Allows the creation and manipulation of texture atlas.
16  /// </summary>
17  internal class AtlasTexLibrary : ITexLibrary
18  {
19  private static Logger Log = GlobalLogger.GetLogger("AtlasTexLibrary");
20 
21  /// <summary>
22  /// Initializes a new instance of the <see cref="AtlasTexLibrary"/> class.
23  /// </summary>
24  public AtlasTexLibrary() { }
25 
26  public bool CanHandleRequest(TexImage image, IRequest request)
27  {
28  if (image.GetType() != typeof(TexAtlas))
29  {
30  return false;
31  }
32 
33  TexAtlas atlas = (TexAtlas)image;
34 
35  switch (request.Type)
36  {
37  case RequestType.AtlasCreation:
38  case RequestType.AtlasExtraction:
39  case RequestType.AtlasUpdate:
40  return true;
41 
42  default:
43  return false;
44  }
45  }
46 
47  public void Execute(TexImage image, IRequest request)
48  {
49  if (image.GetType() != typeof(TexAtlas))
50  {
51  throw new TextureToolsException("The given texture must be an instance of TexAtlas.");
52  }
53 
54  TexAtlas atlas = (TexAtlas)image;
55 
56  switch (request.Type)
57  {
58  case RequestType.AtlasCreation:
59  Create(atlas, (AtlasCreationRequest)request, 0);
60  break;
61  case RequestType.AtlasExtraction:
62  Extract(atlas, (AtlasExtractionRequest)request);
63  break;
64  case RequestType.AtlasUpdate:
65  Update(atlas, (AtlasUpdateRequest)request);
66  break;
67 
68  default:
69  Log.Error("AtlasTexLibrary can't handle this request: " + request.Type);
70  throw new TextureToolsException("AtlasTexLibrary can't handle this request: " + request.Type);
71  }
72  }
73 
74 
75  public void Dispose(TexImage image)
76  {
77  Marshal.FreeHGlobal(image.Data);
78  }
79 
80 
81  public void Dispose() { }
82 
83  public void StartLibrary(TexImage image) { }
84  public void EndLibrary(TexImage image) { }
85 
86  public bool SupportBGRAOrder()
87  {
88  return true;
89  }
90 
91 
92  /// <summary>
93  /// Creates an atlas.
94  /// </summary>
95  /// <param name="atlas">The atlas.</param>
96  /// <param name="request">The request.</param>
97  /// <param name="atlasSizeIncrement">An indice used to increment the atlas size</param>
98  public void Create(TexAtlas atlas, AtlasCreationRequest request, int atlasSizeIncrement)
99  {
100  Log.Info("Creating atlas ...");
101 
102  // Initalizing Atlas : trying to determine the minimum atlas size and allocating the needed memory
103  InitalizeAtlas(atlas, request, atlasSizeIncrement);
104 
105  // Ordering textures in decreasing order : best heuristic with this algorithm.
106  if (atlasSizeIncrement == 0) OrderTexture(request);
107 
108  // Finding the best layout for the textures in the atlas
109  Node tree = PositionTextures(atlas, request);
110 
111  // One of many textures couldn't be positionned which means the atlas is too small
112  if (tree == null)
113  {
114  Marshal.FreeHGlobal(atlas.Data);
115  Create(atlas, request, atlasSizeIncrement + 1);
116  }
117  else
118  {
119  // Everything went well, we can copy the textures data into the atlas
120  CopyTexturesIntoAtlasMemory(tree, atlas);
121 
122  // Creating the atlas data
123  CreateAtlasData(tree, atlas, request.TextureList.Count);
124  }
125  }
126 
127 
128  /// <summary>
129  /// Extracts the specified TexImage from the atlas.
130  /// </summary>
131  /// <param name="atlas">The atlas.</param>
132  /// <param name="request">The request.</param>
133  private void Extract(TexAtlas atlas, AtlasExtractionRequest request)
134  {
135  if (request.Name != null)
136  {
137  Log.Info("Extracting " + request.Name + " from atlas ...");
138 
139  if (!atlas.Layout.TexList.ContainsKey(request.Name))
140  {
141  Log.Error("The request texture name " + request.Name + " doesn't exist in this atlas.");
142  throw new TextureToolsException("The request texture name " + request.Name + " doesn't exist in this atlas.");
143  }
144  request.Texture.Name = request.Name;
145  ExtractTexture(atlas, request.Texture, atlas.Layout.TexList[request.Name], request.MinimumMipMapSize);
146  }
147  else
148  {
149  Log.Info("Extracting textures from atlas ...");
150 
151  TexImage texture;
152  foreach (KeyValuePair<string, TexAtlas.TexLayout.Position> entry in atlas.Layout.TexList)
153  {
154  texture = new TexImage();
155  texture.Name = entry.Key;
156  request.Textures.Add(texture);
157  ExtractTexture(atlas, texture, entry.Value, request.MinimumMipMapSize);
158  }
159  }
160  }
161 
162 
163  /// <summary>
164  /// Updates the specified atlas with a given TexImage.
165  /// </summary>
166  /// <param name="atlas">The atlas.</param>
167  /// <param name="request">The request.</param>
168  /// <exception cref="TexLibraryException">The request texture name + request.Name + doesn't exist in this atlas.</exception>
169  private void Update(TexAtlas atlas, AtlasUpdateRequest request)
170  {
171  if (!atlas.Layout.TexList.ContainsKey(request.Name))
172  {
173  Log.Error("The given texture name " + request.Name + " doesn't exist in this atlas.");
174  throw new TextureToolsException("The given texture name " + request.Name + " doesn't exist in this atlas.");
175  }
176 
177  TexAtlas.TexLayout.Position position = atlas.Layout.TexList[request.Name];
178 
179  if (request.Texture.Width != position.Width || request.Texture.Height != position.Height)
180  {
181  Log.Error("The given texture must match the dimension of the one you want to update in the atlas.");
182  throw new TextureToolsException("The given texture must match the dimension of the one you want to update in the atlas.");
183  }
184 
185  int mipmapCount = 0;
186  int w = position.Width;
187  int h = position.Height;
188 
189  do
190  {
191  ++mipmapCount;
192  w >>= 1;
193  h >>= 1;
194  }
195  while (w >= 1 && h >= 1 && mipmapCount < atlas.MipmapCount);
196 
197  w = position.Width;
198  h = position.Height;
199  int x = position.UOffset;
200  int y = position.VOffset;
201  long subImageData, atlasData;
202  int xOffset, yOffset;
203  for (int i = 0; i < mipmapCount; ++i)
204  {
205  xOffset = (int)((Decimal)x / atlas.SubImageArray[i].Width * atlas.SubImageArray[i].RowPitch);
206  yOffset = y * atlas.SubImageArray[i].RowPitch;
207  subImageData = request.Texture.SubImageArray[i].Data.ToInt64();
208  atlasData = atlas.SubImageArray[i].Data.ToInt64();
209 
210  for (int j = 0; j < h; ++j)
211  {
212  Utilities.CopyMemory(new IntPtr(atlasData + j * atlas.SubImageArray[i].RowPitch + yOffset + xOffset), new IntPtr(subImageData + j * request.Texture.SubImageArray[i].RowPitch), request.Texture.SubImageArray[i].RowPitch);
213  }
214 
215  w = w > 1 ? w >>= 1 : w;
216  h = h > 1 ? h >>= 1 : h;
217  x = x <= 1 ? 0 : x >>= 1;
218  y = y <= 1 ? 0 : y >>= 1;
219  }
220  }
221 
222 
223  /// <summary>
224  /// Extracts the specified texture from the atlas.
225  /// </summary>
226  /// <param name="atlas">The atlas.</param>
227  /// <param name="texture">The texture that will be filled.</param>
228  /// <param name="position">The position of the texture in the atlas.</param>
229  private void ExtractTexture(TexAtlas atlas, TexImage texture, TexAtlas.TexLayout.Position position, int minimumMipmapSize)
230  {
231  texture.Format = atlas.Format;
232 
233  int x, y, w, h, rowPitch, slicePitch, mipmapCount, dataSize, offset;
234 
235  dataSize = 0;
236  mipmapCount = 0;
237  w = position.Width;
238  h = position.Height;
239 
240  do
241  {
242  Tools.ComputePitch(texture.Format, w, h, out rowPitch, out slicePitch);
243  dataSize += slicePitch;
244  ++mipmapCount;
245 
246  w >>= 1;
247  h >>= 1;
248  }
249  while (w >= minimumMipmapSize && h >= minimumMipmapSize && mipmapCount < atlas.MipmapCount);
250 
251  texture.MipmapCount = mipmapCount;
252  texture.SubImageArray = new TexImage.SubImage[mipmapCount];
253  texture.Data = Marshal.AllocHGlobal(dataSize);
254  texture.DataSize = dataSize;
255  texture.Width = position.Width;
256  texture.Height = position.Height;
257 
258  long atlasData, textureData;
259  int xOffset, yOffset;
260  IntPtr destPtr, srcPtr;
261 
262  w = position.Width;
263  h = position.Height;
264  x = position.UOffset;
265  y = position.VOffset;
266  offset = 0;
267  for (int i = 0; i < mipmapCount; ++i)
268  {
269  Tools.ComputePitch(texture.Format, w, h, out rowPitch, out slicePitch);
270 
271  texture.SubImageArray[i] = new TexImage.SubImage();
272  texture.SubImageArray[i].Data = new IntPtr(texture.Data.ToInt64() + offset);
273  texture.SubImageArray[i].DataSize = slicePitch;
274  texture.SubImageArray[i].Width = w;
275  texture.SubImageArray[i].Height = h;
276  texture.SubImageArray[i].RowPitch = rowPitch;
277  texture.SubImageArray[i].SlicePitch = slicePitch;
278 
279  atlasData = atlas.SubImageArray[i].Data.ToInt64();
280  textureData = texture.SubImageArray[i].Data.ToInt64();
281  xOffset = (int)((Decimal)x / atlas.SubImageArray[i].Width * atlas.SubImageArray[i].RowPitch);
282  yOffset = y * atlas.SubImageArray[i].RowPitch;
283 
284  for (int j = 0; j < h; ++j)
285  {
286  srcPtr = new IntPtr(atlasData + j * atlas.SubImageArray[i].RowPitch + yOffset + xOffset);
287  destPtr = new IntPtr(textureData + j * rowPitch);
288  Utilities.CopyMemory(destPtr, srcPtr, rowPitch);
289  }
290 
291  offset += slicePitch;
292 
293  w = w > 1 ? w >>= 1 : w;
294  h = h > 1 ? h >>= 1 : h;
295  x = x <= 1 ? 0 : x >>= 1;
296  y = y <= 1 ? 0 : y >>= 1;
297  }
298 
299 
300  texture.RowPitch = texture.SubImageArray[0].RowPitch;
301  texture.SlicePitch = texture.SubImageArray[0].SlicePitch;
302 
303  texture.DisposingLibrary = this;
304  }
305 
306 
307  /// <summary>
308  /// Initalizes the atlas. Predictes the minimum size required by the atlas according to the textures
309  /// </summary>
310  /// <param name="atlas">The atlas.</param>
311  /// <param name="request">The request.</param>
312  private void InitalizeAtlas(TexAtlas atlas, AtlasCreationRequest request, int atlasSizeIncrement)
313  {
314  // Calculating the total number of pixels of every texture to be included
315  int pixelCount = 0;
316  bool hasMipMap = false;
317  foreach (TexImage texture in request.TextureList)
318  {
319  pixelCount += texture.Width * texture.Height;
320  if (texture.MipmapCount > 1) hasMipMap = true;
321  }
322 
323  // setting the new size to the atlas
324  int alpha = (int)Math.Log(pixelCount, 2) + 1 + atlasSizeIncrement;
325  atlas.Width = (int)Math.Pow(2, alpha / 2);
326  atlas.Height = (int)Math.Pow(2, alpha - alpha / 2);
327 
328  // If we want a square texture, we compute the max of the width and height and assign it to both
329  if (request.ForceSquaredAtlas)
330  {
331  int size = Math.Max(atlas.Width, atlas.Height);
332  atlas.Width = size;
333  atlas.Height = size;
334  }
335 
336  // Setting the TexImage features
337  int rowPitch, slicePitch;
338  Tools.ComputePitch(atlas.Format, atlas.Width, atlas.Height, out rowPitch, out slicePitch);
339  atlas.RowPitch = rowPitch;
340  atlas.SlicePitch = slicePitch;
341 
342  // Allocating memory
343  if (hasMipMap)
344  {
345  int dataSize = 0;
346  int mipmapCount = 0;
347  int w, h;
348  List<TexImage.SubImage> subImages = new List<TexImage.SubImage>();
349 
350  w = atlas.Width;
351  h = atlas.Height;
352 
353  while (w != 1 || h != 1)
354  {
355  Tools.ComputePitch(atlas.Format, w, h, out rowPitch, out slicePitch);
356  subImages.Add(new TexImage.SubImage()
357  {
358  Data = IntPtr.Zero,
359  DataSize = slicePitch,
360  Width = w,
361  Height = h,
362  RowPitch = rowPitch,
363  SlicePitch = slicePitch,
364  });
365 
366  dataSize += slicePitch;
367  ++mipmapCount;
368 
369  w = w > 1 ? w >>= 1 : w;
370  h = h > 1 ? h >>= 1 : h;
371  }
372 
373  atlas.DataSize = dataSize;
374  atlas.Data = Marshal.AllocHGlobal(atlas.DataSize);
375 
376  atlas.SubImageArray = subImages.ToArray();
377 
378  int offset = 0;
379  for (int i = 0; i < atlas.SubImageArray.Length; ++i)
380  {
381  atlas.SubImageArray[i].Data = new IntPtr(atlas.Data.ToInt64() + offset);
382  offset += atlas.SubImageArray[i].DataSize;
383  }
384  atlas.MipmapCount = mipmapCount;
385  }
386  else
387  {
388  atlas.DataSize = atlas.SlicePitch;
389  atlas.Data = Marshal.AllocHGlobal(atlas.DataSize);
390 
391  atlas.SubImageArray[0].Data = atlas.Data;
392  atlas.SubImageArray[0].DataSize = atlas.DataSize;
393  atlas.SubImageArray[0].Width = atlas.Width;
394  atlas.SubImageArray[0].Height = atlas.Height;
395  atlas.SubImageArray[0].RowPitch = rowPitch;
396  atlas.SubImageArray[0].SlicePitch = slicePitch;
397  }
398 
399  atlas.DisposingLibrary = this;
400  }
401 
402 
403  /// <summary>
404  /// Orders the textures list in decreasing size.
405  /// </summary>
406  /// <param name="request">The request.</param>
407  private void OrderTexture(AtlasCreationRequest request)
408  {
409  QuickSort(request.TextureList, 0, request.TextureList.Count - 1);
410  }
411 
412 
413  /// <summary>
414  /// QuickSort algorithm.
415  /// </summary>
416  /// <param name="list">The list.</param>
417  /// <param name="left">The left.</param>
418  /// <param name="right">The right.</param>
419  private void QuickSort(List<TexImage> list, int left, int right)
420  {
421  int i = left;
422  int j = right;
423  double pivotValue = ((left + right) / 2);
424  int x = list[(int)pivotValue].DataSize;
425  TexImage w;
426  while (i <= j)
427  {
428  while (list[i].DataSize > x)
429  {
430  ++i;
431  }
432  while (x > list[j].DataSize)
433  {
434  --j;
435  }
436  if (i <= j)
437  {
438  w = list[i];
439  list[i++] = list[j];
440  list[j--] = w;
441  }
442  }
443  if (left < j)
444  {
445  QuickSort(list, left, j);
446  }
447  if (i < right)
448  {
449  QuickSort(list, i, right);
450  }
451  }
452 
453 
454  /// <summary>
455  /// Determines the positions of the textures in the Atlas.
456  /// </summary>
457  /// <param name="atlas">The atlas.</param>
458  /// <param name="request">The request.</param>
459  /// <returns>The binary tree containing the positionned textures or null if the atlas is too small.</returns>
460  private Node PositionTextures(TexAtlas atlas, AtlasCreationRequest request)
461  {
462  Node root = new Node(0, 0, atlas.Width, atlas.Height);
463 
464  foreach (TexImage texture in request.TextureList)
465  {
466  if (!Insert(root, texture))
467  {
468  return null;
469  }
470  }
471 
472  return root;
473  }
474 
475 
476  /// <summary>
477  /// Inserts the specified node.
478  /// </summary>
479  /// <param name="node">The node.</param>
480  /// <param name="tex">The tex.</param>
481  /// <returns></returns>
482  private bool Insert(Node node, TexImage tex)
483  {
484  if (node.IsEmpty() && node.IsLeaf())
485  {
486  if (node.Width < tex.Width || node.Height < tex.Height)
487  {
488  return false;
489  }
490  else if (node.Width == tex.Width && node.Height == tex.Height)
491  {
492  node.Texture = (TexImage)tex.Clone(false);
493  return true;
494  }
495 
496  if (node.Width - tex.Width >= node.Height - tex.Height)
497  {
498  node.Left = new Node(node.X, node.Y, tex.Width, node.Height);
499  node.Right = new Node(node.X + tex.Width, node.Y, node.Width - tex.Width, node.Height);
500  return Insert(node.Left, tex);
501  }
502  else
503  {
504  node.Left = new Node(node.X, node.Y, node.Width, tex.Height);
505  node.Right = new Node(node.X, node.Y + tex.Height, node.Width, node.Height - tex.Height);
506  return Insert(node.Left, tex);
507  }
508  }
509  else if (!node.IsLeaf())
510  {
511  return Insert(node.Left, tex) || Insert(node.Right, tex);
512  }
513  else
514  {
515  return false;
516  }
517  }
518 
519 
520  /// <summary>
521  /// Copies the textures data drom the binary tree into atlas memory at the right position.
522  /// </summary>
523  /// <param name="node">The node.</param>
524  /// <param name="atlas">The atlas.</param>
525  private void CopyTexturesIntoAtlasMemory(Node node, TexAtlas atlas)
526  {
527  if (!node.IsEmpty() && node.IsLeaf())
528  {
529  long atlasData = atlas.Data.ToInt64();
530  long textureData = node.Texture.Data.ToInt64();
531  //int xOffset = (int)((Decimal)node.X / atlas.Width * atlas.RowPitch);
532  //int yOffset = node.Y * atlas.RowPitch;
533  //IntPtr destPtr, srcPtr;
534 
535  int x, y, xOffset, yOffset;
536  IntPtr destPtr, srcPtr;
537 
538  x = node.X;
539  y = node.Y;
540 
541  for (int i = 0; i < node.Texture.MipmapCount && i < atlas.MipmapCount; ++i)
542  {
543  atlasData = atlas.SubImageArray[i].Data.ToInt64();
544  textureData = node.Texture.SubImageArray[i].Data.ToInt64();
545  xOffset = (int)((Decimal)x / (Decimal)atlas.SubImageArray[i].Width * atlas.SubImageArray[i].RowPitch);
546  yOffset = y * atlas.SubImageArray[i].RowPitch;
547 
548  /*if (node.Texture.SubImageArray[i].Width == 3)
549  {
550  //xOffset += 4;
551  //node.Texture.SubImageArray[i].RowPitch += 4;
552  Console.WriteLine(node.Texture.SubImageArray[i].RowPitch); ///////////////----------------------------------------------------------------------------------------
553  }*/
554  for (int j = 0; j < node.Texture.SubImageArray[i].Height; ++j)
555  {
556  destPtr = new IntPtr(atlasData + j * atlas.SubImageArray[i].RowPitch + yOffset + xOffset);
557  srcPtr = new IntPtr(textureData + j * node.Texture.SubImageArray[i].RowPitch);
558  Utilities.CopyMemory(destPtr, srcPtr, node.Texture.SubImageArray[i].RowPitch);
559  }
560 
561  x = x <= 1 ? 0 : x >>= 1;
562  y = y <= 1 ? 0 : y >>= 1;
563  }
564 
565  node.Texture.Dispose();
566  }
567  else if (!node.IsLeaf())
568  {
569  CopyTexturesIntoAtlasMemory(node.Left, atlas);
570  CopyTexturesIntoAtlasMemory(node.Right, atlas);
571  }
572  }
573 
574 
575  /// <summary>
576  /// Creates the atlas data.
577  /// </summary>
578  /// <param name="node">The node.</param>
579  /// <param name="atlas">The atlas.</param>
580  /// <param name="textureCount">The texture count.</param>
581  private void CreateAtlasData(Node node, TexAtlas atlas, int textureCount)
582  {
583  atlas.Layout.TexList.Clear();
584  UpdateAtlasData(node, atlas);
585  }
586 
587 
588  /// <summary>
589  /// Updates the atlas data.
590  /// </summary>
591  /// <param name="node">The node.</param>
592  /// <param name="atlas">The atlas.</param>
593  private void UpdateAtlasData(Node node, TexAtlas atlas)
594  {
595  if (!node.IsEmpty() && node.IsLeaf())
596  {
597  if (atlas.Layout.TexList.ContainsKey(node.Texture.Name) || node.Texture.Name.Equals("")) node.Texture.Name = node.Texture.Name + "_x" + node.X + "_y" + node.Y;
598  atlas.Layout.TexList.Add(node.Texture.Name, new TexAtlas.TexLayout.Position(node.X, node.Y, node.Width, node.Height));
599  }
600  else if (!node.IsLeaf())
601  {
602  UpdateAtlasData(node.Left, atlas);
603  UpdateAtlasData(node.Right, atlas);
604  }
605  }
606 
607 
608  /// <summary>
609  /// A node containing position information, a TexImage and 2 other nodes, used to represent a binary tree of a texture atlas
610  /// </summary>
611  private class Node
612  {
613  public TexImage Texture;
614  public Node Left, Right;
615 
616  /// <summary>
617  /// The position and size of the available space of the node into the Atlas
618  /// </summary>
619  public int X, Y, Width, Height;
620 
621  public Node(int x, int y, int width, int height)
622  {
623  Texture = null;
624  Left = null;
625  Right = null;
626  Width = width;
627  Height = height;
628  X = x;
629  Y = y;
630  }
631 
632  public bool IsLeaf()
633  {
634  return Left == null && Right == null;
635  }
636 
637  public bool IsEmpty()
638  {
639  return Texture == null;
640  }
641  }
642 
643  }
644 }
Request to extract one or every textures from an atlas.
_In_ size_t _In_ DXGI_FORMAT _In_ size_t _In_ float size_t y
Definition: DirectXTexP.h:191
int MinimumMipMapSize
The minimum size of the smallest mipmap.
Base implementation for ILogger.
Definition: Logger.cs:10
bool ForceSquaredAtlas
The boolean to decide whether the atlas will be squared.
List< TexImage > TextureList
The texture list that will populate the atlas.
Creates a new file, always. If a file exists, the function overwrites the file, clears the existing a...
Same as Deferred mode, except sprites are sorted by texture prior to drawing. This can improve perfor...
string Name
The name of the texture to replace in the atlas.
Request to create an atlas from a texture list.
_In_ size_t _In_ size_t size
Definition: DirectXTexP.h:175
Output message to log right away.