diff options
| author | Sarah Duck <git@sarahduck.ca> | 2025-06-16 22:10:06 -0700 |
|---|---|---|
| committer | Sarah Duck <git@sarahduck.ca> | 2025-06-16 22:10:06 -0700 |
| commit | 48deeeabfd86cfc5b993936760e1332308a47ab2 (patch) | |
| tree | a2dd297bb356cad7792fe0f75de52f47685ee03f | |
| parent | c5b8b31dc7f29fd5512ac482cdffa2d274e6e01b (diff) | |
Optimizations, and Transparent Window!
| -rw-r--r-- | Bitmap.cs | 82 | ||||
| -rw-r--r-- | EmbeddedResources.cs | 5 | ||||
| -rw-r--r-- | Extensions.cs | 29 | ||||
| -rw-r--r-- | Main.cs | 30 | ||||
| -rw-r--r-- | Mouse.cs | 4 | ||||
| -rw-r--r-- | NetClient.cs | 1 | ||||
| -rw-r--r-- | Oneko.cs | 12 | ||||
| -rw-r--r-- | OnekoOnline.csproj | 1 |
8 files changed, 107 insertions, 57 deletions
@@ -25,23 +25,19 @@ class Bitmap : IDisposable Texture = Raylib.LoadTextureFromImage(img); - //Get Data for Serialization - int imgSize = Raylib.GetPixelDataSize(img.Width, img.Height, PixelFormat.UncompressedR8G8B8A8); - Span<byte> imgData; - unsafe { - imgData = new(img.Data, imgSize); - } - using MemoryStream stream = new(); + //Write image info before compressed data + Span<byte> TexInfo = stackalloc byte[6]; + BinaryPrimitives.WriteInt16LittleEndian(TexInfo, (short)Width); + BinaryPrimitives.WriteInt16LittleEndian(TexInfo[2..], (short)Height); + TexInfo[4] = (byte)img.Format; + TexInfo[5] = (byte)img.Mipmaps; - //Write width and height before compressed data - Span<byte> WidthHeight = stackalloc byte[4]; - BinaryPrimitives.WriteInt16LittleEndian(WidthHeight, (short)Width); - BinaryPrimitives.WriteInt16LittleEndian(WidthHeight[2..], (short)Height); - stream.Write(WidthHeight); + using MemoryStream stream = new(); + stream.Write(TexInfo); - using DeflateStream compressor = new(stream, CompressionLevel.Optimal); - compressor.Write(imgData); - compressor.Close(); + DeflateStream compressor = new(stream, CompressionLevel.Optimal); + compressor.Write(img.DataSpan()); + compressor.Dispose(); SerializedData = stream.ToArray(); Raylib.UnloadImage(img); @@ -53,54 +49,58 @@ class Bitmap : IDisposable Texture = tex; } + public Bitmap Copy() + { + Image ImgCopy = Raylib.LoadImageFromTexture(Texture); + Bitmap Copy = new(Raylib.LoadTextureFromImage(ImgCopy), SerializedData.ToArray()); + Raylib.UnloadImage(ImgCopy); + return Copy; + } + public static Bitmap FromFile(string path, int MaxW = MaxWidth, int MaxH = MaxHeight) { if (!File.Exists(path) || new FileInfo(path).Length > 40000 || !path.Contains(".png")) return new Bitmap(Raylib.GenImageChecked(32, 32, 4, 4, Color.Black, Color.Pink)); - return new Bitmap(Raylib.LoadImage(path), MaxW, MaxH); + return new Bitmap(Raylib.LoadImageFromMemory(".png", File.ReadAllBytes(path)), MaxW, MaxH); } - public static Bitmap FromPNGMemory(byte[] memory, int MaxW = MaxWidth, int MaxH = MaxHeight) - { - return new Bitmap(Raylib.LoadImageFromMemory(".png", memory), MaxW, MaxH); - } + public static Bitmap FromPNGMemory(byte[] memory, int MaxW = MaxWidth, int MaxH = MaxHeight) => new(Raylib.LoadImageFromMemory(".png", memory), MaxW, MaxH); - public static Bitmap Deserialize(ReadOnlySpan<byte> span, int MaxW = MaxWidth, int MaxH = MaxHeight, byte[]? fallbackImage = null) + public static Bitmap Deserialize(ReadOnlySpan<byte> span, int MaxW = MaxWidth, int MaxH = MaxHeight, Image? fallbackImage = null) { int width = BinaryPrimitives.ReadInt16LittleEndian(span[..2]); int height = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(2, 2)); + PixelFormat format = (PixelFormat)span[4]; + int mipmaps = span[5]; - bool Invalid = width <=0 || height <= 0 || width > MaxW || height > MaxH; - width = Math.Clamp(width, 1, MaxW); - height = Math.Clamp(height, 1, MaxH); + bool Invalid = width <= 0 || height <= 0 || width > MaxW || height > MaxH || format != PixelFormat.UncompressedR8G8B8A8; - //If the width and height are somehow wrong, then no use in loading the rest. - if (Invalid && fallbackImage != null) return FromPNGMemory(fallbackImage, MaxW, MaxH); - Image img = Raylib.GenImageChecked(width, height, 4, 4, Color.Pink, Color.Black); - Raylib.ImageFormat(ref img, PixelFormat.UncompressedR8G8B8A8); + //If the is somehow invalid, then no use in loading the rest. Either use fallback, or generate error checkerboard. + if (Invalid && fallbackImage != null) return new Bitmap(Raylib.ImageCopy(fallbackImage.Value), MaxW, MaxH); + Image img = Raylib.GenImageChecked(Math.Clamp(width, 8, MaxW), Math.Clamp(height, 8, MaxH), 4, 4, Color.Pink, Color.Black); if (Invalid) return new Bitmap(img); - byte[] compressed = span[4..].ToArray(); - byte[] data = new byte[Raylib.GetPixelDataSize(width, height, PixelFormat.UncompressedR8G8B8A8)]; + byte[] compressed = span[6..].ToArray(); + byte[] data = new byte[RaylibExt.TextureLength(width, height, format, mipmaps)]; { using MemoryStream input = new(compressed); using DeflateStream decompressor = new(input, CompressionMode.Decompress); decompressor.ReadExactly(data); } - - Texture2D tex = Raylib.LoadTextureFromImage(img); - Raylib.UpdateTexture(tex, data); - - Raylib.UnloadImage(img); - - return new Bitmap(tex, compressed); + unsafe {fixed (void* dataPtr = data) { + Texture2D tex = new() { + Id = Rlgl.LoadTexture(dataPtr, width, height, format, mipmaps), + Width = width, + Height = height, + Format = format, + Mipmaps = mipmaps + }; + return new Bitmap(tex, compressed); + }} } - public byte[] Serialize() - { - return SerializedData; - } + public byte[] Serialize() => SerializedData; bool disposed = false; diff --git a/EmbeddedResources.cs b/EmbeddedResources.cs index 8fdc1f8..2be42a6 100644 --- a/EmbeddedResources.cs +++ b/EmbeddedResources.cs @@ -10,7 +10,8 @@ static class EmbeddedResources public static byte[] GetResource(string name) { using Stream EmbeddedFile = assembly.GetManifestResourceStream($"{assemblyName}.{name}")!; - using BinaryReader reader = new(EmbeddedFile); - return reader.ReadBytes((int)EmbeddedFile.Length); + byte[] fileData = new byte[EmbeddedFile.Length]; + EmbeddedFile.ReadExactly(fileData); + return fileData; } }
\ No newline at end of file diff --git a/Extensions.cs b/Extensions.cs index e6368c3..80a8e2f 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -1,8 +1,9 @@ using System.Numerics; +using Raylib_cs; namespace OnekoOnline; -static class MathExtensions +static class MathExt { public static Vector2 LimitLength(this Vector2 toLimit, float lengthLimit) { @@ -17,7 +18,7 @@ static class MathExtensions } } -static class StringExtensions +static class StringExt { public static string LimitLength(this string value, int maxLength) { @@ -26,6 +27,30 @@ static class StringExtensions } } +static class RaylibExt +{ + public static int TextureLength(int width, int height, PixelFormat format, int mipmaps) + { + int size = 0; + + for (int i = 0; i < mipmaps; i++) { + size += Raylib.GetPixelDataSize(width, height, format); + + //Max is a security check for NPOT textures + width = Math.Max(width/2, 1); + height = Math.Max(height/2, 1); + } + + return size; + } + + public static int DataLength(this Image img) => TextureLength(img.Width, img.Height, img.Format, img.Mipmaps); + + public static int DataLength(this Texture2D tex) => TextureLength(tex.Width, tex.Height, tex.Format, tex.Mipmaps); + + public unsafe static Span<byte> DataSpan(this Image img) => new(img.Data, img.DataLength()); +} + public static class Directions { public static readonly Vector2 Up = new(0,-1); @@ -16,15 +16,24 @@ static class OnekoOnline public static Vector2 Resolution => new(WindowX, WindowY); public static readonly bool SpectatorMode = Config.GetValue("SpectatorMode", false); + public static readonly bool HostServer = Config.GetValue("HostServer", false); + public static readonly bool TransparentWindow = Config.GetValue("TransparentWindow", false); public static Font DefaultFont; - const ConfigFlags raylibConfFlags = ConfigFlags.VSyncHint; + public static string AppTitle { + get => _appTitle; + set { + Raylib.SetWindowTitle(value); + _appTitle = value; + } + } + static string _appTitle = "Oneko Online"; public static void Main() { - Raylib.SetConfigFlags(raylibConfFlags); - Raylib.InitWindow(WindowX*WindowScale, WindowY*WindowScale, "OnekoOnline"); + if (TransparentWindow) Raylib.SetConfigFlags(ConfigFlags.TransparentWindow); + Raylib.InitWindow(WindowX*WindowScale, WindowY*WindowScale, AppTitle); Raylib.SetTargetFPS(30); Raylib.HideCursor(); @@ -36,13 +45,16 @@ static class OnekoOnline int port = Config.GetValue("ServerPort", 42069); string serverPassword = Config.GetValue("ServerPassword", ""); - if (Config.GetValue("HostServer", false)) { + if (HostServer) { Server = new(port, Config.GetValue("ServerMaxUsers", 32)); Client = new("127.0.0.1", port, serverPassword); } else { string Address = Config.GetValue("ServerIP", "pond.sarahduck.ca"); - if (string.IsNullOrEmpty(Address)) Console.WriteLine("Server IP empty or invalid, you're offline."); + if (string.IsNullOrEmpty(Address)) { + Console.WriteLine("Server IP empty or invalid, you're offline."); + AppTitle = "Oneko Offline"; + } else Client = new(Address, port, serverPassword); } Net.Client.UserConnected += OnekoNet.SpawnNetNeko; @@ -50,6 +62,9 @@ static class OnekoOnline DefaultFont = Raylib.LoadFont("misc/MPlusBitmap.fnt"); + Color BackgroundColor = Color.Gray; + if (TransparentWindow) BackgroundColor = BackgroundColor with {A = 0}; + while (!Raylib.WindowShouldClose()) { //Poll networking @@ -58,15 +73,16 @@ static class OnekoOnline Raylib.BeginTextureMode(RenderTexture); - Raylib.ClearBackground(Color.Gray); + Raylib.ClearBackground(BackgroundColor); Raylib.DrawTextEx(DefaultFont, "こんにちは", new(17,18), 11, 0, Color.White); - Raylib.DrawText("Oneko Online", 10, 9, 8, Color.White); + Raylib.DrawText(AppTitle, 10, 9, 8, Color.White); Drawable.DrawAll(); Raylib.EndTextureMode(); Raylib.BeginDrawing(); + if (TransparentWindow) Raylib.ClearBackground(Color.Blank); //Dunno why, but it renders upside down, so I flip it here Raylib.DrawTexturePro(RenderTexture.Texture, new Rectangle(0f,0f,WindowX,-WindowY), new Rectangle(0,0,WindowX*WindowScale,WindowY*WindowScale), Vector2.Zero,0f,Color.White); Raylib.EndDrawing(); @@ -15,7 +15,7 @@ abstract class Mouse : Drawable public static Action<Mouse>? Clicked; - protected readonly static byte[] FallbackImg = EmbeddedResources.GetResource("misc.cursor.png"); + protected readonly static Image FallbackImg = Raylib.LoadImageFromMemory(".png", EmbeddedResources.GetResource("misc.cursor.png")); public Mouse() : base() { @@ -28,7 +28,7 @@ abstract class Mouse : Drawable Cursor = Bitmap.FromPNGMemory(File.ReadAllBytes(CursorPath), 32, 32); } else { Console.WriteLine("The cursor PNG was either mising or too big. Using the default."); - Cursor = Bitmap.FromPNGMemory(FallbackImg); + Cursor = new Bitmap(Raylib.ImageCopy(FallbackImg), 32, 32); } } diff --git a/NetClient.cs b/NetClient.cs index ce70d91..db62337 100644 --- a/NetClient.cs +++ b/NetClient.cs @@ -59,6 +59,7 @@ class Client ServerDisconnected?.Invoke(); users.Clear(); Console.WriteLine("Server Disconnected! You're offline!"); + OnekoOnline.AppTitle = "Oneko Offline"; }; Listener.NetworkReceiveEvent += (fromPeer, reader, channel, DeliveryMethod) => { @@ -20,7 +20,7 @@ abstract class Oneko : Drawable protected static List<Oneko> allNekos = []; public static ReadOnlyCollection<Oneko> AllNekos => allNekos.AsReadOnly(); - protected readonly static byte[] FallbackImg = EmbeddedResources.GetResource("nekos.oneko.png"); + protected readonly static Image FallbackImg = Raylib.LoadImageFromMemory(".png", EmbeddedResources.GetResource("nekos.oneko.png")); public Oneko() : base() { @@ -30,10 +30,16 @@ abstract class Oneko : Drawable string SpriteSheetPath = OnekoOnline.Config.GetValue("SpriteSheetPath", "nekos/oneko.png"); if (File.Exists(SpriteSheetPath) && new FileInfo(SpriteSheetPath).Length < 128*256*3) { - SpriteSheet = Bitmap.FromPNGMemory(File.ReadAllBytes(SpriteSheetPath), 256, 128); + #if DEBUG + //Bitmap Serialization test + using Bitmap SerializeTest = Bitmap.FromPNGMemory(File.ReadAllBytes(SpriteSheetPath), 256, 128); + SpriteSheet = Bitmap.Deserialize(SerializeTest.Serialize()); + #else + SpriteSheet = Bitmap.FromPNGMemory(File.ReadAllBytes(SpriteSheetPath), 256, 128); + #endif } else { Console.WriteLine("Path to spritesheet was invalid or the file was too big, using the default."); - SpriteSheet = Bitmap.FromPNGMemory(FallbackImg, 256, 128); + SpriteSheet = new Bitmap(Raylib.ImageCopy(FallbackImg), 256, 128); } allNekos.Add(this); diff --git a/OnekoOnline.csproj b/OnekoOnline.csproj index c0eb221..190e234 100644 --- a/OnekoOnline.csproj +++ b/OnekoOnline.csproj @@ -10,6 +10,7 @@ <PublishAot>true</PublishAot> <StripSymbols>true</StripSymbols> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <InvariantGlobalization>true</InvariantGlobalization> </PropertyGroup> |
