@@ -43,6 +43,8 @@ private sealed class DemoApp
4343 {
4444 private const int BallCount = 1000 ;
4545 private static readonly TimeSpan FpsUpdateInterval = TimeSpan . FromSeconds ( 1 ) ;
46+ private static readonly Brush BackgroundBrush = Brushes . Solid ( Color . FromPixel ( new Bgra32 ( 30 , 30 , 40 , 255 ) ) ) ;
47+ private static readonly Brush TextBrush = Brushes . Solid ( Color . FromPixel ( new Bgra32 ( 70 , 70 , 100 , 255 ) ) ) ;
4648
4749 private readonly WebGPUWindow < Bgra32 > window ;
4850 private readonly Random rng = new ( 42 ) ;
@@ -151,13 +153,12 @@ private void OnRender(WebGPUSurfaceFrame<Bgra32> frame)
151153 {
152154 DrawingCanvas canvas = frame . Canvas ;
153155 Rectangle bounds = canvas . Bounds ;
154- canvas . Fill ( Brushes . Solid ( Color . FromPixel ( new Bgra32 ( 30 , 30 , 40 , 255 ) ) ) ) ;
156+ canvas . Fill ( BackgroundBrush ) ;
155157
156158 for ( int i = 0 ; i < this . balls . Length ; i ++ )
157159 {
158160 ref Ball ball = ref this . balls [ i ] ;
159- EllipsePolygon ellipse = new ( ball . X , ball . Y , ball . Radius ) ;
160- canvas . Fill ( Brushes . Solid ( ball . Color ) , ellipse ) ;
161+ ball . Draw ( canvas ) ;
161162 }
162163
163164 this . DrawScrollingText ( canvas , bounds . Width , bounds . Height ) ;
@@ -193,7 +194,6 @@ private void DrawScrollingText(DrawingCanvas canvas, int width, int height)
193194
194195 Matrix3x2 translation = Matrix3x2 . CreateTranslation ( 0 , y ) ;
195196 RectangleF viewport = new ( 0 , 0 , width , height ) ;
196- Brush textBrush = Brushes . Solid ( Color . FromPixel ( new Bgra32 ( 70 , 70 , 100 , 255 ) ) ) ;
197197 DrawingOptions translatedOptions = new ( )
198198 {
199199 Transform = new Matrix4x4 ( translation ) ,
@@ -215,7 +215,7 @@ private void DrawScrollingText(DrawingCanvas canvas, int width, int height)
215215 continue ;
216216 }
217217
218- canvas . Fill ( textBrush , path ) ;
218+ canvas . Fill ( TextBrush , path ) ;
219219 }
220220
221221 canvas . Restore ( ) ;
@@ -232,7 +232,27 @@ private struct Ball
232232 public float VelocityX ;
233233 public float VelocityY ;
234234 public float Radius ;
235- public Color Color ;
235+ private readonly Brush brush ;
236+ private readonly EllipsePolygon shape ;
237+ private readonly DrawingOptions drawingOptions ;
238+
239+ private Ball (
240+ float x ,
241+ float y ,
242+ float velocityX ,
243+ float velocityY ,
244+ float radius ,
245+ Brush brush )
246+ {
247+ this . X = x ;
248+ this . Y = y ;
249+ this . VelocityX = velocityX ;
250+ this . VelocityY = velocityY ;
251+ this . Radius = radius ;
252+ this . brush = brush ;
253+ this . shape = new EllipsePolygon ( 0 , 0 , radius ) ;
254+ this . drawingOptions = new DrawingOptions ( ) ;
255+ }
236256
237257 /// <summary>
238258 /// Creates a random ball that fits within the current framebuffer bounds.
@@ -244,19 +264,36 @@ private struct Ball
244264 public static Ball CreateRandom ( Random rng , int width , int height )
245265 {
246266 float radius = 20F + ( rng . NextSingle ( ) * 40F ) ;
247- return new Ball
248- {
249- X = radius + ( rng . NextSingle ( ) * ( width - ( 2 * radius ) ) ) ,
250- Y = radius + ( rng . NextSingle ( ) * ( height - ( 2 * radius ) ) ) ,
251- VelocityX = ( 100F + ( rng . NextSingle ( ) * 200F ) ) * ( rng . Next ( 2 ) == 0 ? - 1 : 1 ) ,
252- VelocityY = ( 100F + ( rng . NextSingle ( ) * 200F ) ) * ( rng . Next ( 2 ) == 0 ? - 1 : 1 ) ,
253- Radius = radius ,
254- Color = Color . FromPixel ( new Bgra32 (
255- ( byte ) ( 80 + rng . Next ( 176 ) ) ,
256- ( byte ) ( 80 + rng . Next ( 176 ) ) ,
257- ( byte ) ( 80 + rng . Next ( 176 ) ) ,
258- 200 ) ) ,
259- } ;
267+ Color color = Color . FromPixel ( new Bgra32 (
268+ ( byte ) ( 80 + rng . Next ( 176 ) ) ,
269+ ( byte ) ( 80 + rng . Next ( 176 ) ) ,
270+ ( byte ) ( 80 + rng . Next ( 176 ) ) ,
271+ 200 ) ) ;
272+
273+ return new Ball (
274+ radius + ( rng . NextSingle ( ) * ( width - ( 2 * radius ) ) ) ,
275+ radius + ( rng . NextSingle ( ) * ( height - ( 2 * radius ) ) ) ,
276+ ( 100F + ( rng . NextSingle ( ) * 200F ) ) * ( rng . Next ( 2 ) == 0 ? - 1 : 1 ) ,
277+ ( 100F + ( rng . NextSingle ( ) * 200F ) ) * ( rng . Next ( 2 ) == 0 ? - 1 : 1 ) ,
278+ radius ,
279+ Brushes . Solid ( color ) ) ;
280+ }
281+
282+ /// <summary>
283+ /// Draws the retained ball shape at its current animated location.
284+ /// </summary>
285+ /// <remarks>
286+ /// The ellipse is centered at the origin and moved through per-ball drawing options so the path geometry
287+ /// remains cached across frames without allocating a new shape each render.
288+ /// </remarks>
289+ /// <param name="canvas">The destination canvas for the current frame.</param>
290+ public void Draw ( DrawingCanvas canvas )
291+ {
292+ this . drawingOptions . Transform = Matrix4x4 . CreateTranslation ( this . X , this . Y , 0 ) ;
293+
294+ canvas . Save ( this . drawingOptions ) ;
295+ canvas . Fill ( this . brush , this . shape ) ;
296+ canvas . Restore ( ) ;
260297 }
261298
262299 /// <summary>
0 commit comments