Browse Source

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.
improved_timing
Ian Burgmyer 5 years ago
parent
commit
bfc43b9463
  1. 56
      DotSDL/Graphics/Canvas.cs
  2. 49
      DotSDL/Graphics/SdlWindow.cs
  3. 24
      DotSDL/Graphics/Sprite.cs
  4. 9
      DotSDL/Graphics/SpriteList.cs
  5. 55
      Samples/Sample.Sprites/Player.cs
  6. 95
      Samples/Sample.Sprites/Window.cs

56
DotSDL/Graphics/Canvas.cs

@ -9,7 +9,11 @@ namespace DotSDL.Graphics {
/// </summary>
public class Canvas {
private int _width, _height;
private bool _hasTexture;
/// <summary>
/// <c>true</c> if this <see cref="Canvas"/> has an SDL texture associated with it, otherwise <c>false</c>.
/// </summary>
protected bool HasTexture { get; set; }
/// <summary>
/// The SDL_Texture that this <see cref="Canvas"/> maintains.
@ -22,6 +26,16 @@ namespace DotSDL.Graphics {
/// </summary>
public Color[] Pixels;
/// <summary>
/// Gets an <see cref="IntPtr"/> 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 <see cref="Canvas"/>
/// object. You usually do not need to override this method.
/// </summary>
/// <remarks>If an invalid <see cref="IntPtr"/> is generated by this method, your application may
/// crash with a segmentation fault. When in doubt, override <see cref="OnDraw"/> instead!</remarks>
/// <returns>An <see cref="IntPtr"/> containing the contents of the window's background.</returns>
public Func<IntPtr> GetCanvasPointer;
/// <summary>
/// Gets or sets the width of the <see cref="Canvas"/> texture.
/// </summary>
@ -74,28 +88,45 @@ namespace DotSDL.Graphics {
Clipping = clipping;
GetCanvasPointer = () => {
unsafe {
fixed(void* pixelPtr = Pixels) {
return (IntPtr)pixelPtr;
}
}
};
Resize();
}
/// <summary>
/// Creates a texture or recreates it if it already exists.
/// </summary>
internal void CreateTexture() {
internal virtual void CreateTexture() {
CreateTexture(Render.TextureAccess.Streaming);
}
/// <summary>
/// Creates a texture or recreates it if it already exists.
/// </summary>
/// <param name="textureAccess">The access mode for this texture.</param>
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;
}
/// <summary>
/// Destroys the texture associated with this <see cref="Sprite"/>.
/// </summary>
internal void DestroyTexture() {
if(!_hasTexture) return;
if(!HasTexture) return;
Render.DestroyTexture(Texture);
_hasTexture = false;
HasTexture = false;
}
/// <summary>
@ -124,8 +155,19 @@ namespace DotSDL.Graphics {
protected void Resize() {
Pixels = new Color[Width * Height];
if(_hasTexture)
if(HasTexture)
CreateTexture();
}
/// <summary>
/// Updates the texture associated with this <see cref="Canvas"/>. This function must be called when the
/// <see cref="Canvas.Pixels"/> array is changed.
/// </summary>
/// <returns><c>true</c> if the texture was successfully updated, otherwise <c>false</c>. This will return <c>false</c> if this <see cref="Sprite"/> hasn't been added to the sprite list.</returns>
internal bool UpdateTexture() {
if(!HasTexture) return false;
Render.UpdateTexture(Texture, IntPtr.Zero, GetCanvasPointer(), Width * 4);
return true;
}
}
}

49
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.
/// </summary>
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
);
}
}
/// <summary>
/// Gets an <see cref="IntPtr"/> 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 <see cref="Canvas"/>
/// object. You usually do not need to override this method.
/// </summary>
/// <remarks>If an invalid <see cref="IntPtr"/> is generated by this method, your application may
/// crash with a segmentation fault. When in doubt, override <see cref="OnDraw"/> instead!</remarks>
/// <returns>An <see cref="IntPtr"/> containing the contents of the window's background.</returns>
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
);
}
}
}

24
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 {
/// <summary>
@ -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);
}
/// <summary>
/// Creates a texture or recreates it if it already exists.
/// </summary>
internal override void CreateTexture() {
CreateTexture(Render.TextureAccess.Static);
}
/// <summary>
/// Updates the texture associated with this <see cref="Sprite"/>. This function must be called when the
/// <see cref="Canvas.Pixels"/> array is changed after adding this sprite to the sprite list associated
/// with the application's <see cref="SdlWindow"/>.
/// </summary>
/// <returns><c>true</c> if the texture was successfully updated, otherwise <c>false</c>. This will return <c>false</c> if this <see cref="Sprite"/> hasn't been added to the sprite list.</returns>
public new bool UpdateTexture() {
return base.UpdateTexture();
}
}
}

9
DotSDL/Graphics/SpriteList.cs

@ -83,12 +83,13 @@ namespace DotSDL.Graphics {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Creates a texture for a sprite prior to adding it to the list.
/// Creates a texture for a <see cref="Sprite"/> prior to adding it to the list.
/// </summary>
/// <param name="sprite"></param>
/// <param name="sprite">The <see cref="Sprite"/> that needs to be initialized.</param>
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();
}
/// <summary>

55
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;
}
}
}

95
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;*/
}
}
}

Loading…
Cancel
Save