partial void DisposeTexture() { //check if another sprite is using the same texture if (!string.IsNullOrEmpty(FilePath)) //file can be empty if the sprite is created directly from a Texture2D instance { lock (list) { foreach (Sprite s in LoadedSprites) { if (s.FullPath == FullPath) { return; } } } } //if not, free the texture if (texture != null) { CrossThread.RequestExecutionOnMainThread(() => { texture.Dispose(); }); texture = null; } }
public static Texture2D LoadTexture(string file, bool preMultiplyAlpha = true) { if (string.IsNullOrWhiteSpace(file)) { Texture2D t = null; CrossThread.RequestExecutionOnMainThread(() => { t = new Texture2D(GameMain.GraphicsDeviceManager.GraphicsDevice, 1, 1); }); return(t); } file = Path.GetFullPath(file); foreach (Sprite s in list) { if (s.FullPath == file && s.texture != null) { return(s.texture); } } if (File.Exists(file)) { ToolBox.IsProperFilenameCase(file); return(TextureLoader.FromFile(file, preMultiplyAlpha)); } else { DebugConsole.ThrowError("Sprite \"" + file + "\" not found!"); } return(null); }
public static Texture2D FromStream(Stream fileStream, bool preMultiplyAlpha = true, string path = null) { try { int width = 0; int height = 0; int channels = 0; byte[] textureData = null; textureData = Texture2D.TextureDataFromStream(fileStream, out width, out height, out channels); if (preMultiplyAlpha) { PreMultiplyAlpha(ref textureData); } Texture2D tex = null; CrossThread.RequestExecutionOnMainThread(() => { tex = new Texture2D(_graphicsDevice, width, height); tex.SetData(textureData); }); return(tex); } catch (Exception e) { #if WINDOWS if (e is SharpDX.SharpDXException) { throw; } #endif DebugConsole.ThrowError("Loading texture from stream failed!", e); return(null); } }
public static Texture2D FromStream(Stream fileStream, string path = null, bool mipmap = false) { try { int width = 0; int height = 0; int channels = 0; byte[] textureData = null; textureData = Texture2D.TextureDataFromStream(fileStream, out width, out height, out channels); Texture2D tex = null; CrossThread.RequestExecutionOnMainThread(() => { tex = new Texture2D(_graphicsDevice, width, height, mipmap, SurfaceFormat.Color); tex.SetData(textureData); }); return(tex); } catch (Exception e) { #if WINDOWS if (e is SharpDX.SharpDXException) { throw; } #endif DebugConsole.ThrowError("Loading texture from stream failed!", e); return(null); } }
public static Texture2D FromStream(Stream fileStream, bool preMultiplyAlpha = true, string path = null) { try { int width = 0; int height = 0; int channels = 0; byte[] textureData = null; Task loadTask = Task.Run(() => { textureData = Texture2D.TextureDataFromStream(fileStream, out width, out height, out channels); }); bool success = loadTask.Wait(10000); if (!success) { DebugConsole.ThrowError("Failed to load texture data from " + (path ?? "stream") + ": timed out"); return(null); } if (preMultiplyAlpha) { PreMultiplyAlpha(ref textureData); } Texture2D tex = null; CrossThread.RequestExecutionOnMainThread(() => { tex = new Texture2D(_graphicsDevice, width, height); tex.SetData(textureData); }); return(tex); } catch (Exception e) { DebugConsole.ThrowError("Loading texture from stream failed!", e); return(null); } }
public static Texture2D FromStream(System.IO.Stream stream, string path = null, bool compress = true, bool mipmap = false) { try { path = path.CleanUpPath(); byte[] textureData = null; textureData = Texture2D.TextureDataFromStream(stream, out int width, out int height, out int channels); SurfaceFormat format = SurfaceFormat.Color; if (GameMain.Config.TextureCompressionEnabled && compress) { if (((width & 0x03) == 0) && ((height & 0x03) == 0)) { textureData = CompressDxt5(textureData, width, height); format = SurfaceFormat.Dxt5; mipmap = false; } else { DebugConsole.NewMessage($"Could not compress a texture because the dimensions aren't a multiple of 4 (path: {path ?? "null"}, size: {width}x{height})", Color.Orange); } } if (((width & 0x03) != 0) || ((height & 0x03) != 0)) { DebugConsole.AddWarning($"Cannot compress a texture because the dimensions are not a multiple of 4 (path: {path ?? "null"}, size: {width}x{height})"); } Texture2D tex = null; CrossThread.RequestExecutionOnMainThread(() => { if (cancelAll) { return; } tex = new Texture2D(_graphicsDevice, width, height, mipmap, format); tex.SetData(textureData); }); return(tex); } catch (Exception e) { #if WINDOWS if (e is SharpDX.SharpDXException) { throw; } #endif DebugConsole.ThrowError(string.IsNullOrEmpty(path) ? "Loading texture from stream failed!" : "Loading texture \"" + path + "\" failed!", e); return(null); } }
private static Texture2D GetTexture(SpriteBatch spriteBatch) { if (_whitePixelTexture == null) { CrossThread.RequestExecutionOnMainThread(() => { _whitePixelTexture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1, false, SurfaceFormat.Color); _whitePixelTexture.SetData(new[] { Color.White }); }); } return(_whitePixelTexture); }
public static void Init(GraphicsDevice graphicsDevice, bool needsBmp = false) { _graphicsDevice = graphicsDevice; Color[] data = new Color[32 * 32]; for (int i = 0; i < 32 * 32; i++) { data[i] = Color.Magenta; } CrossThread.RequestExecutionOnMainThread(() => { PlaceHolderTexture = new Texture2D(graphicsDevice, 32, 32); PlaceHolderTexture.SetData(data); }); }
public static Texture2D LoadTexture(string file, out Sprite reusedSprite, bool compress = true) { reusedSprite = null; if (string.IsNullOrWhiteSpace(file)) { Texture2D t = null; CrossThread.RequestExecutionOnMainThread(() => { t = new Texture2D(GameMain.GraphicsDeviceManager.GraphicsDevice, 1, 1); }); return(t); } string fullPath = Path.GetFullPath(file); foreach (Sprite s in LoadedSprites) { if (s.FullPath == fullPath && s.texture != null && !s.texture.IsDisposed) { reusedSprite = s; return(s.texture); } } if (File.Exists(file)) { if (!ToolBox.IsProperFilenameCase(file)) { #if DEBUG DebugConsole.ThrowError("Texture file \"" + file + "\" has incorrect case!"); #endif } return(TextureLoader.FromFile(file, compress)); } else { DebugConsole.ThrowError($"Sprite \"{file}\" not found! {Environment.StackTrace.CleanupStackTrace()}"); } return(null); }
partial void GenerateNoiseMapProjSpecific() { if (noiseTexture == null) { CrossThread.RequestExecutionOnMainThread(() => { noiseTexture = new Texture2D(GameMain.Instance.GraphicsDevice, generationParams.NoiseResolution, generationParams.NoiseResolution); rawNoiseTexture = new Texture2D(GameMain.Instance.GraphicsDevice, generationParams.NoiseResolution, generationParams.NoiseResolution); }); rawNoiseSprite = new Sprite(rawNoiseTexture, null, null); } Color[] crackTextureData = new Color[generationParams.NoiseResolution * generationParams.NoiseResolution]; Color[] noiseTextureData = new Color[generationParams.NoiseResolution * generationParams.NoiseResolution]; Color[] rawNoiseTextureData = new Color[generationParams.NoiseResolution * generationParams.NoiseResolution]; for (int x = 0; x < generationParams.NoiseResolution; x++) { for (int y = 0; y < generationParams.NoiseResolution; y++) { noiseTextureData[x + y * generationParams.NoiseResolution] = Color.Lerp(Color.Black, Color.Transparent, Noise[x, y]); rawNoiseTextureData[x + y * generationParams.NoiseResolution] = Color.Lerp(Color.Black, Color.White, Rand.Range(0.0f, 1.0f)); } } float mapRadius = size / 2; Vector2 mapCenter = Vector2.One * mapRadius; foreach (LocationConnection connection in connections) { float centerDist = Vector2.Distance(connection.CenterPos, mapCenter); Vector2 connectionStart = connection.Locations[0].MapPosition; Vector2 connectionEnd = connection.Locations[1].MapPosition; float connectionLength = Vector2.Distance(connectionStart, connectionEnd); int iterations = (int)(Math.Sqrt(connectionLength * generationParams.ConnectionIndicatorIterationMultiplier)); connection.CrackSegments = MathUtils.GenerateJaggedLine( connectionStart, connectionEnd, iterations, connectionLength * generationParams.ConnectionIndicatorDisplacementMultiplier); iterations = (int)(Math.Sqrt(connectionLength * generationParams.ConnectionIterationMultiplier)); var visualCrackSegments = MathUtils.GenerateJaggedLine( connectionStart, connectionEnd, iterations, connectionLength * generationParams.ConnectionDisplacementMultiplier); float totalLength = Vector2.Distance(visualCrackSegments[0][0], visualCrackSegments.Last()[1]); for (int i = 0; i < visualCrackSegments.Count; i++) { Vector2 start = visualCrackSegments[i][0] * (generationParams.NoiseResolution / (float)size); Vector2 end = visualCrackSegments[i][1] * (generationParams.NoiseResolution / (float)size); float length = Vector2.Distance(start, end); for (float x = 0; x < 1; x += 1.0f / length) { Vector2 pos = Vector2.Lerp(start, end, x); SetNoiseColorOnArea(pos, MathHelper.Clamp((int)(totalLength / 30), 2, 5) + Rand.Range(-1, 1), Color.Transparent); } } } void SetNoiseColorOnArea(Vector2 pos, int dist, Color color) { for (int x = -dist; x < dist; x++) { for (int y = -dist; y < dist; y++) { float d = 1.0f - new Vector2(x, y).Length() / dist; if (d <= 0) { continue; } int xIndex = (int)pos.X + x; if (xIndex < 0 || xIndex >= generationParams.NoiseResolution) { continue; } int yIndex = (int)pos.Y + y; if (yIndex < 0 || yIndex >= generationParams.NoiseResolution) { continue; } float perlin = (float)PerlinNoise.CalculatePerlin( xIndex / (float)generationParams.NoiseResolution * 100.0f, yIndex / (float)generationParams.NoiseResolution * 100.0f, 0); byte a = Math.Max(crackTextureData[xIndex + yIndex * generationParams.NoiseResolution].A, (byte)((d * perlin) * 255)); crackTextureData[xIndex + yIndex * generationParams.NoiseResolution].A = a; } } } for (int i = 0; i < noiseTextureData.Length; i++) { float darken = noiseTextureData[i].A / 255.0f; Color pathColor = Color.Lerp(Color.White, Color.Transparent, noiseTextureData[i].A / 255.0f); noiseTextureData[i] = Color.Lerp(noiseTextureData[i], pathColor, crackTextureData[i].A / 255.0f * 0.5f); } CrossThread.RequestExecutionOnMainThread(() => { noiseTexture.SetData(noiseTextureData); rawNoiseTexture.SetData(rawNoiseTextureData); }); }
public void End() { if (isDisposed) { return; } //sort commands according to the sorting //mode given in the last Begin call switch (currentSortMode) { case SpriteSortMode.FrontToBack: commandList.Sort((c1, c2) => { return(c1.Depth <c2.Depth ? -1 : c1.Depth> c2.Depth ? 1 : c1.Index <c2.Index ? 1 : c1.Index> c2.Index ? -1 : 0); }); break; case SpriteSortMode.BackToFront: commandList.Sort((c1, c2) => { return(c1.Depth <c2.Depth ? 1 : c1.Depth> c2.Depth ? -1 : c1.Index <c2.Index ? 1 : c1.Index> c2.Index ? -1 : 0); }); break; } //try to place commands of the same texture //contiguously for optimal buffer generation //while maintaining the same visual result for (int i = 1; i < commandList.Count; i++) { if (commandList[i].Texture != commandList[i - 1].Texture) { for (int j = i - 1; j >= 0; j--) { if (commandList[j].Texture == commandList[i].Texture) { //no commands between i and j overlap with //i, therefore we can safely sift i down to //make a contiguous block commandList.SiftElement(i, j + 1); break; } else if (commandList[j].Overlaps(commandList[i])) { //an overlapping command was found, therefore //attempting to sift this one down would change //the visual result break; } } } } if (isDisposed) { return; } //each contiguous block of commands of the same texture //requires a vertex buffer to be rendered CrossThread.RequestExecutionOnMainThread(() => { if (commandList.Count == 0) { return; } int startIndex = 0; for (int i = 1; i < commandList.Count; i++) { if (commandList[i].Texture != commandList[startIndex].Texture) { maxSpriteCount = Math.Max(maxSpriteCount, i - startIndex); recordedBuffers.Add(new RecordedBuffer(commandList, startIndex, i - startIndex)); startIndex = i; } } recordedBuffers.Add(new RecordedBuffer(commandList, startIndex, commandList.Count - startIndex)); maxSpriteCount = Math.Max(maxSpriteCount, commandList.Count - startIndex); }); commandList.Clear(); ReadyToRender = true; }
private IEnumerable <object> Load(bool isSeparateThread) { if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("LOADING COROUTINE", Color.Lime); } while (TitleScreen.WaitForLanguageSelection) { yield return(CoroutineStatus.Running); } SoundManager = new Sounds.SoundManager(); SoundManager.SetCategoryGainMultiplier("default", Config.SoundVolume, 0); SoundManager.SetCategoryGainMultiplier("ui", Config.SoundVolume, 0); SoundManager.SetCategoryGainMultiplier("waterambience", Config.SoundVolume, 0); SoundManager.SetCategoryGainMultiplier("music", Config.MusicVolume, 0); SoundManager.SetCategoryGainMultiplier("voip", Math.Min(Config.VoiceChatVolume, 1.0f), 0); if (Config.EnableSplashScreen && !ConsoleArguments.Contains("-skipintro")) { var pendingSplashScreens = TitleScreen.PendingSplashScreens; float baseVolume = MathHelper.Clamp(Config.SoundVolume * 2.0f, 0.0f, 1.0f); pendingSplashScreens?.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_UTG.webm", baseVolume * 0.5f)); pendingSplashScreens?.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_FF.webm", baseVolume)); pendingSplashScreens?.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_Daedalic.webm", baseVolume * 0.1f)); } //if not loading in a separate thread, wait for the splash screens to finish before continuing the loading //otherwise the videos will look extremely choppy if (!isSeparateThread) { while (TitleScreen.PlayingSplashScreen || TitleScreen.PendingSplashScreens.Count > 0) { yield return(CoroutineStatus.Running); } } GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice); DebugConsole.Init(); if (Config.AutoUpdateWorkshopItems) { bool waitingForWorkshopUpdates = true; bool result = false; TaskPool.Add(SteamManager.AutoUpdateWorkshopItemsAsync(), (task) => { result = task.Result; waitingForWorkshopUpdates = false; }); while (waitingForWorkshopUpdates) { yield return(CoroutineStatus.Running); } if (result) { CrossThread.RequestExecutionOnMainThread(() => { ContentPackage.LoadAll(); Config.ReloadContentPackages(); }); } } if (SelectedPackages.None()) { DebugConsole.Log("No content packages selected"); } else { DebugConsole.Log("Selected content packages: " + string.Join(", ", SelectedPackages.Select(cp => cp.Name))); } #if DEBUG GameSettings.ShowUserStatisticsPrompt = false; GameSettings.SendUserStatistics = false; #endif InitUserStats(); yield return(CoroutineStatus.Running); Debug.WriteLine("sounds"); int i = 0; foreach (object crObj in SoundPlayer.Init()) { CoroutineStatus status = (CoroutineStatus)crObj; if (status == CoroutineStatus.Success) { break; } i++; TitleScreen.LoadState = SoundPlayer.SoundCount == 0 ? 1.0f : Math.Min(40.0f * i / Math.Max(SoundPlayer.SoundCount, 1), 40.0f); yield return(CoroutineStatus.Running); } TitleScreen.LoadState = 40.0f; yield return(CoroutineStatus.Running); LightManager = new Lights.LightManager(base.GraphicsDevice, Content); TitleScreen.LoadState = 41.0f; yield return(CoroutineStatus.Running); GUI.LoadContent(); TitleScreen.LoadState = 42.0f; yield return(CoroutineStatus.Running); CharacterPrefab.LoadAll(); MissionPrefab.Init(); TraitorMissionPrefab.Init(); MapEntityPrefab.Init(); Tutorials.Tutorial.Init(); MapGenerationParams.Init(); LevelGenerationParams.LoadPresets(); WreckAIConfig.LoadAll(); ScriptedEventSet.LoadPrefabs(); AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings)); Order.Init(); EventManagerSettings.Init(); TitleScreen.LoadState = 50.0f; yield return(CoroutineStatus.Running); StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure)); TitleScreen.LoadState = 53.0f; yield return(CoroutineStatus.Running); ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item)); TitleScreen.LoadState = 55.0f; yield return(CoroutineStatus.Running); JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs)); CorpsePrefab.LoadAll(GetFilesOfType(ContentType.Corpses)); NPCConversation.LoadAll(GetFilesOfType(ContentType.NPCConversations)); ItemAssemblyPrefab.LoadAll(); TitleScreen.LoadState = 60.0f; yield return(CoroutineStatus.Running); GameModePreset.Init(); SubmarineInfo.RefreshSavedSubs(); TitleScreen.LoadState = 65.0f; yield return(CoroutineStatus.Running); GameScreen = new GameScreen(GraphicsDeviceManager.GraphicsDevice, Content); TitleScreen.LoadState = 68.0f; yield return(CoroutineStatus.Running); MainMenuScreen = new MainMenuScreen(this); LobbyScreen = new LobbyScreen(); ServerListScreen = new ServerListScreen(); TitleScreen.LoadState = 70.0f; yield return(CoroutineStatus.Running); #if USE_STEAM SteamWorkshopScreen = new SteamWorkshopScreen(); if (SteamManager.IsInitialized) { Steamworks.SteamFriends.OnGameRichPresenceJoinRequested += OnInvitedToGame; Steamworks.SteamFriends.OnGameLobbyJoinRequested += OnLobbyJoinRequested; } #endif SubEditorScreen = new SubEditorScreen(); TitleScreen.LoadState = 75.0f; yield return(CoroutineStatus.Running); ParticleEditorScreen = new ParticleEditorScreen(); TitleScreen.LoadState = 80.0f; yield return(CoroutineStatus.Running); LevelEditorScreen = new LevelEditorScreen(); SpriteEditorScreen = new SpriteEditorScreen(); CharacterEditorScreen = new CharacterEditor.CharacterEditorScreen(); yield return(CoroutineStatus.Running); TitleScreen.LoadState = 85.0f; ParticleManager = new ParticleManager(GameScreen.Cam); ParticleManager.LoadPrefabs(); TitleScreen.LoadState = 88.0f; LevelObjectPrefab.LoadAll(); TitleScreen.LoadState = 90.0f; yield return(CoroutineStatus.Running); DecalManager = new DecalManager(); LocationType.Init(); MainMenuScreen.Select(); CheckContentPackage(); foreach (string steamError in SteamManager.InitializationErrors) { new GUIMessageBox(TextManager.Get("Error"), TextManager.Get(steamError)); } TitleScreen.LoadState = 100.0f; hasLoaded = true; if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("LOADING COROUTINE FINISHED", Color.Lime); } yield return(CoroutineStatus.Success); }
public void DynamicRenderAtlas(GraphicsDevice gd, uint character, int texDims = 1024, uint baseChar = 0x54) { if (System.Threading.Thread.CurrentThread != GameMain.MainThread) { CrossThread.RequestExecutionOnMainThread(() => { DynamicRenderAtlas(gd, character, texDims, baseChar); }); return; } byte[] bitmap; int glyphWidth; int glyphHeight; Fixed26Dot6 horizontalAdvance; Vector2 drawOffset; lock (mutex) { if (texCoords.ContainsKey(character)) { return; } if (textures.Count == 0) { this.texDims = texDims; this.baseChar = baseChar; face.SetPixelSizes(0, size); face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal); baseHeight = face.Glyph.Metrics.Height.ToInt32(); textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color)); } uint glyphIndex = face.GetCharIndex(character); if (glyphIndex == 0) { return; } face.SetPixelSizes(0, size); face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal); if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0) { if (face.Glyph.Metrics.HorizontalAdvance > 0) { //glyph is empty, but char still applies advance GlyphData blankData = new GlyphData(); blankData.advance = (float)face.Glyph.Metrics.HorizontalAdvance; blankData.texIndex = -1; //indicates no texture because the glyph is empty texCoords.Add(character, blankData); } return; } //stacktrace doesn't really work that well when RenderGlyph throws an exception face.Glyph.RenderGlyph(RenderMode.Normal); bitmap = (byte[])face.Glyph.Bitmap.BufferData.Clone(); glyphWidth = face.Glyph.Bitmap.Width; glyphHeight = bitmap.Length / glyphWidth; horizontalAdvance = face.Glyph.Metrics.HorizontalAdvance; drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop); if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1) { throw new Exception(filename + ", " + size.ToString() + ", " + (char)character + "; Glyph dimensions exceed texture atlas dimensions"); } currentDynamicAtlasNextY = Math.Max(currentDynamicAtlasNextY, glyphHeight + 2); if (currentDynamicAtlasCoords.X + glyphWidth + 2 > texDims - 1) { currentDynamicAtlasCoords.X = 0; currentDynamicAtlasCoords.Y += currentDynamicAtlasNextY; currentDynamicAtlasNextY = 0; } //no more room in current texture atlas, create a new one if (currentDynamicAtlasCoords.Y + glyphHeight + 2 > texDims - 1) { currentDynamicAtlasCoords.X = 0; currentDynamicAtlasCoords.Y = 0; currentDynamicAtlasNextY = 0; textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color)); currentDynamicPixelBuffer = null; } GlyphData newData = new GlyphData { advance = (float)horizontalAdvance, texIndex = textures.Count - 1, texCoords = new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight), drawOffset = drawOffset }; texCoords.Add(character, newData); if (currentDynamicPixelBuffer == null) { currentDynamicPixelBuffer = new uint[texDims * texDims]; textures[newData.texIndex].GetData <uint>(currentDynamicPixelBuffer, 0, texDims * texDims); } for (int y = 0; y < glyphHeight; y++) { for (int x = 0; x < glyphWidth; x++) { byte byteColor = bitmap[x + y * glyphWidth]; currentDynamicPixelBuffer[((int)currentDynamicAtlasCoords.X + x) + ((int)currentDynamicAtlasCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff); } } textures[newData.texIndex].SetData <uint>(currentDynamicPixelBuffer); currentDynamicAtlasCoords.X += glyphWidth + 2; } }
/// <summary> /// Renders the font into at least one texture atlas, which is simply a collection of all glyphs in the ranges defined by charRanges. /// Don't call this too often or with very large sizes. /// </summary> /// <param name="gd">Graphics device, required to create textures.</param> /// <param name="charRanges">Character ranges between each even element with their corresponding odd element. Default is 0x20 to 0xFFFF.</param> /// <param name="texDims">Texture dimensions. Default is 512x512.</param> /// <param name="baseChar">Base character used to shift all other characters downwards when rendering. Defaults to T.</param> public void RenderAtlas(GraphicsDevice gd, uint[] charRanges = null, int texDims = 1024, uint baseChar = 0x54) { if (DynamicLoading) { return; } if (charRanges == null) { charRanges = new uint[] { 0x20, 0xFFFF }; } this.charRanges = charRanges; this.texDims = texDims; this.baseChar = baseChar; textures.ForEach(t => t.Dispose()); textures.Clear(); texCoords.Clear(); uint[] pixelBuffer = new uint[texDims * texDims]; for (int i = 0; i < texDims * texDims; i++) { pixelBuffer[i] = 0; } CrossThread.RequestExecutionOnMainThread(() => { textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color)); }); int texIndex = 0; Vector2 currentCoords = Vector2.Zero; int nextY = 0; lock (mutex) { face.SetPixelSizes(0, size); face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal); baseHeight = face.Glyph.Metrics.Height.ToInt32(); for (int i = 0; i < charRanges.Length; i += 2) { uint start = charRanges[i]; uint end = charRanges[i + 1]; for (uint j = start; j <= end; j++) { uint glyphIndex = face.GetCharIndex(j); if (glyphIndex == 0) { continue; } face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal); if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0) { if (face.Glyph.Metrics.HorizontalAdvance > 0) { //glyph is empty, but char still applies advance GlyphData blankData = new GlyphData(); blankData.advance = (float)face.Glyph.Metrics.HorizontalAdvance; blankData.texIndex = -1; //indicates no texture because the glyph is empty texCoords.Add(j, blankData); } continue; } //stacktrace doesn't really work that well when RenderGlyph throws an exception face.Glyph.RenderGlyph(RenderMode.Normal); byte[] bitmap = face.Glyph.Bitmap.BufferData; int glyphWidth = face.Glyph.Bitmap.Width; int glyphHeight = bitmap.Length / glyphWidth; //if (glyphHeight>lineHeight) lineHeight=glyphHeight; if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1) { throw new Exception(filename + ", " + size.ToString() + ", " + (char)j + "; Glyph dimensions exceed texture atlas dimensions"); } nextY = Math.Max(nextY, glyphHeight + 2); if (currentCoords.X + glyphWidth + 2 > texDims - 1) { currentCoords.X = 0; currentCoords.Y += nextY; nextY = 0; } if (currentCoords.Y + glyphHeight + 2 > texDims - 1) { currentCoords.X = 0; currentCoords.Y = 0; CrossThread.RequestExecutionOnMainThread(() => { textures[texIndex].SetData <uint>(pixelBuffer); textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color)); }); texIndex++; for (int k = 0; k < texDims * texDims; k++) { pixelBuffer[k] = 0; } } GlyphData newData = new GlyphData { advance = (float)face.Glyph.Metrics.HorizontalAdvance, texIndex = texIndex, texCoords = new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight), drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop) }; texCoords.Add(j, newData); for (int y = 0; y < glyphHeight; y++) { for (int x = 0; x < glyphWidth; x++) { byte byteColor = bitmap[x + y * glyphWidth]; pixelBuffer[((int)currentCoords.X + x) + ((int)currentCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff); } } currentCoords.X += glyphWidth + 2; } CrossThread.RequestExecutionOnMainThread(() => { textures[texIndex].SetData <uint>(pixelBuffer); }); } } }