From d7213bcd70d7b2553e62461faf6e91db4724c893 Mon Sep 17 00:00:00 2001 From: Ian Burgmyer Date: Wed, 17 Jul 2019 08:10:23 -0400 Subject: [PATCH] Support for multiple background layers. * An arbitrary number of background layers can now be created and deleted using AddLayer and RemoveLayer. * The SdlWindow's Background object now points to Layer 0, which cannot be deleted. This should ensure that no code has been broken by this. * A basic sample project has been added to demonstrate this new feature. --- DotSDL.sln | 11 ++++ DotSDL/Graphics/SdlWindow.cs | 99 ++++++++++++++++++++++-------- DotSDL/Graphics/Sprite.cs | 2 +- Samples/Sample.Layers/Program.cs | 8 +++ Samples/Sample.Layers/Sample.Layers.csproj | 13 ++++ Samples/Sample.Layers/Window.cs | 91 +++++++++++++++++++++++++++ 6 files changed, 196 insertions(+), 28 deletions(-) create mode 100644 Samples/Sample.Layers/Program.cs create mode 100644 Samples/Sample.Layers/Sample.Layers.csproj create mode 100644 Samples/Sample.Layers/Window.cs diff --git a/DotSDL.sln b/DotSDL.sln index 907d5db..0c9955f 100644 --- a/DotSDL.sln +++ b/DotSDL.sln @@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Power", "Samples\Sam EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Sprites", "Samples\Sample.Sprites\Sample.Sprites.csproj", "{305585D5-BD22-400D-80AA-459E05A9E243}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Layers", "Samples\Sample.Layers\Sample.Layers.csproj", "{23D3396E-6B46-4177-ACBB-CEE66910F05B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -69,6 +71,14 @@ Global {305585D5-BD22-400D-80AA-459E05A9E243}.Release|x64.Build.0 = Release|Any CPU {305585D5-BD22-400D-80AA-459E05A9E243}.Release|x86.ActiveCfg = Release|Any CPU {305585D5-BD22-400D-80AA-459E05A9E243}.Release|x86.Build.0 = Release|Any CPU + {23D3396E-6B46-4177-ACBB-CEE66910F05B}.Debug|x64.ActiveCfg = Debug|Any CPU + {23D3396E-6B46-4177-ACBB-CEE66910F05B}.Debug|x64.Build.0 = Debug|Any CPU + {23D3396E-6B46-4177-ACBB-CEE66910F05B}.Debug|x86.ActiveCfg = Debug|Any CPU + {23D3396E-6B46-4177-ACBB-CEE66910F05B}.Debug|x86.Build.0 = Debug|Any CPU + {23D3396E-6B46-4177-ACBB-CEE66910F05B}.Release|x64.ActiveCfg = Release|Any CPU + {23D3396E-6B46-4177-ACBB-CEE66910F05B}.Release|x64.Build.0 = Release|Any CPU + {23D3396E-6B46-4177-ACBB-CEE66910F05B}.Release|x86.ActiveCfg = Release|Any CPU + {23D3396E-6B46-4177-ACBB-CEE66910F05B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -80,5 +90,6 @@ Global {B71D69F1-3BB1-4CC8-AB6A-2A0F2DAE9FE5} = {70DA3135-B76E-421D-B9CF-E49CD6440B0A} {45E8AA1C-97DC-4889-B64E-2874BCAC6D40} = {70DA3135-B76E-421D-B9CF-E49CD6440B0A} {305585D5-BD22-400D-80AA-459E05A9E243} = {70DA3135-B76E-421D-B9CF-E49CD6440B0A} + {23D3396E-6B46-4177-ACBB-CEE66910F05B} = {70DA3135-B76E-421D-B9CF-E49CD6440B0A} EndGlobalSection EndGlobal diff --git a/DotSDL/Graphics/SdlWindow.cs b/DotSDL/Graphics/SdlWindow.cs index 5b39347..9998b15 100644 --- a/DotSDL/Graphics/SdlWindow.cs +++ b/DotSDL/Graphics/SdlWindow.cs @@ -2,6 +2,7 @@ using DotSDL.Input; using DotSDL.Interop.Core; using System; +using System.Collections.Generic; using System.Linq; namespace DotSDL.Graphics { @@ -18,8 +19,6 @@ namespace DotSDL.Graphics { private IntPtr _texture; private bool _hasTexture; - public readonly Canvas Background; - private bool _running; private uint _nextVideoUpdate; @@ -27,6 +26,18 @@ namespace DotSDL.Graphics { private ScalingQuality _scalingQuality = ScalingQuality.Nearest; + /// Gets the background layer of this window. This is equivalent to accessing Layers[0]. + public Canvas Background => Layers[0]; + + /// Gets the list of background layers for this window. + public List Layers { get; } + + /// + /// Gets the number of background layers for this windows. This number will always be greater + /// than or equal to 1. + /// + public int LayerCount => Layers.Count; + /// true if this instance has been destroyed, othersize false. public bool IsDestroyed { get; set; } @@ -50,9 +61,9 @@ namespace DotSDL.Graphics { } /// Gets the width of the rendering target used by this . - public int TextureWidth { get; } + public int RenderWidth { get; } /// Gets the height of the rendering target used by this . - public int TextureHeight { get; } + public int RenderHeight { get; } /// The amount of time, in milliseconds, from when the application was started. public uint TicksElapsed => Timer.GetTicks(); @@ -144,9 +155,9 @@ namespace DotSDL.Graphics { /// A representing the starting position of the window. The X and Y coordinates of the Point can be set to or . /// The width of the window. /// The height of the window. - /// The width of the window's texture. - /// The height of the window's texture. - public SdlWindow(string title, Point position, int windowWidth, int windowHeight, int textureWidth, int textureHeight) : this(title, position, windowWidth, windowHeight, textureWidth, textureHeight, ScalingQuality.Nearest) { } + /// The width of the rendering target. + /// The height of the rendering target. + public SdlWindow(string title, Point position, int windowWidth, int windowHeight, int renderWidth, int renderHeight) : this(title, position, windowWidth, windowHeight, renderWidth, renderHeight, ScalingQuality.Nearest) { } /// /// Creates a new . @@ -155,10 +166,10 @@ namespace DotSDL.Graphics { /// A representing the starting position of the window. The X and Y coordinates of the Point can be set to or . /// The width of the window. /// The height of the window. - /// The width of the window's texture. - /// The height of the window's texture. + /// The width of the rendering target. + /// The height of the rendering target. /// The scaling (filtering) method to use for the background canvas texture. - public SdlWindow(string title, Point position, int windowWidth, int windowHeight, int textureWidth, int textureHeight, ScalingQuality scalingQuality) { + public SdlWindow(string title, Point position, int windowWidth, int windowHeight, int renderWidth, int renderHeight, ScalingQuality scalingQuality) { _sdlInit.InitSubsystem(Init.SubsystemFlags.Video); _windowTitle = title; @@ -168,15 +179,17 @@ namespace DotSDL.Graphics { WindowWidth = windowWidth; WindowHeight = windowHeight; - TextureWidth = textureWidth; - TextureHeight = textureHeight; + RenderWidth = renderWidth; + RenderHeight = renderHeight; ScalingQuality = scalingQuality; CreateTexture(); - Background = new Background(textureWidth, textureHeight) { - Renderer = _renderer, - BlendMode = BlendMode.None + Layers = new List { + new Background(renderWidth, renderHeight) { + Renderer = _renderer, + BlendMode = BlendMode.None + } }; Background.CreateTexture(); @@ -199,6 +212,24 @@ namespace DotSDL.Graphics { _resources.UnregisterResource(this); } + /// + /// Adds a background layer to the top of the layer stack. + /// + /// The width of the new layer's texture. + /// The height of the new layer's texture. + /// The blending mode of the new layer. + /// An integer identifying the new layer. + public int AddLayer(int width, int height, BlendMode blendMode = BlendMode.Alpha) { + var layer = new Background(width, height) { + Renderer = _renderer, + BlendMode = blendMode + }; + layer.CreateTexture(); + Layers.Add(layer); + + return Layers.Count - 1; + } + /// /// Handles calling the user draw function and passing the CLR objects to SDL2. /// @@ -209,13 +240,15 @@ namespace DotSDL.Graphics { Render.SetRenderTarget(_renderer, _texture); - // Blit the Canvas to the target texture. - Background.Clipping.Position = CameraView.Position; - Background.Clipping.Size = CameraView.Size; - Background.UpdateTexture(); - unsafe { - var canvasClippingRect = Background.Clipping.SdlRect; - Render.RenderCopy(_renderer, Background.Texture, new IntPtr(&canvasClippingRect), IntPtr.Zero); + // Blit the background canvases to the target texture. + foreach(var canvas in Layers) { + canvas.Clipping.Position = CameraView.Position; + canvas.Clipping.Size = CameraView.Size; + canvas.UpdateTexture(); + unsafe { + var canvasClippingRect = canvas.Clipping.SdlRect; + Render.RenderCopy(_renderer, canvas.Texture, new IntPtr(&canvasClippingRect), IntPtr.Zero); + } } // Plot sprites on top of the background layer. @@ -250,7 +283,7 @@ namespace DotSDL.Graphics { private void CreateTexture() { DestroyTexture(); Hints.SetHint(Hints.RenderScaleQuality, ScalingQuality.ToString()); - _texture = Render.CreateTexture(_renderer, Pixels.PixelFormatArgb8888, Render.TextureAccess.Target, TextureWidth, TextureHeight); + _texture = Render.CreateTexture(_renderer, Pixels.PixelFormatArgb8888, Render.TextureAccess.Target, RenderWidth, RenderHeight); _hasTexture = true; } @@ -291,11 +324,11 @@ namespace DotSDL.Graphics { // and this sprite. var relPosition = new Point(sprite.Position - CameraView.Position); var screenPosition = new Point( - (int)((float)relPosition.X / CameraView.Size.X * TextureWidth), - (int)((float)relPosition.Y / CameraView.Size.Y * TextureHeight) + (int)((float)relPosition.X / CameraView.Size.X * RenderWidth), + (int)((float)relPosition.Y / CameraView.Size.Y * RenderHeight) ); - var scaleFactorX = (float)TextureWidth / CameraView.Size.X; - var scaleFactorY = (float)TextureHeight / CameraView.Size.Y; + var scaleFactorX = (float)RenderWidth / CameraView.Size.X; + var scaleFactorY = (float)RenderHeight / CameraView.Size.Y; var size = new Point( (int)(drawSize.X * scaleFactorX), (int)(drawSize.Y * scaleFactorY) @@ -430,6 +463,18 @@ namespace DotSDL.Graphics { /// protected virtual void OnUpdate() { } + /// + /// Removes a layer from the layer stack. + /// + /// The unique identifier of the layer to remove. + /// The background layer (layer 0) cannot be deleted. + /// id is less than 0. + /// /// id is equal to or greater than . + public void RemoveLayer(int id) { + if(id == 0) throw new ArgumentOutOfRangeException(nameof(id), "The background object (layer 0) cannot be deleted."); + Layers.RemoveAt(id); + } + /// /// Displays the window and begins executing code that's associated with it. /// diff --git a/DotSDL/Graphics/Sprite.cs b/DotSDL/Graphics/Sprite.cs index a787122..dbc4f61 100644 --- a/DotSDL/Graphics/Sprite.cs +++ b/DotSDL/Graphics/Sprite.cs @@ -7,7 +7,7 @@ namespace DotSDL.Graphics { /// Represents a graphical, two-dimensional object in a program. /// public class Sprite : Canvas { - private Point _effectiveSize = new Point(); + private readonly Point _effectiveSize = new Point(); private Vector2 _scale; /// diff --git a/Samples/Sample.Layers/Program.cs b/Samples/Sample.Layers/Program.cs new file mode 100644 index 0000000..83d2dfe --- /dev/null +++ b/Samples/Sample.Layers/Program.cs @@ -0,0 +1,8 @@ +namespace Sample.Layers { + internal class Program { + private static void Main(string[] args) { + var window = new Window(4); + window.Start(16); + } + } +} diff --git a/Samples/Sample.Layers/Sample.Layers.csproj b/Samples/Sample.Layers/Sample.Layers.csproj new file mode 100644 index 0000000..0280ee2 --- /dev/null +++ b/Samples/Sample.Layers/Sample.Layers.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp2.2 + 7.2 + + + + + + + diff --git a/Samples/Sample.Layers/Window.cs b/Samples/Sample.Layers/Window.cs new file mode 100644 index 0000000..262b24a --- /dev/null +++ b/Samples/Sample.Layers/Window.cs @@ -0,0 +1,91 @@ +using System; +using DotSDL.Events; +using DotSDL.Graphics; +using DotSDL.Input.Keyboard; + +namespace Sample.Layers { + public class Window : SdlWindow { + private const double Amplitude = 32.0f; + private const double Period = Math.PI * 4; + + private readonly int _redLayer, _greenLayer, _blueLayer; + + private double _redPhase = 0.0, _greenPhase = 2.0, _bluePhase = 4.0; + private const double RedSpeed = 0.010, GreenSpeed = 0.015, BlueSpeed = 0.020; + + public Window(int scale) : base("Layer Test", + new Point(WindowPosUndefined, WindowPosUndefined), + 256 * scale, 196 * scale, + 256, 196) { + KeyPressed += OnKeyPressed; + + // Base background layer. + for(var i = RenderHeight / 2 * RenderWidth; i < RenderHeight / 2 * RenderWidth + RenderWidth; i++) { + Background.Pixels[i].A = 255; + Background.Pixels[i].R = + Background.Pixels[i].G = + Background.Pixels[i].B = 32; + } + + for(var i = (RenderHeight / 2 + 1) * RenderWidth; i < RenderWidth * RenderHeight; i++) { + Background.Pixels[i].A = 255; + Background.Pixels[i].R = + Background.Pixels[i].G = + Background.Pixels[i].B = 64; + } + + // Add new layers. + _redLayer = AddLayer(RenderWidth, RenderHeight, BlendMode.Additive); + _greenLayer = AddLayer(RenderWidth, RenderHeight, BlendMode.Additive); + _blueLayer = AddLayer(RenderWidth, RenderHeight, BlendMode.Additive); + } + + private void DrawSlice(in Color[] pixels, int x, double phase, byte rVal, byte gVal, byte bVal) { + var val = Math.Abs(Math.Sin((double)x / RenderWidth * Period + phase)); + + var yMax = val * Amplitude; + var yMaxInt = (int)yMax; + var center = RenderHeight / 2; + + Func pos = (xPix, yPix) => (yPix + center) * RenderWidth + xPix; + + for(var y = -yMaxInt; y < yMaxInt + 1; y++) { + var idx = pos(x, y); + pixels[idx].A = 255; + pixels[idx].R = rVal; + pixels[idx].G = gVal; + pixels[idx].B = bVal; + } + + // Update the alpha value on the top/bottom pixels to make it sorta look + // antialiased. :) + pixels[pos(x, yMaxInt)].A = pixels[pos(x, -yMaxInt)].A = (byte)(255.0 * (yMax - yMaxInt)); + } + + protected override void OnDraw() { + // Clear the sine wave from each of the layers. + for(var i = (RenderHeight / 2 - (int)Amplitude) * RenderWidth; i < (RenderHeight / 2 + (int)Amplitude) * RenderWidth; i++) { + Layers[_redLayer].Pixels[i].R = 0; + Layers[_greenLayer].Pixels[i].G = 0; + Layers[_blueLayer].Pixels[i].B = 0; + } + + for(var x = 0; x < RenderWidth; x++) { + DrawSlice(Layers[_redLayer].Pixels, x, _redPhase, 255, 0, 0); + DrawSlice(Layers[_greenLayer].Pixels, x, _greenPhase, 0, 255, 0); + DrawSlice(Layers[_blueLayer].Pixels, x, _bluePhase, 0, 0, 255); + } + } + + private void OnKeyPressed(object sender, KeyboardEvent e) { + if(e.Keycode == Keycode.Escape) + Stop(); + } + + protected override void OnUpdate() { + _redPhase += RedSpeed; + _greenPhase += GreenSpeed; + _bluePhase += BlueSpeed; + } + } +}