@ -1,8 +1,10 @@
using DotSDL.Events ;
using DotSDL.Input ;
using DotSDL.Interop.Core ;
using DotSDL.Platform ;
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Linq ;
namespace DotSDL.Graphics {
@ -19,13 +21,26 @@ namespace DotSDL.Graphics {
private IntPtr _ texture ;
private bool _ hasTexture ;
private float _ videoUpdateRate , _ gameUpdateRate ;
private float _ videoUpdateMs , _ gameUpdateMs ;
private bool _ videoUpdateUncapped , _ gameUpdateUncapped ;
private bool _ running ;
private uint _ nextVideoUpdate ;
private uint _ nextGameUpdate ;
private float _ nextVideoUpdate ;
private float _ nextGameUpdate ;
private float _ updateDelta ;
private List < Canvas > _d rawList = new List < Canvas > ( ) ;
private ScalingQuality _ scalingQuality = ScalingQuality . Nearest ;
/// <summary>
/// An <see cref="IPlatform"/> that contains native functions appropriate to
/// the platform that this application is running on.
/// </summary>
protected IPlatform Platform { get ; } = PlatformFactory . GetPlatform ( ) ;
/// <summary>Gets the background layer of this window. This is equivalent to accessing Layers[0].</summary>
public Canvas Background = > Layers [ 0 ] ;
@ -66,11 +81,35 @@ namespace DotSDL.Graphics {
public int RenderHeight { get ; }
/// <summary>The amount of time, in milliseconds, from when the application was started.</summary>
public uint TicksElapsed = > Timer . GetTicks ( ) ;
/// <summary>Gets or sets the amount of time, in milliseconds, between video updates.</summary>
public uint VideoUpdateTicks { get ; set ; }
/// <summary>Gets or sets the amount of time, in milliseconds, between game (logic) updates.</summary>
public uint GameUpdateTicks { get ; set ; }
public float MillisecondsElapsed { get ; private set ; } = 0.0f ;
/// <summary>Gets or sets the rate, in hertz, between video updates.</summary>
public float VideoUpdateRate {
get = > _ videoUpdateUncapped ? 0 : _ videoUpdateRate ;
set {
_ videoUpdateRate = value ;
if ( System . Math . Abs ( _ videoUpdateRate ) < 1.0f ) {
_ videoUpdateUncapped = true ;
} else {
_ videoUpdateUncapped = false ;
_ videoUpdateMs = 1 0 0 0 / _ videoUpdateRate ;
}
}
}
/// <summary>Gets or sets the rate, in hertz, between game (logic) updates.</summary>
public float GameUpdateRate {
get = > _ gameUpdateUncapped ? 0 : _ gameUpdateRate ;
set {
_ gameUpdateRate = value ;
if ( System . Math . Abs ( _ gameUpdateRate ) < 1.0f ) {
_ gameUpdateUncapped = true ;
} else {
_ gameUpdateUncapped = false ;
_ gameUpdateMs = 1 0 0 0 / _ gameUpdateRate ;
}
}
}
/// <summary>Gets a <see cref="Rectangle"/> that can be manipulated to modify how much of the scene is displayed.</summary>
public Rectangle CameraView { get ; }
@ -191,6 +230,7 @@ namespace DotSDL.Graphics {
BlendMode = BlendMode . None
}
} ;
Background . ZOrder = int . MinValue ;
Background . CreateTexture ( ) ;
CameraView = new Rectangle (
@ -240,20 +280,29 @@ namespace DotSDL.Graphics {
Render . SetRenderTarget ( _ renderer , _ texture ) ;
// 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 ) ;
// Sort all of the canvases, then draw them.
_d rawList . Clear ( ) ;
_d rawList . AddRange ( Layers . Where ( l = > l . Shown ) . ToArray ( ) ) ;
_d rawList . AddRange ( Sprites . Where ( s = > s . Shown ) . ToArray ( ) ) ;
foreach ( var canvas in _d rawList . OrderBy ( layer = > layer . ZOrder ) ) {
switch ( canvas ) {
case Sprite sprite :
DrawSprite ( sprite ) ;
break ;
default :
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 ) ;
}
break ;
}
}
// 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 ) ;
@ -270,11 +319,11 @@ namespace DotSDL.Graphics {
/// <summary>
/// Handles updating the application logic for the <see cref="SdlWindow"/>.
/// </summary>
private void BaseUpdate ( ) {
private void BaseUpdate ( float delta ) {
if ( IsDestroyed ) return ;
Events . EventHandler . ProcessEvents ( ) ;
OnUpdate ( ) ; // Call the overridden Update function.
OnUpdate ( delta ) ; // Call the overridden Update function.
}
/// <summary>
@ -306,61 +355,58 @@ namespace DotSDL.Graphics {
}
/// <summary>
/// Plots the sprites stored in <see cref="Sprites"/> to the screen. Please note that this method is called by
/// 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 void DrawSprites ( ) {
Render . SetRenderTarget ( _ renderer , _ texture ) ;
foreach ( var sprite in Sprites . Where ( e = > e . Shown ) . OrderBy ( e = > e . ZOrder ) ) {
var srcRect = sprite . Clipping . SdlRect ;
var drawSize = sprite . DrawSize ;
Rectangle dest ;
Point rotationCenter ;
if ( sprite . CoordinateSystem = = CoordinateSystem . ScreenSpace ) {
dest = new Rectangle ( sprite . Position , drawSize ) ;
rotationCenter = sprite . RotationCenter ;
} else {
// Create a set of world coordinates based on the position of the camera
// and this sprite.
var relPosition = new Point ( sprite . Position - CameraView . Position ) ;
var screenPosition = new Point (
( int ) ( ( float ) relPosition . X / CameraView . Size . X * RenderWidth ) ,
( int ) ( ( float ) relPosition . Y / CameraView . Size . Y * RenderHeight )
) ;
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 )
) ;
dest = new Rectangle ( screenPosition , size ) ;
rotationCenter = new Point (
( int ) ( sprite . RotationCenter . X * scaleFactorX ) ,
( int ) ( sprite . RotationCenter . Y * scaleFactorY )
) ;
}
/// Plots a sprite to the screen. Please note that this method is called by DotSDL's drawing
/// routines and does not need to be called manually. Additionally, this method will never be
/// called if there are no active sprites. You usually do not need to override this method.
/// </summary>
public virtual void DrawSprite ( Sprite sprite ) {
var srcRect = sprite . Clipping . SdlRect ;
var drawSize = sprite . DrawSize ;
Rectangle dest ;
Point rotationCenter ;
if ( sprite . CoordinateSystem = = CoordinateSystem . ScreenSpace ) {
dest = new Rectangle ( sprite . Position , drawSize ) ;
rotationCenter = sprite . RotationCenter ;
} else {
// Create a set of world coordinates based on the position of the camera
// and this sprite.
var relPosition = new Point ( sprite . Position - CameraView . Position ) ;
var screenPosition = new Point (
( int ) ( ( float ) relPosition . X / CameraView . Size . X * RenderWidth ) ,
( int ) ( ( float ) relPosition . Y / CameraView . Size . Y * RenderHeight )
) ;
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 )
) ;
dest = new Rectangle ( screenPosition , size ) ;
rotationCenter = new Point (
( int ) ( sprite . RotationCenter . X * scaleFactorX ) ,
( int ) ( sprite . RotationCenter . Y * scaleFactorY )
) ;
}
var destRect = dest . SdlRect ;
var rotationCenterPoint = rotationCenter . SdlPoint ;
unsafe {
var srcRectPtr = new IntPtr ( & srcRect ) ;
var destRectPtr = new IntPtr ( & destRect ) ;
var rotationCenterPtr = new IntPtr ( & rotationCenterPoint ) ;
Render . RenderCopyEx (
renderer : _ renderer ,
texture : sprite . Texture ,
srcRect : srcRectPtr ,
dstRect : destRectPtr ,
angle : sprite . Rotation ,
center : rotationCenterPtr ,
flip : sprite . Flip
) ;
}
var destRect = dest . SdlRect ;
var rotationCenterPoint = rotationCenter . SdlPoint ;
unsafe {
var srcRectPtr = new IntPtr ( & srcRect ) ;
var destRectPtr = new IntPtr ( & destRect ) ;
var rotationCenterPtr = new IntPtr ( & rotationCenterPoint ) ;
Render . RenderCopyEx (
renderer : _ renderer ,
texture : sprite . Texture ,
srcRect : srcRectPtr ,
dstRect : destRectPtr ,
angle : sprite . Rotation ,
center : rotationCenterPtr ,
flip : sprite . Flip
) ;
}
}
@ -409,26 +455,40 @@ namespace DotSDL.Graphics {
/// A game loop that calls the <see cref="SdlWindow"/> update and draw functions.
/// </summary>
private void Loop ( ) {
long MsToNs ( float ms ) = > ( long ) ( ms * 1 0 0 0 0 0 0 ) ;
var sw = new Stopwatch ( ) ;
_ running = true ;
while ( _ running ) {
var ticks = TicksElapsed ;
sw . Restart ( ) ;
if ( ticks > _ nextGameUpdate | | GameUpdateTicks = = 0 ) {
_ nextGameUpdate = ticks + GameUpdateTicks ;
BaseUpdate ( ) ;
if ( _ nextGameUpdate < = 0 | | _ gameUpdateUncapped ) {
BaseUpdate ( _ updateDelta ) ;
_ updateDelta = 0 ;
_ nextGameUpdate + = _ gameUpdateMs ;
}
if ( ticks > _ nextVideoUpdate | | VideoUpdateTicks = = 0 ) {
_ nextVideoUpdate = ticks + VideoUpdateTicks ;
if ( _ nextVideoUpdate < = 0 | | _ videoUpdateUncapped ) {
BaseDraw ( ) ;
_ nextVideoUpdate + = _ videoUpdateMs ;
}
if ( VideoUpdateTicks < = 0 & & GameUpdateTicks < = 0 ) continue ; // Cook the CPU!
var cycleElapsed = ( float ) sw . Elapsed . TotalMilliseconds ;
MillisecondsElapsed + = cycleElapsed ;
_ nextGameUpdate - = cycleElapsed ;
_ nextVideoUpdate - = cycleElapsed ;
_ updateDelta + = cycleElapsed ;
if ( ! _ videoUpdateUncapped & & ! _ gameUpdateUncapped ) {
var waitMs = _ nextGameUpdate > _ nextVideoUpdate ? _ nextVideoUpdate : _ nextGameUpdate ;
if ( waitMs > 0 )
Platform . Nanosleep ( MsToNs ( waitMs ) ) ;
var updateTicks = ( long ) ( _ nextGameUpdate > _ nextVideoUpdate ? _ nextVideoUpdate : _ nextGameUpdate ) - TicksElapsed ;
if ( updateTicks > 0 )
Timer . Delay ( ( uint ) updateTicks ) ;
_ updateDelta + = waitMs ;
_ nextGameUpdate - = waitMs ;
_ nextVideoUpdate - = waitMs ;
}
}
}
@ -469,7 +529,7 @@ namespace DotSDL.Graphics {
/// <summary>
/// Called every time the application logic update runs.
/// </summary>
protected virtual void OnUpdate ( ) { }
protected virtual void OnUpdate ( float delta ) { }
/// <summary>
/// Removes a layer from the layer stack.
@ -486,26 +546,22 @@ namespace DotSDL.Graphics {
/// <summary>
/// Displays the window and begins executing code that's associated with it.
/// </summary>
public void Start ( ) {
Start ( 0 , 0 ) ;
}
public void Start ( ) = > Start ( 0 , 0 ) ;
/// <summary>
/// Displays the window and begins executing code that's associated with it.
/// </summary>
/// <param name="updateRate">The desired number of milliseconds between frames and game logic updates. 0 causes the display and game to be continuously updated.</param>
public void Start ( uint updateRate ) {
Start ( updateRate , updateRate ) ;
}
/// <param name="updateRate">The desired number of video and game logic updates per second. 0 causes the display and game to be updated as quickly as possible.</param>
public void Start ( float updateRate ) = > Start ( updateRate , updateRate ) ;
/// <summary>
/// Displays the window and begins executing code that's associated with it.
/// </summary>
/// <param name="drawRate">The desired number of milliseconds between draw calls. 0 causes the display to be continuously updated.</param>
/// <param name="updateRate">The desired number of milliseconds between game logic updates. 0 causes the game to be continuously updated.</param>
public void Start ( uint drawRate , uin t updateRate ) {
VideoUpdateTicks = drawRate ;
GameUpdateTicks = updateRate ;
/// <param name="drawRate">The desired number of draw calls per second . 0 causes the display to be updated as quickly as possible .</param>
/// <param name="updateRate">The desired number of game logic updates per second . 0 causes the game to be updated as quickly as possible .</param>
public void Start ( float drawRate , floa t updateRate ) {
VideoUpdateRate = drawRate ;
GameUpdateRate = updateRate ;
BaseLoad ( ) ;
Video . ShowWindow ( _ window ) ;