Revision: 2196
Author: mike.popoloski
Date: Fri Mar 23 16:09:34 2012
Log: Added texture atlas support.
http://code.google.com/p/slimdx/source/detail?r=2196
Added:
/branches/lite/SlimDX.Toolkit/Drawing/IRectanglePacker.cs
/branches/lite/SlimDX.Toolkit/Drawing/SimpleHeightPacker.cs
/branches/lite/SlimDX.Toolkit/Drawing/TextureAtlas.cs
/branches/lite/source/dxgi/DXGIExtensionMethods.cpp
/branches/lite/source/dxgi/DXGIExtensionMethods.h
Modified:
/branches/lite/SlimDX.Toolkit/SlimDX.Toolkit.csproj
/branches/lite/build/SlimDX.vcxproj
/branches/lite/build/SlimDX.vcxproj.filters
=======================================
--- /dev/null
+++ /branches/lite/SlimDX.Toolkit/Drawing/IRectanglePacker.cs Fri Mar 23
16:09:34 2012
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Drawing;
+
+namespace SlimDX.Toolkit
+{
+ /// <summary>
+ /// Defines an interface for rectangle packing algorithms.
+ /// </summary>
+ public interface IRectanglePacker
+ {
+ /// <summary>
+ /// Defines the area of the packing region.
+ /// </summary>
+ /// <param name="width">The width of the packing region.</param>
+ /// <param name="height">The height of the packing region.</param>
+ void DefineArea(int width, int height);
+
+ /// <summary>
+ /// Attempts to pack the given rectangle into the region.
+ /// </summary>
+ /// <param name="width">The width of the rectangle to pack.</param>
+ /// <param name="height">The height of the rectangle to
pack.</param>
+ /// <param name="placement">Returns the (x,y) coordinates of the
packed location, if the method succeeds.</param>
+ /// <returns><c>true</c> if the rectangle was successfully packed;
otherwise, <c>false</c>.</returns>
+ bool TryPack(int width, int height, out Point placement);
+ }
+}
=======================================
--- /dev/null
+++ /branches/lite/SlimDX.Toolkit/Drawing/SimpleHeightPacker.cs Fri Mar 23
16:09:34 2012
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Drawing;
+
+namespace SlimDX.Toolkit
+{
+ /// <summary>
+ /// Performs rectangle packing by tracking heights and attempting to
place each rectangle at the lowest Y-position possible.
+ /// </summary>
+ public class SimpleHeightPacker : IRectanglePacker
+ {
+ int totalWidth;
+ int totalHeight;
+ int[] heights;
+
+ /// <summary>
+ /// Initializes a new instance of the <see
cref="SimpleHeightPacker"/> class.
+ /// </summary>
+ public SimpleHeightPacker()
+ {
+ }
+
+ /// <summary>
+ /// Defines the area of the packing region.
+ /// </summary>
+ /// <param name="width">The width of the packing region.</param>
+ /// <param name="height">The height of the packing region.</param>
+ public void DefineArea(int width, int height)
+ {
+ totalWidth = width;
+ totalHeight = height;
+
+ heights = new int[totalWidth];
+ }
+
+ /// <summary>
+ /// Attempts to pack the given rectangle into the region.
+ /// </summary>
+ /// <param name="width">The width of the rectangle to pack.</param>
+ /// <param name="height">The height of the rectangle to
pack.</param>
+ /// <param name="placement">Returns the (x,y) coordinates of the
packed location, if the method succeeds.</param>
+ /// <returns>
+ /// <c>true</c> if the rectangle was successfully packed;
otherwise, <c>false</c>.
+ /// </returns>
+ public bool TryPack(int width, int height, out Point placement)
+ {
+ // a rectangle bigger than the whole area will obviously not
fit
+ placement = Point.Empty;
+ if (width > totalWidth || height > totalHeight)
+ return false;
+
+ int currentMax = FindMax(0, width);
+ int currentMin = currentMax;
+ int minX = 0;
+
+ // find the lowest Y value we can fit into nicely
+ for (int i = 1; i < totalWidth - width; i++)
+ {
+ if (heights[i + width - 1] >= currentMax)
+ currentMax = heights[i + width - 1];
+ else if (heights[i - 1] == currentMax)
+ {
+ currentMax = FindMax(i, width);
+ if (currentMax < currentMin)
+ {
+ currentMin = currentMax;
+ minX = i;
+ }
+ }
+ }
+
+ if (currentMin + height > totalHeight)
+ return false;
+
+ // update the heights for the new area we're adding to
+ for (int i = 0; i < width; ++i)
+ heights[minX + i] = height;
+
+ placement = new Point(minX, currentMin);
+ return false;
+ }
+
+ int FindMax(int startX, int width)
+ {
+ int currentMax = heights[startX];
+ for (int i = 1; i < width; ++i)
+ currentMax = Math.Max(currentMax, heights[startX + i]);
+
+ return currentMax;
+ }
+ }
+}
=======================================
--- /dev/null
+++ /branches/lite/SlimDX.Toolkit/Drawing/TextureAtlas.cs Fri Mar 23
16:09:34 2012
@@ -0,0 +1,225 @@
+using System;
+using System.Drawing;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using Device = SlimDX.Direct3D11.Device;
+
+namespace SlimDX.Toolkit
+{
+ /// <summary>
+ /// Manages a collection of textures packed into one sheet for
efficiency.
+ /// </summary>
+ public class TextureAtlas : IDisposable
+ {
+ int count;
+ int sheetWidth;
+ int sheetHeight;
+ int dirtyCount;
+ int gutterWidth;
+ int formatSize;
+ bool shouldFreeze;
+ bool frozen;
+ Rectangle dirtyArea;
+ AlignedArray<RectangleF> coordinates;
+ DataStream textureData;
+ Texture2D texture;
+ ShaderResourceView textureView;
+ IRectanglePacker packer;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TextureAtlas"/>
class.
+ /// </summary>
+ /// <param name="device">The device used to create hardware
resources.</param>
+ /// <param name="sheetWidth">Width of the sheet, in pixels.</param>
+ /// <param name="sheetHeight">Height of the sheet, in
pixels.</param>
+ /// <param name="maxTextureCount">The maximum number of textures
that can be stored in the atlas. Must be less than 65535.</param>
+ /// <param name="gutterWidth">Width of the gutter region
surrounding each texture.</param>
+ /// <param name="format">The pixel format of the atlas
texture.</param>
+ /// <param name="packer">The packing algorithm to use to optimally
store textures. If <c>null</c>, a default packer will be used.</param>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref
name="device"/> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Thrown if
<paramref name="sheetWidth"/>, <paramref name="sheetHeight"/>, or <paramref
name="maxTextureCount"/> is not in a valid range of values.</exception>
+ public TextureAtlas(Device device, int sheetWidth = 512, int
sheetHeight = 512, int maxTextureCount = 2048, int gutterWidth = 1, Format
format = Format.R8G8B8A8_UNorm_SRGB, IRectanglePacker packer = null)
+ {
+ if (device == null)
+ throw new ArgumentNullException("device");
+ if (sheetWidth <= 0)
+ throw new ArgumentOutOfRangeException("sheetWidth",
sheetWidth, "Size of the texture atlas must be positive.");
+ if (sheetHeight <= 0)
+ throw new ArgumentOutOfRangeException("sheetHeight",
sheetHeight, "Size of the texture atlas must be positive.");
+ if (maxTextureCount <= 0 || maxTextureCount >= 65535)
+ throw new ArgumentOutOfRangeException("maxTextureCount",
maxTextureCount, "Maximum texture count must be between 0 and 65535.");
+
+ this.sheetWidth = sheetWidth;
+ this.sheetHeight = sheetHeight;
+ this.gutterWidth = gutterWidth;
+ this.packer = packer ?? new SimpleHeightPacker();
+ coordinates = new AlignedArray<RectangleF>(maxTextureCount);
+
+ // create space for all the texture data; use native memory to
avoid the GC
+ formatSize = format.SizeInBytes();
+ int textureSize = sheetWidth * sheetHeight * formatSize;
+ textureData = new DataStream(textureSize, true, true);
+ for (int i = 0; i < textureSize; i++)
+ textureData.WriteByte(0);
+
+ texture = new Texture2D(device, new Texture2DDescription
+ {
+ Width = sheetWidth,
+ Height = sheetHeight,
+ ArraySize = 1,
+ MipLevels = 1,
+ Format = format,
+ SampleDescription = new SampleDescription(1, 0),
+ Usage = ResourceUsage.Default,
+ BindFlags = BindFlags.ShaderResource
+ });
+
+ textureView = new ShaderResourceView(device, texture);
+ }
+
+ /// <summary>
+ /// Adds a texture to the atlas.
+ /// </summary>
+ /// <param name="width">The width of the texture.</param>
+ /// <param name="height">The height of the texture.</param>
+ /// <param name="pixelData">The pixel data.</param>
+ /// <param name="rowPitch">The number of bytes in a row, including
padding.</param>
+ /// <param name="bytesPerPixel">The number of bytes per pixel in
the texture.</param>
+ /// <returns>A handle that can be used to retrieve texture
coordinates for the stored texture, or -1 if the texture cannot fit into
the atlas.</returns>
+ /// <exception cref="InvalidOperationException">Thrown if the
atlas has been frozen.</exception>
+ public int AddTexture(int width, int height, DataStream pixelData,
int rowPitch, int bytesPerPixel)
+ {
+ if (shouldFreeze || frozen)
+ throw new InvalidOperationException("Cannot add a texture
to an atlas that has been frozen.");
+ if (count >= coordinates.Length)
+ return -1;
+
+ // pad the width and height with a gutter region to prevent
blending artifacts
+ Point position;
+ if (!packer.TryPack(width + (gutterWidth * 2), height +
(gutterWidth * 2), out position))
+ return -1;
+
+ unsafe
+ {
+ var coords = (RectangleF*)coordinates[count];
+ coords->X = (position.X + gutterWidth) / (float)sheetWidth;
+ coords->Y = (position.Y + gutterWidth) /
(float)sheetHeight;
+ coords->Width = width / (float)sheetWidth;
+ coords->Height = height / (float)sheetHeight;
+
+ // blit the pixel data into the texture, making sure we
don't overrun the bounds of the atlas texture
+ int bytesToCopy = Math.Min(bytesPerPixel, formatSize);
+ position.X += gutterWidth;
+ position.Y += gutterWidth;
+ for (int i = 0; i < height && i < sheetHeight -
position.Y; i++)
+ {
+ byte* src = (byte*)pixelData.DataPointer + (i *
rowPitch);
+ byte* dest = (byte*)textureData.DataPointer +
((position.Y + i) * sheetWidth + position.X) * formatSize;
+
+ // copy each pixel into the texture, only copying the
number of bytes that we have support for
+ for (int j = 0; j < width && j < sheetWidth -
position.X; j++)
+ {
+ for (int k = 0; k < bytesToCopy; k++)
+ dest[j * formatSize + k] = src[j *
bytesPerPixel + k];
+ }
+ }
+ }
+
+ // update the dirty rect to be flushed to device texture
+ if (dirtyCount == 0)
+ {
+ dirtyArea.X = position.X;
+ dirtyArea.Y = position.Y;
+ dirtyArea.Width = Math.Min(position.X + width, sheetWidth)
- dirtyArea.X;
+ dirtyArea.Height = Math.Min(position.Y + height,
sheetHeight) - dirtyArea.Y;
+ }
+ else
+ {
+ dirtyArea.X = Math.Min(dirtyArea.X, position.X);
+ dirtyArea.Y = Math.Min(dirtyArea.Y, position.Y);
+ dirtyArea.Width = Math.Min(Math.Max(dirtyArea.Right,
position.X + width), sheetWidth) - dirtyArea.X;
+ dirtyArea.Height = Math.Min(Math.Max(dirtyArea.Bottom,
position.Y + height), sheetHeight) - dirtyArea.Y;
+ }
+
+ dirtyCount++;
+ return count++;
+ }
+
+ /// <summary>
+ /// Gets the texture coordinates for the given texture stored in
the atlas.
+ /// </summary>
+ /// <param name="texture">The ID of the texture whose coordinates
are to be retrieved.</param>
+ /// <returns>The normalized texture coordinates.</returns>
+ public RectangleF GetCoordinates(int texture)
+ {
+ unsafe { return *(RectangleF*)coordinates[texture]; }
+ }
+
+ /// <summary>
+ /// Freezes the texture atlas, preventing further additions and
allowing the local copy
+ /// of the texture memory to be released.
+ /// </summary>
+ public void Freeze()
+ {
+ // freeze the next time we flush
+ shouldFreeze = true;
+ }
+
+ /// <summary>
+ /// Binds the texture atlas as a pixel shader resource.
+ /// </summary>
+ /// <param name="context">The rendering context.</param>
+ /// <param name="slot">The resource slot to bind.</param>
+ public void Bind(DeviceContext context, int slot = 0)
+ {
+ context.PixelShader.SetShaderResource(textureView, slot);
+ }
+
+ /// <summary>
+ /// Flushes the texture memory to the graphics device. This must
be called before the texture atlas can be used for rendering operations.
+ /// </summary>
+ /// <param name="context">The rendering context.</param>
+ public void Flush(DeviceContext context)
+ {
+ if (frozen)
+ return;
+
+ if (dirtyCount > 0 && dirtyArea.Right > dirtyArea.Left &&
dirtyArea.Bottom > dirtyArea.Top)
+ {
+ // upload the texture changes to the GPU
+ var region = new ResourceRegion(dirtyArea.Left,
dirtyArea.Top, 0, dirtyArea.Right, dirtyArea.Bottom, 1);
+ textureData.Position = (region.Top * sheetWidth +
region.Left) * formatSize;
+ context.UpdateSubresource(new DataBox(sheetWidth *
formatSize, 0, textureData), texture, 0, region);
+ }
+
+ dirtyCount = 0;
+ if (shouldFreeze)
+ {
+ // the sheet is now static; save memory
+ frozen = true;
+ textureData.Dispose();
+ textureData = null;
+ }
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing,
releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ if (textureView != null)
+ textureView.Dispose();
+ if (texture != null)
+ texture.Dispose();
+ if (textureData != null)
+ textureData.Dispose();
+ if (coordinates != null)
+ coordinates.Dispose();
+
+ textureView = null;
+ texture = null;
+ textureData = null;
+ coordinates = null;
+ }
+ }
+}
=======================================
--- /dev/null
+++ /branches/lite/source/dxgi/DXGIExtensionMethods.cpp Fri Mar 23 16:09:34
2012
@@ -0,0 +1,38 @@
+/*
+* Copyright (c) 2007-2012 SlimDX Group
+*
+* Permission is hereby granted, free of charge, to any person obtaining a
copy
+* of this software and associated documentation files (the "Software"), to
deal
+* in the Software without restriction, including without limitation the
rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+#include "stdafx.h"
+
+#include "../Utilities.h"
+
+#include "Enums.h"
+#include "DXGIExtensionMethods.h"
+
+namespace SlimDX
+{
+namespace DXGI
+{
+ int DXGIExtensions::SizeInBytes(Format format)
+ {
+ return Utilities::SizeOfFormatElement(static_cast<DXGI_FORMAT>(format))
/ 8;
+ }
+}
+}
=======================================
--- /dev/null
+++ /branches/lite/source/dxgi/DXGIExtensionMethods.h Fri Mar 23 16:09:34
2012
@@ -0,0 +1,39 @@
+/*
+* Copyright (c) 2007-2012 SlimDX Group
+*
+* Permission is hereby granted, free of charge, to any person obtaining a
copy
+* of this software and associated documentation files (the "Software"), to
deal
+* in the Software without restriction, including without limitation the
rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+#pragma once
+
+namespace SlimDX
+{
+ namespace DXGI
+ {
+ [System::Runtime::CompilerServices::ExtensionAttribute]
+ public ref class DXGIExtensions sealed
+ {
+ private:
+ DXGIExtensions() { }
+
+ public:
+ [System::Runtime::CompilerServices::ExtensionAttribute]
+ static int SizeInBytes(Format format);
+ };
+ }
+}
=======================================
--- /branches/lite/SlimDX.Toolkit/SlimDX.Toolkit.csproj Thu Mar 22 19:09:22
2012
+++ /branches/lite/SlimDX.Toolkit/SlimDX.Toolkit.csproj Fri Mar 23 16:09:34
2012
@@ -46,7 +46,10 @@
<ItemGroup>
<Compile Include="Drawing\CommonStates.cs" />
<Compile Include="Drawing\ConstantBuffer.cs" />
+ <Compile Include="Drawing\IRectanglePacker.cs" />
+ <Compile Include="Drawing\SimpleHeightPacker.cs" />
<Compile Include="Drawing\StateSaver.cs" />
+ <Compile Include="Drawing\TextureAtlas.cs" />
<Compile Include="Fonts\CreateOptions.cs" />
<Compile Include="Fonts\Font.cs" />
<Compile Include="Fonts\Geometry\GlyphVertex.cs" />
=======================================
--- /branches/lite/build/SlimDX.vcxproj Mon Mar 19 14:40:29 2012
+++ /branches/lite/build/SlimDX.vcxproj Fri Mar 23 16:09:34 2012
@@ -248,6 +248,7 @@
<ClCompile Include="..\source\directwrite\ITextRenderer.cpp" />
<ClCompile Include="..\source\directwrite\Strikethrough.cpp" />
<ClCompile Include="..\source\directwrite\Underline.cpp" />
+ <ClCompile Include="..\source\dxgi\DXGIExtensionMethods.cpp" />
<ClCompile Include="..\source\multimedia\AdpcmWaveFormat.cpp" />
<ClCompile Include="..\source\multimedia\XWMAStream.cpp" />
<ClCompile Include="..\source\ObjectTable.cpp" />
@@ -474,6 +475,7 @@
<ClInclude Include="..\source\directwrite\ITextRenderer.h" />
<ClInclude Include="..\source\directwrite\Strikethrough.h" />
<ClInclude Include="..\source\directwrite\Underline.h" />
+ <ClInclude Include="..\source\dxgi\DXGIExtensionMethods.h" />
<ClInclude Include="..\source\Enums.h" />
<ClInclude Include="..\source\InternalHelpers.h" />
<ClInclude Include="..\source\multimedia\AdpcmWaveFormat.h" />
=======================================
--- /branches/lite/build/SlimDX.vcxproj.filters Sat Mar 17 17:27:04 2012
+++ /branches/lite/build/SlimDX.vcxproj.filters Fri Mar 23 16:09:34 2012
@@ -904,6 +904,9 @@
<ClCompile Include="..\source\direct3d11\TextureLoaderDDS.cpp">
<Filter>Direct3D11\Texture</Filter>
</ClCompile>
+ <ClCompile Include="..\source\dxgi\DXGIExtensionMethods.cpp">
+ <Filter>DXGI</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\source\auto_array.h">
@@ -1645,6 +1648,9 @@
<ClInclude Include="..\source\direct3d11\TextureLoader.h">
<Filter>Direct3D11\Texture</Filter>
</ClInclude>
+ <ClInclude Include="..\source\dxgi\DXGIExtensionMethods.h">
+ <Filter>DXGI</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\source\directwrite\ToDo.txt">