From bfc43b9463dbe910fc9842b06fd9ed2afc2fb320 Mon Sep 17 00:00:00 2001 From: Ian Burgmyer Date: Sat, 9 Mar 2019 05:34:00 -0500 Subject: [PATCH] Major work on Canvases and Sprites. * GetCanvasPointer() is now a Canvas function pointer. This is a breaking change, but it should allow any Canvas-derived object to be fed an arbitrary ARGB surface if the implementer chooses to take that approach. * Canvas and Sprite textures are created in a more consistent way. * The background Canvas now uses the same functions that Sprites use for texture creation and updates. * Added a public UpdateTexture() function so that implementers can manually update sprites if necessary. This should not be used too often as it can lead to performance issues. * The SpriteList now initializes Sprites using the stanard Canvas initialization routines. * Implemented two moveable "players" in Sample.Sprites. * Sprites can actually be drawn! Alpha and blending modes are not supported at the moment. --- DotSDL/Graphics/Canvas.cs | 56 ++++++++++++++++++++--- DotSDL/Graphics/SdlWindow.cs | 49 ++++++++------------- DotSDL/Graphics/Sprite.cs | 24 ++++++++-- DotSDL/Graphics/SpriteList.cs | 9 ++-- Samples/Sample.Sprites/Player.cs | 55 ++++++++++++++++++++++- Samples/Sample.Sprites/Window.cs | 95 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 238 insertions(+), 50 deletions(-) diff --git a/DotSDL/Graphics/Canvas.cs b/DotSDL/Graphics/Canvas.cs index 2ca8627..18ce637 100644 --- a/DotSDL/Graphics/Canvas.cs +++ b/DotSDL/Graphics/Canvas.cs @@ -9,7 +9,11 @@ namespace DotSDL.Graphics { /// public class Canvas { private int _width, _height; - private bool _hasTexture; + + /// + /// true if this has an SDL texture associated with it, otherwise false. + /// + protected bool HasTexture { get; set; } /// /// The SDL_Texture that this maintains. @@ -22,6 +26,16 @@ namespace DotSDL.Graphics { /// public Color[] Pixels; + /// + /// Gets an that points to what should be displayed on the window's background. This is + /// useful if you're maintaining your own ARGB framebuffer and don't plan to use DotSDL's + /// object. You usually do not need to override this method. + /// + /// If an invalid is generated by this method, your application may + /// crash with a segmentation fault. When in doubt, override instead! + /// An containing the contents of the window's background. + public Func GetCanvasPointer; + /// /// Gets or sets the width of the texture. /// @@ -74,28 +88,45 @@ namespace DotSDL.Graphics { Clipping = clipping; + GetCanvasPointer = () => { + unsafe { + fixed(void* pixelPtr = Pixels) { + return (IntPtr)pixelPtr; + } + } + }; + Resize(); } /// /// Creates a texture or recreates it if it already exists. /// - internal void CreateTexture() { + internal virtual void CreateTexture() { + CreateTexture(Render.TextureAccess.Streaming); + } + + /// + /// Creates a texture or recreates it if it already exists. + /// + /// The access mode for this texture. + internal void CreateTexture(Render.TextureAccess textureAccess) { if(Renderer == IntPtr.Zero) return; DestroyTexture(); - Texture = Render.CreateTexture(Renderer, SdlPixels.PixelFormatArgb8888, Render.TextureAccess.Streaming, Width, Height); - _hasTexture = true; + Texture = Render.CreateTexture(Renderer, SdlPixels.PixelFormatArgb8888, textureAccess, Width, Height); + HasTexture = true; + } /// /// Destroys the texture associated with this . /// internal void DestroyTexture() { - if(!_hasTexture) return; + if(!HasTexture) return; Render.DestroyTexture(Texture); - _hasTexture = false; + HasTexture = false; } /// @@ -124,8 +155,19 @@ namespace DotSDL.Graphics { protected void Resize() { Pixels = new Color[Width * Height]; - if(_hasTexture) + if(HasTexture) CreateTexture(); } + + /// + /// Updates the texture associated with this . This function must be called when the + /// array is changed. + /// + /// true if the texture was successfully updated, otherwise false. This will return false if this hasn't been added to the sprite list. + internal bool UpdateTexture() { + if(!HasTexture) return false; + Render.UpdateTexture(Texture, IntPtr.Zero, GetCanvasPointer(), Width * 4); + return true; + } } } diff --git a/DotSDL/Graphics/SdlWindow.cs b/DotSDL/Graphics/SdlWindow.cs index 0216bd8..79a1855 100644 --- a/DotSDL/Graphics/SdlWindow.cs +++ b/DotSDL/Graphics/SdlWindow.cs @@ -179,12 +179,10 @@ namespace DotSDL.Graphics { Render.SetRenderTarget(_renderer, _texture); // Blit the Canvas to the target texture. + Background.UpdateTexture(); unsafe { var canvasClippingRect = Background.Clipping.SdlRect; - var canvasClippingRectPtr = new IntPtr(&canvasClippingRect); - - Render.UpdateTexture(Background.Texture, IntPtr.Zero, GetCanvasPointer(), Background.Width * 4); - Render.RenderCopy(_renderer, Background.Texture, canvasClippingRectPtr, IntPtr.Zero); + Render.RenderCopy(_renderer, Background.Texture, new IntPtr(&canvasClippingRect), IntPtr.Zero); } // Plot sprites on top of the background layer. @@ -226,7 +224,8 @@ namespace DotSDL.Graphics { /// DotSDL's drawing routines and does not need to be called manually. Additionally, this method will not be /// called if there are no sprites defined. You usually do not need to override this method. /// - public virtual unsafe void DrawSprites() { + public virtual void DrawSprites() { + Render.SetRenderTarget(_renderer, _texture); foreach(var sprite in Sprites.Where(e => e.Shown).OrderBy(e => e.ZOrder)) { SetScalingQuality(sprite.ScalingQuality); @@ -237,32 +236,20 @@ namespace DotSDL.Graphics { ); var destRect = new Rectangle(sprite.Position, drawSize).SdlRect; - var srcRectPtr = new IntPtr(&srcRect); - var destRectPtr = new IntPtr(&destRect); - - Render.RenderCopyEx( - renderer: _renderer, - texture: _texture, - srcRect: srcRectPtr, - dstRect: destRectPtr, - angle: sprite.Rotation, - center: sprite.Position.SdlPoint, - flip: sprite.Flip - ); - } - } - - /// - /// Gets an that points to what should be displayed on the window's background. This is - /// useful if you're maintaining your own ARGB framebuffer and don't plan to use DotSDL's - /// object. You usually do not need to override this method. - /// - /// If an invalid is generated by this method, your application may - /// crash with a segmentation fault. When in doubt, override instead! - /// An containing the contents of the window's background. - public virtual unsafe IntPtr GetCanvasPointer() { - fixed(void* pixelsPtr = Background.Pixels) { - return (IntPtr)pixelsPtr; + unsafe { + var srcRectPtr = new IntPtr(&srcRect); + var destRectPtr = new IntPtr(&destRect); + + Render.RenderCopyEx( + renderer: _renderer, + texture: sprite.Texture, + srcRect: srcRectPtr, + dstRect: destRectPtr, + angle: sprite.Rotation, + center: sprite.RotationCenter.SdlPoint, + flip: sprite.Flip + ); + } } } diff --git a/DotSDL/Graphics/Sprite.cs b/DotSDL/Graphics/Sprite.cs index d232bee..678d331 100644 --- a/DotSDL/Graphics/Sprite.cs +++ b/DotSDL/Graphics/Sprite.cs @@ -1,4 +1,6 @@ -using System.Numerics; +using DotSDL.Interop.Core; +using SdlPixels = DotSDL.Interop.Core.Pixels; +using System.Numerics; namespace DotSDL.Graphics { /// @@ -120,8 +122,24 @@ namespace DotSDL.Graphics { ZOrder = zOrder; Shown = false; - RotationCenter.X = clipping.Size.X / 2; - RotationCenter.Y = clipping.Size.Y / 2; + RotationCenter = new Point(clipping.Size.X / 2, clipping.Size.Y / 2); + } + + /// + /// Creates a texture or recreates it if it already exists. + /// + internal override void CreateTexture() { + CreateTexture(Render.TextureAccess.Static); + } + + /// + /// Updates the texture associated with this . This function must be called when the + /// array is changed after adding this sprite to the sprite list associated + /// with the application's . + /// + /// true if the texture was successfully updated, otherwise false. This will return false if this hasn't been added to the sprite list. + public new bool UpdateTexture() { + return base.UpdateTexture(); } } } diff --git a/DotSDL/Graphics/SpriteList.cs b/DotSDL/Graphics/SpriteList.cs index 80b73ed..55b7b04 100644 --- a/DotSDL/Graphics/SpriteList.cs +++ b/DotSDL/Graphics/SpriteList.cs @@ -83,12 +83,13 @@ namespace DotSDL.Graphics { IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// - /// Creates a texture for a sprite prior to adding it to the list. + /// Creates a texture for a prior to adding it to the list. /// - /// + /// The that needs to be initialized. private void InitializeSprite(Canvas sprite) { - sprite.Texture = Render.CreateTexture(_renderer, Pixels.PixelFormatArgb8888, Render.TextureAccess.Static, - sprite.Width, sprite.Height); + sprite.Renderer = _renderer; + sprite.CreateTexture(); + sprite.UpdateTexture(); } /// diff --git a/Samples/Sample.Sprites/Player.cs b/Samples/Sample.Sprites/Player.cs index 212cc98..621eb31 100644 --- a/Samples/Sample.Sprites/Player.cs +++ b/Samples/Sample.Sprites/Player.cs @@ -2,14 +2,67 @@ namespace Sample.Sprites { public class Player : Sprite { + private const int Radius = 15; + private const int Size = 32; + private Color _color; private int _speed; - public Player(Color color, int speed) : base(64, 64) { + public Player(Color color, int speed, int playerId) : base(Size, Size, playerId) { _color = color; _speed = speed; Shown = true; + + // Draw some really rough yet strangely lovable circles. + for(var x = Radius; x > 1; x--) { + var rX = x; + var rY = 0; + var err = 0; + + while(rX >= rY) { + PlotMirroredPoints(rX, rY); + PlotMirroredPoints(rY, rX); + + rY += 1; + if(err <= 0) { + err += 2 * rY + 1; + } + + if(err > 0) { + rX -= 1; + err -= 2 * rX + 1; + } + } + + // Increase the brightness as we move further inside. + var newR = (short)(_color.R * 1.1); + _color.R = (byte)(newR > 255 ? 255 : newR); + + var newG = (short)(_color.G * 1.1); + _color.G = (byte)(newG > 255 ? 255 : newG); + + var newB = (short)(_color.B * 1.1); + _color.B = (byte)(newB > 255 ? 255 : newB); + } + + // Plot a little line so that we can show rotation. + for(var y = Radius; y >= 0; y--) { + Pixels[GetIndex(Radius, y)] = new Color { R = 64, G = 255, B = 64 }; + } + } + + public void Move(Point delta) { + // TODO: Support some basic vector arithmetic for points. + Position.X += delta.X * _speed; + Position.Y += delta.Y * _speed; + } + + private void PlotMirroredPoints(int x, int y) { + Pixels[GetIndex(Radius + x, Radius + y)] = _color; + Pixels[GetIndex(Radius + x, Radius - y)] = _color; + Pixels[GetIndex(Radius - x, Radius + y)] = _color; + Pixels[GetIndex(Radius - x, Radius - y)] = _color; } } } diff --git a/Samples/Sample.Sprites/Window.cs b/Samples/Sample.Sprites/Window.cs index 2218f3e..79c7719 100644 --- a/Samples/Sample.Sprites/Window.cs +++ b/Samples/Sample.Sprites/Window.cs @@ -5,6 +5,10 @@ using System; namespace Sample.Sprites { public class Window : SdlWindow { + //private int _camX = 0, _camY = 0, _deltaX = 2, _deltaY = 1; + private Player _player1, _player2; + private Point _player1Delta, _player2Delta; + public Window(int scale) : base("Sprites Test", new Point { X = WindowPosUndefined, Y = WindowPosUndefined }, 256 * scale, 196 * scale, @@ -15,6 +19,7 @@ namespace Sample.Sprites { Background.Height = Background.Width = 1024; GenerateBackground(); + GeneratePlayers(); } private void GenerateBackground() { @@ -34,10 +39,20 @@ namespace Sample.Sprites { } } + // Darken every other layer, because why not. :) + for(var y = 2; y < Background.Height; y += 2) { + for(var x = 0; x < Background.Width; x++) { + var pix = Background.Width * y + x; + Background.Pixels[pix].R = (byte)(Background.Pixels[pix].R * 0.8); + Background.Pixels[pix].G = (byte)(Background.Pixels[pix].G * 0.8); + Background.Pixels[pix].B = (byte)(Background.Pixels[pix].B * 0.8); + } + } + // Finally, draw a dashed border around the edge to show the edge boundaries. // This routine assumes that the canvas is square. const int lineSize = 7; - const int margin = 1; + const int margin = 4; var black = new Color { R = 0, G = 0, B = 0 }; var yellow = new Color { R = 255, G = 255, B = 0 }; @@ -60,18 +75,90 @@ namespace Sample.Sprites { } } - protected override void OnDraw() { + private void GeneratePlayers() { + _player1 = new Player(new Color { R = 255, G = 64, B = 64 }, 2, 1); + _player2 = new Player(new Color { R = 64, G = 64, B = 255 }, 3, 2); + + _player1.Position.X = 24; + _player1.Position.Y = 24; + _player1Delta = new Point(); + + _player2.Position.X = 96; + _player2.Position.Y = 24; + _player2Delta = new Point(); + + Sprites.Add(_player1); + Sprites.Add(_player2); } private void OnKeyPressed(object sender, KeyboardEvent e) { - if(e.Keycode == Keycode.Escape) - Stop(); + switch(e.Keycode) { + case Keycode.Escape: + Stop(); + break; + case Keycode.W: + _player1Delta.Y = -1; + break; + case Keycode.S: + _player1Delta.Y = 1; + break; + case Keycode.A: + _player1Delta.X = -1; + break; + case Keycode.D: + _player1Delta.X = 1; + break; + case Keycode.Up: + _player2Delta.Y = -1; + break; + case Keycode.Down: + _player2Delta.Y = 1; + break; + case Keycode.Left: + _player2Delta.X = -1; + break; + case Keycode.Right: + _player2Delta.X = 1; + break; + } } private void OnKeyReleased(object sender, KeyboardEvent e) { + switch(e.Keycode) { + case Keycode.W: + case Keycode.S: + _player1Delta.Y = 0; + break; + case Keycode.A: + case Keycode.D: + _player1Delta.X = 0; + break; + case Keycode.Up: + case Keycode.Down: + _player2Delta.Y = 0; + break; + case Keycode.Left: + case Keycode.Right: + _player2Delta.X = 0; + break; + + } } protected override void OnUpdate() { + _player1.Move(_player1Delta); + _player2.Move(_player2Delta); + + /*_camX += _deltaX; + _camY += _deltaY; + + if(_camX + Background.Clipping.Size.X >= Background.Width || _camX <= 0) + _deltaX = -_deltaX; + if(_camY + Background.Clipping.Size.Y >= Background.Height || _camY <= 0) + _deltaY = -_deltaY; + + Background.Clipping.Position.X = _camX; + Background.Clipping.Position.Y = _camY;*/ } } }