Browse Source

Background canvas changes.

* Background canvas is now decoupled from the window. It can now be
      sized independently of the window.
    * Canvases/Sprites now have their own textures that must be
      initialized with the renderer prior to them being used. This is
      all handled by DotSDL.
    * A SpriteList class has been created to maintain collections of
      Sprites, replacing the old List<Sprite> system. This was required
      to ensure that the Sprite textures are properly allocated and
      freed during creation/destruction.
    * Background clipping now functions as expected. The background
      Canvas can now be cropped and scrolled at will.
    * Background rendering has changed from directly blitting the canvas
      onto a texture to using an intermediate target texture.
    * Updated the Sample.Sprites project to test/demonstrate the new
      Canvas features. Project is still incomplete.
improved_timing
Ian Burgmyer 5 years ago
parent
commit
daa6a6b392
  1. 35
      DotSDL/Graphics/Canvas.cs
  2. 30
      DotSDL/Graphics/SdlWindow.cs
  3. 130
      DotSDL/Graphics/SpriteList.cs
  4. 24
      DotSDL/Interop/Core/Render.cs
  5. 2
      Samples/Sample.Sprites/Program.cs
  6. 46
      Samples/Sample.Sprites/Window.cs

35
DotSDL/Graphics/Canvas.cs

@ -1,4 +1,6 @@
using System;
using DotSDL.Interop.Core;
using SdlPixels = DotSDL.Interop.Core.Pixels;
using System;
namespace DotSDL.Graphics {
/// <summary>
@ -7,6 +9,13 @@ namespace DotSDL.Graphics {
/// </summary>
public class Canvas {
private int _width, _height;
private bool _hasTexture;
/// <summary>
/// The SDL_Texture that this <see cref="Canvas"/> maintains.
/// </summary>
internal IntPtr Renderer;
internal IntPtr Texture;
/// <summary>
/// The raw pixels in the <see cref="Canvas"/>.
@ -68,6 +77,27 @@ namespace DotSDL.Graphics {
Resize();
}
/// <summary>
/// Creates a texture or recreates it if it already exists.
/// </summary>
internal void CreateTexture() {
if(Renderer == IntPtr.Zero) return;
DestroyTexture();
Texture = Render.CreateTexture(Renderer, SdlPixels.PixelFormatArgb8888, Render.TextureAccess.Streaming, Width, Height);
_hasTexture = true;
}
/// <summary>
/// Destroys the texture associated with this <see cref="Sprite"/>.
/// </summary>
internal void DestroyTexture() {
if(!_hasTexture) return;
Render.DestroyTexture(Texture);
_hasTexture = false;
}
/// <summary>
/// Retrieves an array index on the <see cref="Canvas"/>.
/// </summary>
@ -93,6 +123,9 @@ namespace DotSDL.Graphics {
/// </summary>
protected void Resize() {
Pixels = new Color[Width * Height];
if(_hasTexture)
CreateTexture();
}
}
}

30
DotSDL/Graphics/SdlWindow.cs

@ -4,6 +4,7 @@ using DotSDL.Interop.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace DotSDL.Graphics {
/// <summary>
@ -50,7 +51,7 @@ namespace DotSDL.Graphics {
public uint GameUpdateTicks { get; set; }
/// <summary>The list of active <see cref="Sprite"/> objects.</summary>
public List<Sprite> Sprites { get; }
public SpriteList Sprites { get; }
/// <summary>Indicates that the window manager should position the window. To place the window on a specific display, use the <see cref="WindowPosCenteredDisplay"/> function.</summary>
public const int WindowPosUndefined = 0x1FFF0000;
@ -141,10 +142,11 @@ namespace DotSDL.Graphics {
// Everything should be kept as nearest *except* for the target texture.
SetScalingQuality(scalingQuality);
_texture = Render.CreateTexture(_renderer, Pixels.PixelFormatArgb8888, Render.TextureAccess.Streaming, textureWidth, textureHeight);
_texture = Render.CreateTexture(_renderer, Pixels.PixelFormatArgb8888, Render.TextureAccess.Target, textureWidth, textureHeight);
SetScalingQuality(ScalingQuality.Nearest);
Background = new Canvas(textureWidth, textureHeight);
Background = new Canvas(textureWidth, textureHeight) { Renderer = _renderer };
Background.CreateTexture();
WindowWidth = windowWidth;
WindowHeight = windowHeight;
@ -152,7 +154,7 @@ namespace DotSDL.Graphics {
TextureWidth = textureWidth;
TextureHeight = textureHeight;
Sprites = new List<Sprite>();
Sprites = new SpriteList(_renderer);
IsDestroyed = false;
_resources.RegisterResource(this);
@ -174,9 +176,23 @@ namespace DotSDL.Graphics {
OnDraw();
Render.UpdateTexture(_texture, IntPtr.Zero, GetCanvasPointer(), TextureWidth * 4);
Render.RenderCopy(_renderer, _texture, IntPtr.Zero, IntPtr.Zero);
Render.SetRenderTarget(_renderer, _texture);
// Blit the Canvas to the target texture.
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);
}
// Plot sprites on top of the background layer.
if(Sprites.Count > 0) DrawSprites();
Render.SetRenderTarget(_renderer, IntPtr.Zero);
Render.RenderCopy(_renderer, _texture, IntPtr.Zero, IntPtr.Zero);
Render.RenderPresent(_renderer);
}
@ -244,7 +260,7 @@ namespace DotSDL.Graphics {
/// <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 unsafe virtual IntPtr GetCanvasPointer() {
public virtual unsafe IntPtr GetCanvasPointer() {
fixed(void* pixelsPtr = Background.Pixels) {
return (IntPtr)pixelsPtr;
}

130
DotSDL/Graphics/SpriteList.cs

@ -0,0 +1,130 @@
using System;
using System.Collections;
using System.Collections.Generic;
using DotSDL.Interop.Core;
namespace DotSDL.Graphics {
/// <summary>
/// Represents a collection of <see cref="Sprite"/> objects.
/// </summary>
public class SpriteList : IList<Sprite> {
private readonly IntPtr _renderer;
private readonly List<Sprite> _sprites = new List<Sprite>();
/// <summary>Retrieves the number of <see cref="Sprite"/> objects contained in this <see cref="SpriteList"/>.</summary>
public int Count => _sprites.Count;
/// <summary>Indicates whether or not this <see cref="SpriteList"/> is read-only.</summary>
public bool IsReadOnly => false;
/// <inheritdoc/>
public Sprite this[int index] {
get => _sprites[index];
set {
DestroySprite(_sprites[index]);
InitializeSprite(value);
_sprites[index] = value;
}
}
/// <summary>
/// Creates a new <see cref="SpriteList"/>.
/// </summary>
/// <param name="renderer">The SDL2 rendering context that should be used.</param>
public SpriteList(IntPtr renderer) {
_renderer = renderer;
}
/// <summary>
/// Adds a <see cref="Sprite"/> to this <see cref="SpriteList"/>.
/// </summary>
/// <param name="item">The <see cref="Sprite"/> to add.</param>
public void Add(Sprite item) {
InitializeSprite(item);
_sprites.Add(item);
}
/// <summary>
/// Removes all items from this <see cref="SpriteList"/>.
/// </summary>
public void Clear() {
_sprites.ForEach(DestroySprite);
_sprites.Clear();
}
/// <summary>
/// Determines whether this <see cref="SpriteList"/> contains a particular instance
/// of a <see cref="Sprite"/>.
/// </summary>
/// <param name="item">The <see cref="Sprite"/> instance to look for.</param>
/// <returns><c>true</c> if the <see cref="Sprite"/> instance in <paramref name="item"/> was found, otherwise <c>false</c>.</returns>
public bool Contains(Sprite item) => _sprites.Contains(item);
/// <summary>
/// Copies the eleents on this <see cref="SpriteList"/> into an array, starting at
/// index <paramref name="arrayIndex"/>.
/// </summary>
/// <param name="array">The array to copy this <see cref="SpriteList"/> into.</param>
/// <param name="arrayIndex">The intex to start copying from.</param>
public void CopyTo(Sprite[] array, int arrayIndex) => _sprites.CopyTo(array, arrayIndex);
/// <summary>
/// Frees the texture used by a sprite prior to removing it from the list.
/// </summary>
/// <param name="sprite">The <see cref="Sprite"/> to destroy.</param>
private void DestroySprite(Canvas sprite) {
Render.DestroyTexture(sprite.Texture);
}
/// <inheritdoc/>
public IEnumerator<Sprite> GetEnumerator() => _sprites.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Creates a texture for a sprite prior to adding it to the list.
/// </summary>
/// <param name="sprite"></param>
private void InitializeSprite(Canvas sprite) {
sprite.Texture = Render.CreateTexture(_renderer, Pixels.PixelFormatArgb8888, Render.TextureAccess.Static,
sprite.Width, sprite.Height);
}
/// <summary>
/// Removes a <see cref="Sprite"/> from this <see cref="SpriteList"/>.
/// </summary>
/// <param name="item">The <see cref="Sprite"/> to remove.</param>
/// <returns><c>true</c> if the <see cref="Sprite"/> was successfully removed, otherwise <c>false</c>.</returns>
public bool Remove(Sprite item) {
DestroySprite(item);
return _sprites.Remove(item);
}
/// <summary>
/// Determines the index of a specific <see cref="Sprite"/> ionstnace in the <see cref="SpriteList"/>.
/// </summary>
/// <param name="item">The <see cref="Sprite"/> instance to locate.</param>
/// <returns>The index of the <see cref="Sprite"/> instance referenced by <paramref name="item"/> if it's been found in the list, otherwise -1.</returns>
public int IndexOf(Sprite item) => _sprites.IndexOf(item);
/// <summary>
/// Inserts a <see cref="Sprite"/> into the <see cref="SpriteList"/> at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
/// <param name="item">The <see cref="Sprite"/> to insert into the list.</param>
public void Insert(int index, Sprite item) {
InitializeSprite(item);
_sprites.Insert(index, item);
}
/// <summary>
/// Removes the <see cref="Sprite"/> contained at the specified index from this <see cref="SpriteList"/>.
/// </summary>
/// <param name="index">The zero-based index of the <see cref="Sprite"/> to remove.</param>
public void RemoveAt(int index) {
DestroySprite(_sprites[index]);
_sprites.RemoveAt(index);
}
}
}

24
DotSDL/Interop/Core/Render.cs

@ -63,6 +63,20 @@ namespace DotSDL.Interop.Core {
[DllImport(Meta.CoreLib, EntryPoint = "SDL_CreateTexture", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr CreateTexture(IntPtr renderer, uint format, TextureAccess access, int w, int h);
/// <summary>
/// Destroys a rendering context for a window and frees all associated textures.
/// </summary>
/// <param name="texture">The rendering context to destroy.</param>
[DllImport(Meta.CoreLib, EntryPoint = "SDL_DestroyRenderer", CallingConvention = CallingConvention.Cdecl)]
internal static extern void DestroyRenderer(IntPtr texture);
/// <summary>
/// Destroys a texture.
/// </summary>
/// <param name="texture">The texture to destroy.</param>
[DllImport(Meta.CoreLib, EntryPoint = "SDL_DestroyTexture", CallingConvention = CallingConvention.Cdecl)]
internal static extern void DestroyTexture(IntPtr texture);
/// <summary>
/// Clear the current rendering target with the drawing color.
///
@ -132,6 +146,16 @@ namespace DotSDL.Interop.Core {
[DllImport(Meta.CoreLib, EntryPoint = "SDL_RenderPresent", CallingConvention = CallingConvention.Cdecl)]
internal static extern void RenderPresent(IntPtr renderer);
/// <summary>
/// Sets a texture as the current rendering target.
/// </summary>
/// <param name="renderer">The rendering context.</param>
/// <param name="texture">The targeted texture, or <see cref="IntPtr.Zero"/> for the default render target.
/// If a texture is used, it must have been created with the <see cref="TextureAccess.Target"/> flag.</param>
/// <returns></returns>
[DllImport(Meta.CoreLib, EntryPoint = "SDL_SetRenderTarget", CallingConvention = CallingConvention.Cdecl)]
internal static extern int SetRenderTarget(IntPtr renderer, IntPtr texture);
/// <summary>
/// Update the given texture rectangle with new pixel data.
/// </summary>

2
Samples/Sample.Sprites/Program.cs

@ -1,7 +1,7 @@
namespace Sample.Sprites {
internal static class Program {
private static void Main(string[] args) {
var window = new Window(2);
var window = new Window(4);
window.Start(16);
}
}

46
Samples/Sample.Sprites/Window.cs

@ -1,6 +1,7 @@
using DotSDL.Events;
using DotSDL.Graphics;
using DotSDL.Input.Keyboard;
using System;
namespace Sample.Sprites {
public class Window : SdlWindow {
@ -11,18 +12,51 @@ namespace Sample.Sprites {
KeyPressed += OnKeyPressed;
KeyReleased += OnKeyReleased;
Background.Width = 1024;
Background.Height = 1024;
Background.Height = Background.Width = 1024;
GenerateBackground();
}
private void GenerateBackground() {
// Draw colored, diagonal strips across the entire background canvas.
for(var i = 0; i < Background.Width; i++) {
Background.Pixels[i].R = 128;
Background.Pixels[i].G = 64;
Background.Pixels[i].B = 32;
var stripRef = new Color[Background.Width * 2];
for(var i = 0; i < Background.Width * 2; i++) {
stripRef[i].R = (byte)(Math.Abs(i % 511 - 255) / 2);
stripRef[i].G = (byte)(80 / (stripRef[i].R + 4) / 2);
stripRef[i].B = (byte)(120 * (Math.Sin(i * Math.PI / 128 + 256) * 0.2 + 0.8));
}
for(var y = 0; y < Background.Height; y++) {
var stripIdx = y % Background.Width;
for(var x = 0; x < Background.Width; x++) {
var pix = y * Background.Width + x;
Background.Pixels[pix] = stripRef[stripIdx + x];
}
}
// 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;
var black = new Color { R = 0, G = 0, B = 0 };
var yellow = new Color { R = 255, G = 255, B = 0 };
for(var i = margin; i < Background.Width - margin; i++) {
// Y axis.
var activeColor = (i - margin) / lineSize % 2 == 1 ? black : yellow;
var pix = Background.Width * i + margin;
Background.Pixels[pix] = activeColor;
pix = Background.Width * i + Background.Width - 1 - margin;
Background.Pixels[pix] = activeColor;
// X axis.
activeColor = (i - margin) / lineSize % 2 == 1 ? yellow : black;
pix = Background.Width * margin + i;
Background.Pixels[pix] = activeColor;
pix = Background.Width * (Background.Height - 1 - margin) + i;
Background.Pixels[pix] = activeColor;
}
}

Loading…
Cancel
Save