unsafe static void TestQuickInlining() { { var pool = new PassthroughArrayPool <double>(); var intPool = new PassthroughArrayPool <int>(); QuickSet <double, Array <double>, Array <int>, PrimitiveComparer <double> > .Create(pool, intPool, 2, 3, out var set); set.AddUnsafely(5); var item = set[0]; Console.WriteLine($"Managed Item: {item}"); var comparer = default(PrimitiveComparer <double>); var hash = comparer.Hash(ref item); Console.WriteLine($"Hash: {hash}"); } { var pool = new BufferPool().SpecializeFor <int>(); QuickSet <int, Buffer <int>, Buffer <int>, PrimitiveComparer <int> > .Create(pool, pool, 2, 3, out var set); set.AddUnsafely(5); var item = set[0]; pool.Raw.Clear(); } }
void AddDigit(ref double value, ref double multiplier, ref PassthroughArrayPool <char> pool) { var digit = (int)((value * multiplier) % 10); characters.Add(GetCharForDigit(digit), pool); value -= digit / multiplier; multiplier *= 10; }
public TextBuilder Append(double value, int decimalCount) { //This is a bit of a throwaway implementation and is far from the fastest or numerically best implementation, //but it is fairly simple and it doesn't matter very much. const double minimumDoubleMagnitude = 2.22507385850720138309023271733240406421921598046233e-308; bool negative = value < 0; if (negative) { value = -value; } var pool = new PassthroughArrayPool <char>(); if (value <= minimumDoubleMagnitude) { //Don't bother with signed zeroes. characters.Add('0', pool); return(this); } if (negative) { characters.Add('-', pool); } value = Math.Round(value, decimalCount); var place = (int)Math.Floor(Math.Log10(value)); var multiplier = Math.Pow(0.1, place); var epsilon = Math.Pow(0.1, decimalCount); for (int i = place; i >= 0; --i) { AddDigit(ref value, ref multiplier, ref pool); } if (value > epsilon) { characters.Add('.', pool); for (int i = -1; i > place; --i) { characters.Add('0', pool); } do { AddDigit(ref value, ref multiplier, ref pool); } while (value > epsilon); } return(this); }
public Input(Window window) { this.window = window.window; this.window.KeyDown += KeyDown; this.window.KeyUp += KeyUp; this.window.MouseDown += MouseDown; this.window.MouseUp += MouseUp; this.window.MouseWheel += MouseWheel; var keyPool = new PassthroughArrayPool <Key>(); var mouseButtonPool = new PassthroughArrayPool <MouseButton>(); var intPool = new PassthroughArrayPool <int>(); MouseButtonSet.Create(mouseButtonPool, intPool, 3, 3, out anyDownedButtons); MouseButtonSet.Create(mouseButtonPool, intPool, 3, 3, out downedButtons); MouseButtonSet.Create(mouseButtonPool, intPool, 3, 3, out previousDownedButtons); KeySet.Create(keyPool, intPool, 3, 3, out anyDownedKeys); KeySet.Create(keyPool, intPool, 3, 3, out downedKeys); KeySet.Create(keyPool, intPool, 3, 3, out previousDownedKeys); }
public void End() { anyDownedKeys.Clear(); anyDownedButtons.Clear(); previousDownedKeys.Clear(); previousDownedButtons.Clear(); var keyPool = new PassthroughArrayPool <Key>(); var mouseButtonPool = new PassthroughArrayPool <MouseButton>(); var intPool = new PassthroughArrayPool <int>(); for (int i = 0; i < downedKeys.Count; ++i) { previousDownedKeys.Add(downedKeys[i], keyPool, intPool); } for (int i = 0; i < downedButtons.Count; ++i) { previousDownedButtons.Add(downedButtons[i], mouseButtonPool, intPool); } ScrolledDown = 0; ScrolledUp = 0; }
public Input(Window window) { this.window = window.window; this.window.KeyDown += KeyDown; this.window.KeyUp += KeyUp; this.window.MouseDown += MouseDown; this.window.MouseUp += MouseUp; this.window.MouseWheel += MouseWheel; this.window.KeyPress += KeyPress; var keyPool = new PassthroughArrayPool <Key>(); var mouseButtonPool = new PassthroughArrayPool <MouseButton>(); var intPool = new PassthroughArrayPool <int>(); MouseButtonSet.Create(mouseButtonPool, intPool, 3, 3, out anyDownedButtons); MouseButtonSet.Create(mouseButtonPool, intPool, 3, 3, out downedButtons); MouseButtonSet.Create(mouseButtonPool, intPool, 3, 3, out previousDownedButtons); KeySet.Create(keyPool, intPool, 3, 3, out anyDownedKeys); KeySet.Create(keyPool, intPool, 3, 3, out downedKeys); KeySet.Create(keyPool, intPool, 3, 3, out previousDownedKeys); QuickList <char, Array <char> > .Create(new PassthroughArrayPool <char>(), 32, out TypedCharacters); }
public static void TestChurnStability() { var allocator = new Allocator(2048); var random = new Random(5); ulong idCounter = 0; var pool = new PassthroughArrayPool <ulong>(); QuickList <ulong, Array <ulong> > .Create(pool, 8, out var allocatedIds); QuickList <ulong, Array <ulong> > .Create(pool, 8, out var unallocatedIds); for (int i = 0; i < 512; ++i) { long start; var id = idCounter++; //allocator.ValidatePointers(); if (allocator.Allocate(id, 1 + random.Next(5), out start)) { allocatedIds.Add(id, pool); } else { unallocatedIds.Add(id, pool); } //allocator.ValidatePointers(); } for (int timestepIndex = 0; timestepIndex < 100000; ++timestepIndex) { //First add and remove a bunch randomly. for (int i = random.Next(Math.Min(allocatedIds.Count, 15)); i >= 0; --i) { var indexToRemove = random.Next(allocatedIds.Count); //allocator.ValidatePointers(); var deallocated = allocator.Deallocate(allocatedIds[indexToRemove]); Debug.Assert(deallocated); //allocator.ValidatePointers(); unallocatedIds.Add(allocatedIds[indexToRemove], pool); allocatedIds.FastRemoveAt(indexToRemove); } for (int i = random.Next(Math.Min(unallocatedIds.Count, 15)); i >= 0; --i) { var indexToAllocate = random.Next(unallocatedIds.Count); //allocator.ValidatePointers(); if (allocator.Allocate(unallocatedIds[indexToAllocate], random.Next(3), out long start)) { //allocator.ValidatePointers(); allocatedIds.Add(unallocatedIds[indexToAllocate], pool); unallocatedIds.FastRemoveAt(indexToAllocate); } //allocator.ValidatePointers(); } //Check to ensure that everything's still coherent. for (int i = 0; i < allocatedIds.Count; ++i) { Debug.Assert(allocator.Contains(allocatedIds[i])); } for (int i = 0; i < unallocatedIds.Count; ++i) { Debug.Assert(!allocator.Contains(unallocatedIds[i])); } } //Wind it down. for (int i = 0; i < allocatedIds.Count; ++i) { var deallocated = allocator.Deallocate(allocatedIds[i]); Debug.Assert(deallocated); } //Confirm cleanup. for (int i = 0; i < allocatedIds.Count; ++i) { Debug.Assert(!allocator.Contains(allocatedIds[i])); } for (int i = 0; i < unallocatedIds.Count; ++i) { Debug.Assert(!allocator.Contains(unallocatedIds[i])); } }
public unsafe static FontContent Build(Stream fontDataStream) { var faceBytes = new byte[fontDataStream.Length]; fontDataStream.Read(faceBytes, 0, faceBytes.Length); using (var library = new Library()) { using (var face = new Face(library, faceBytes, 0)) { //Collect glyph boundings information. face.SetPixelSizes(FontSizeInPixels, FontSizeInPixels); var sortedCharacterSet = new char[characterSet.Length]; var sortedCharacterData = new CharacterData[characterSet.Length]; for (int i = 0; i < characterSet.Length; ++i) { sortedCharacterSet[i] = characterSet[i]; face.LoadGlyph(face.GetCharIndex(characterSet[i]), LoadFlags.Default, LoadTarget.Normal); ref var characterData = ref sortedCharacterData[i]; characterData.SourceSpan.X = face.Glyph.Metrics.Width.ToInt32(); characterData.SourceSpan.Y = face.Glyph.Metrics.Height.ToInt32(); characterData.Bearing.X = face.Glyph.Metrics.HorizontalBearingX.ToInt32(); characterData.Bearing.Y = face.Glyph.Metrics.HorizontalBearingY.ToInt32(); characterData.Advance = face.Glyph.Metrics.HorizontalAdvance.ToInt32(); } //Next, allocate space in the atlas for each character. //Sort the characters by height, and then scan from one side of the atlas to the other placing characters. //Once the other side is reached, flip directions and repeat. Continue until no more characters remain. Array.Sort(sortedCharacterData, sortedCharacterSet, new CharacterHeightComparer()); const int padding = 1 << MipLevels; var characters = new Dictionary <char, CharacterData>(); var packer = new FontPacker(AtlasWidth, MipLevels, padding, characterSet.Length); for (int i = 0; i < sortedCharacterSet.Length; ++i) { //The packer class handles the placement logic and sets the SourceMinimum in the character data, too. packer.Add(ref sortedCharacterData[i]); } //Now that every glyph has been positioned within the sheet, we can actually rasterize the glyph alphas into a bitmap proto-atlas. //We're building the rasterized set sequentially first so we don't have to worry about threading issues in the underlying library. var rasterizedAlphas = new Texture2DContent(AtlasWidth, packer.Height, 1, 1); for (int i = 0; i < sortedCharacterSet.Length; ++i) { //Rasterize the glyph. var character = sortedCharacterSet[i]; face.LoadGlyph(face.GetCharIndex(character), LoadFlags.Default, LoadTarget.Normal); face.Glyph.RenderGlyph(RenderMode.Normal); //Copy the alphas into the pixel alpha buffer at the appropriate position. int glyphWidth = face.Glyph.Bitmap.Width; int glyphHeight = face.Glyph.Bitmap.Rows; var glyphBuffer = (byte *)face.Glyph.Bitmap.Buffer; Int2 location; location.X = sortedCharacterData[i].SourceMinimum.X; location.Y = sortedCharacterData[i].SourceMinimum.Y; for (int glyphRow = 0; glyphRow < glyphHeight; ++glyphRow) { Unsafe.CopyBlockUnaligned( ref rasterizedAlphas.Data[rasterizedAlphas.GetRowOffsetForMip0(glyphRow + location.Y) + location.X], ref glyphBuffer[glyphRow * glyphWidth], (uint)glyphWidth); } } //Preallocate memory for full single precision float version of the atlas. This will be used as scratch memory (admittedly, more than is necessary) //which will be encoded into the final single byte representation after the mips are calculated. The full precision stage makes the mips a little more accurate. var preciseAtlas = new Texture2DContent(AtlasWidth, packer.Height, MipLevels, 4); var atlas = new Texture2DContent(AtlasWidth, packer.Height, MipLevels, 1); //Compute the distances for every character-covered texel in the atlas. var atlasData = atlas.Pin(); var preciseData = (float *)preciseAtlas.Pin(); var alphaData = rasterizedAlphas.Pin(); var pool = new PassthroughArrayPool <Int2>(); //for (int i = 0; i < sortedCharacterData.Length; ++i) Parallel.For(0, sortedCharacterData.Length, i => { //Note that the padding around characters should also have its distances filled in. That way, the less detailed mips can pull from useful data. ref var charData = ref sortedCharacterData[i]; var min = new Int2(charData.SourceMinimum.X, charData.SourceMinimum.Y); var max = new Int2(charData.SourceMinimum.X + charData.SourceSpan.X, charData.SourceMinimum.Y + charData.SourceSpan.Y); var paddedMin = new Int2(min.X - padding, min.Y - padding); var paddedMax = new Int2(max.X + padding, max.Y + padding); //Initialize every character texel to max distance. The following BFS only ever reduces distances, so it has to start high. var maxDistance = Math.Max(AtlasWidth, packer.Height); for (int rowIndex = paddedMin.Y; rowIndex < paddedMax.Y; ++rowIndex) { var rowOffset = preciseAtlas.GetRowOffsetForMip0(rowIndex); var distancesRow = preciseData + rowOffset; for (int columnIndex = paddedMin.X; columnIndex < paddedMax.X; ++columnIndex) { distancesRow[columnIndex] = maxDistance; } } //Scan the alphas. Add border texels of the glyph to the point set. We collect both the nonzero alpha outline and the 'negative space' zero alpha outline. //While scanning distances, nonzero alpha texels will look for the shortest distance to a zero alpha texel, while zero alpha texels will look for the shortest //distance to a nonzero alpha texel. var glyphOutline = new List <Int2>((max.X - min.X) * (max.Y - min.Y)); int coverageThreshold = 127; for (int rowIndex = min.Y; rowIndex < max.Y; ++rowIndex) { //Alphas and atlas have same dimensions, so sharing row offset is safe. Debug.Assert(padding > 0, "This assumes at least one padding; no boundary checking is performed on the alpha accesses."); var rowOffset = preciseAtlas.GetRowOffsetForMip0(rowIndex); var alphasRow0 = alphaData + rowOffset - preciseAtlas.Width; var alphasRow1 = alphaData + rowOffset; var alphasRow2 = alphaData + rowOffset + preciseAtlas.Width; for (int columnIndex = min.X; columnIndex < max.X; ++columnIndex) { if (alphasRow1[columnIndex] >= coverageThreshold) { //This texel is considered covered. //Only add this to the point set if there is at least one adjacent uncovered texel. //If there isn't an uncovered texel next to this one, then it can't be on the surface. if (alphasRow0[columnIndex] < coverageThreshold || alphasRow1[columnIndex - 1] < coverageThreshold || alphasRow1[columnIndex + 1] < coverageThreshold || alphasRow2[columnIndex] < coverageThreshold) { Int2 texelCoordinates; texelCoordinates.X = columnIndex; texelCoordinates.Y = rowIndex; glyphOutline.Add(texelCoordinates); } } } } //For every texel in the character's region, scan the glyph point set for the nearest texel. //Cache the largest distance as we go so that we can maximize precision within this character. float largestDistanceMagnitude = 0; for (int rowIndex = paddedMin.Y; rowIndex < paddedMax.Y; ++rowIndex) { var rowOffset = preciseAtlas.GetRowOffsetForMip0(rowIndex); //Same dimensions; can be shared. var distancesRow = preciseData + rowOffset; var alphasRow = alphaData + rowOffset; for (int columnIndex = paddedMin.X; columnIndex < paddedMax.X; ++columnIndex) { //This is an uncovered texel. Look for a glyph outline. float lowestDistance = float.MaxValue; for (int pointIndex = 0; pointIndex < glyphOutline.Count; ++pointIndex) { var point = glyphOutline[pointIndex]; var offsetX = point.X - columnIndex; var offsetY = point.Y - rowIndex; var candidateDistance = (float)Math.Sqrt(offsetX * offsetX + offsetY * offsetY); if (candidateDistance < lowestDistance) { lowestDistance = candidateDistance; } } //If it's uncovered, use a positive distance. If it's covered, use a negative distance. distancesRow[columnIndex] = alphasRow[columnIndex] < coverageThreshold ? lowestDistance : -lowestDistance; if (lowestDistance > largestDistanceMagnitude) { largestDistanceMagnitude = lowestDistance; } } } //Build the mips. We already have all the data in cache on this core; 256KiB L2 can easily hold the processing context of a 128x128 glyph. //(Though worrying about performance in the content builder too much is pretty silly. We aren't going to be building fonts often.) //Note that we aligned and padded each glyph during packing. For a given texel in mip(n), the four parent texels in mip(n-1) can be safely sampled. for (int mipLevel = 1; mipLevel < preciseAtlas.MipLevels; ++mipLevel) { var mipMin = new Int2(paddedMin.X >> mipLevel, paddedMin.Y >> mipLevel); var mipMax = new Int2(paddedMax.X >> mipLevel, paddedMax.Y >> mipLevel); //Yes, these do some redundant calculations, but no it doesn't matter. var parentMipStart = preciseData + preciseAtlas.GetMipStartIndex(mipLevel - 1); var parentMipRowPitch = preciseAtlas.GetRowPitch(mipLevel - 1); var mipStart = preciseData + preciseAtlas.GetMipStartIndex(mipLevel); var mipRowPitch = preciseAtlas.GetRowPitch(mipLevel); for (int mipRowIndex = mipMin.Y; mipRowIndex < mipMax.Y; ++mipRowIndex) { var mipRow = mipStart + mipRowIndex * mipRowPitch; var parentRowIndex = mipRowIndex << 1; var parentMipRow0 = parentMipStart + parentRowIndex * parentMipRowPitch; var parentMipRow1 = parentMipStart + (parentRowIndex + 1) * parentMipRowPitch; for (int mipColumnIndex = mipMin.X; mipColumnIndex < mipMax.X; ++mipColumnIndex) { var parentMipColumnIndex0 = mipColumnIndex << 1; var parentMipColumnIndex1 = parentMipColumnIndex0 + 1; mipRow[mipColumnIndex] = 0.25f * ( parentMipRow0[parentMipColumnIndex0] + parentMipRow0[parentMipColumnIndex1] + parentMipRow1[parentMipColumnIndex0] + parentMipRow1[parentMipColumnIndex1]); } } } //Now that all mips have been filled, bake the data into the final single byte encoding. //Use the largest absolute distance as the encoding multiplier to maximize precision. charData.DistanceScale = largestDistanceMagnitude; var encodingMultiplier = 1f / largestDistanceMagnitude; for (int mipLevel = 0; mipLevel < atlas.MipLevels; ++mipLevel) { var mipMin = new Int2(paddedMin.X >> mipLevel, paddedMin.Y >> mipLevel); var mipMax = new Int2(paddedMax.X >> mipLevel, paddedMax.Y >> mipLevel); //Note signed bytes. We're building an R8_SNORM texture, not UNORM. var encodedStart = (sbyte *)atlasData + atlas.GetMipStartIndex(mipLevel); var preciseStart = preciseData + preciseAtlas.GetMipStartIndex(mipLevel); var rowPitch = atlas.GetRowPitch(mipLevel); for (int rowIndex = mipMin.Y; rowIndex < mipMax.Y; ++rowIndex) { var preciseRow = preciseStart + rowIndex * rowPitch; var encodedRow = encodedStart + rowIndex * rowPitch; for (int columnIndex = mipMin.X; columnIndex < mipMax.X; ++columnIndex) { encodedRow[columnIndex] = (sbyte)(127 * Math.Max(-1, Math.Min(1, encodingMultiplier * preciseRow[columnIndex]))); } } } });