Greetings folks, I have couple of questions about implementing tile-based games with Silverlight. In order to better illustrate the problems, implementation details are in order.
Consider the case where a tile-based game, Super Mario, is to be done in Silverlight.
As shown above, the visible region is represented in colors, where as the region outside of the view area is in grey-scale.
The following image outlines the different components that make up the rendering system:
In the above image, the View Canvas (in red) defines the visible region of the game world. It clips away region lying outside of it through its clipping properties. Content Canvas
(in blue) is a child UIElement of the View Canvas, and naturally it is also subjected to the clipping of View Canvas.
Square bricks in the game scene are made up of individual Image object, some sharing the same BitmapSource so they look the same. These bricks are child UIElement of Content Canvas.
These child UIElements are stationary positioned with respect to the Content Canvas, so they appear to scroll along when the Content Canvas scrolls to the left edge of the screen (with respect to the View Canvas).
The following image illustrates all the tiles (child Image objects of Content Canvas) at a given time.
In the illustration above, red tiles have scrolled outside of the visible region, thus becoming “retired tiles”. Green tiles are the ones current visible on screen, and blue color
tiles are ones that will soon be visible as the game progresses.
At one point in time Content Canvas would have scrolled to the left-most and its right edge coincides with the right edge of the View Canvas. In this case, the Content Canvas can
no longer scroll further to left. It needs to remove retired tiles (red), introduce future tiles (blue), and reposition itself to the right side again, as shown below:
The image above shows that Content Canvas repositions itself (with respect to the View Canvas) to the right-most where its left edge coincides with the left edge of View Canvas.
The retired red tiles are being recycled and used as “future tiles”, which are then reinserted as child UIElement of Content Canvas (no recreation, no garbage collection). One thing to note is that as this jump happens, all visible tiles need to be repositioned
as their parent Content Canvas jumps, this is to ensure they remain where they are physically on the user screen:
And so, that much about what goes behind the implementation, and here comes the questions:
Offset of tiles as the jump happens takes non-zero time, the more visible tiles there are, the more tiles to iterate through and the slower it becomes. The frame where this jump
happens usually takes longer time, which results in transition around that not being smooth. Q: Instead of having to loop through all visible Image objects and set their offset, is there a way to do this through a “global transform” kind of thing (i.e. having
all visible tiles referring to the same global transformation matrix)?
Q: Should I be setting Canvas.LeftProperty, Canvas.TopProperty of each Image object, or should I use Image.RenderTransform property? Which one is faster and lighter to Silverlight
runtime?
If I create Content Canvas as wide as the entire stage of the game, I can avoid this setting of Image offset. I will only have to recycle the retired tiles and reuse them for future
tiles. Since the Content Canvas is as wide as the entire game stage, no jump will ever happen. Q: What kind of resource is required, if I have to create a canvas that is 20000x320 as compared to 480x320? Does this only mean its dimension differs or does that
mean some huge memory segment needs to be allocated for 20000x320 canvas?
Thanks a bunch for kind souls who managed to read through this and willing to help clarify things up. Sorry for the lengthy post. [:D]
Offset of tiles as the jump happens takes non-zero time, the more visible tiles there are, the more tiles to iterate through and the slower it becomes.
The frame where this jump happens usually takes longer time, which results in transition around that not being smooth. Q: Instead of having to loop through all visible Image objects and set their offset, is there a way to do this through a “global transform”
kind of thing (i.e. having all visible tiles referring to the same global transformation matrix)?
I don't think there is a global transform that you can use. I would suggest trying out the big canvas (20000x320) approach, and set the transform of the canvas accordingly. I am under the impression that
memory consumption of a canvas is not dictated by the size of the canvas. This is not true for all controls, for example the image control, whose memory consumption is proportional to its size.
Q: Should I be setting Canvas.LeftProperty, Canvas.TopProperty of each Image object, or should I use Image.RenderTransform property? Which one is faster
and lighter to Silverlight runtime?
Should be the same.
If I create Content Canvas as wide as the entire stage of the game, I can avoid this setting of Image offset. I will only have to recycle the retired tiles
and reuse them for future tiles. Since the Content Canvas is as wide as the entire game stage, no jump will ever happen. Q: What kind of resource is required, if I have to create a canvas that is 20000x320 as compared to 480x320? Does this only mean its dimension
differs or does that mean some huge memory segment needs to be allocated for 20000x320 canvas?
I believe there is no memory or performance penalty.
On the other hand, you may want to consider using WriteableBitmap for background drawing. The advantage is that, if you have large number of background objects, say one tile per cell, it is far more efficient to render them into WriteableBitmap (which is
then used as an Image source for the background). Make sure you don't re-render them to the WriteableBitmap on a per-frame basis though, since WriteableBitmap is slow compared to native SL rendering of controls. But if you use it as a background -- say make
it 3X the size of the screen and re-render it whenever it is about to scroll out of scope, which is much more infrequent than the frame rate -- it should be fast. I think you can even arrange it to be redrawn (slightly ahead of getting out of scope) during
"less busy" times, and this may further avoid jittery.
Visit http://www.tagxedo.com, a Silverlight-based word cloud generator. If you like it, please help me spread the word!
God i hate these forums, they dont have quote buttons either... who has a forum without paging and quote buttons in this day and age?
A few things based on what you have said so far:
You mentioned that you had them all sharing the same bitmapsource, but unfortunatly behind the scenes i dont think silverlight shares anything, it will just keep copying the data, so you wont gain any speed there (but i could be wrong).
The Canvas.Top/Left vs RenderTransform argument... this is an interesting one and had the exact same problem when i started, and unfortunatly you need to use both... On its own canvas.top/left will position you anywhere you want *On Screen*, however as you
will be scrolling you will need to set the render transform to be the offset *Off Screen*. I think i kinda lost my mind at one point and broke it all for the sake of it...
The question about having one rendertransform or multiple... im currently using one for each object on screen but they are shared, (So i have a virtual camera class that contains the current translation and its always updated in there, note that i mean its
always updated and not overridden, that way the rest of them dont keep creating new translationtransforms every movement)... but now that you mention it i may be going the completely wrong way and rather than getting each smaller thing to translate get the
big thing to translate...
Dont really know about the canvas size question, i only have a small canvas but have a quadtree style structure to define what is on the screen. Rather than looping through all element each movement though i have a rect that is a movement buffer, so rather
than saying what is *In View* it will check whats in view and a little bit more, so when i start to scroll im not going to need to update my viewable tiles instantly, so lets say my tiles are 32x32 i would give it a 64x64 boost to the viewing bounds rect so
i would pull in some additional tiles that are off screen. This causes a little overhead but gives MASSIVE performance benefits because you only need to update what is on screen after you have moved > 32 pixels. This also is combined with an overridden quadtree
object which contains some caching, so rather than going through each list and turning them on and off, it remembers what was previously viewable and pushes them onto a list, then finds what else is viewable then works out what to turn off and on, so it wont
turn off anything that is already on then turn it back on, it just does what it needs to.
Im currently moving house atm but hopefully later i will be able to revisit your question with a bit more info!
First of all, thanks for taking time to answer my questions, guys. [:D]
I have omitted some history on this implementation actually. Instead of having individual tiles, I used to have one WriteableBitmap in the Content Canvas. This WriteableBitmap is as big as the Content Canvas and it represents static background data (i.e.
those bricks on screen). In DirectX term this is called "primary WriteableBitmap", and there is another "secondary WriteableBitmap" which is not shown on screen.
When the "jump" needs to happen, the unchanged portion of the "primary WriteableBitmap" is blitted to the "secondary WriteableBitmap" through "WriteableBitmap.Render" method. Delta tiles are then rendered on the "secondary
WriteableBitmap", followed by a "flip" which displays the "secondary WriteableBitmap" in the Content Canvas. Delta tiles are only a fraction of the entire screen, so blitting them (again, WriteableBitmap.Render) does not take much time, but I found the bottleneck
is actually the part where "primary WriteableBitmap" is blitted onto the "secondary WriteableBitmap" (even in
Release build with GPU acceleration turned on and
bitmap cached them).
If you imagine a smooth scrolling of the Super Mario screen, there will be occasional "pause" happening, for my case, once every second. Though not exactly the same as Super Mario game, my game fixes the player in the middle of the screen, and it is very
common for user to say "hey, please walk from (0, 0) to (80, 65)" with a click of the mouse button. Naturally, there will be multiple "jumps" that need to happen, which results in jittery scrolling.
I tried using a BackgroundWorker, but as you may already know, it does not allow access to tile "Image" objects which are required for composing the "secondary WriteableBitmap" in the worker thread. I’d really appreciate if anyone can illustrate
how to do "WriteableBitmap.Render" in a non-UI thread.
Then I switched to use independent Image objects on-screen. Currently for the extreme case of my game, I will only have around 500 tiles on screen. It improves the jittery situation significantly, but still the jitter can be felt slightly. So if I am able
to eliminate the offsetting of each Image object (by using a humungous Content Canvas), then the problem is likely to be resolved completely. Let me report this back soon as I figure it out (thinking of BitmapCache and the humungous Content Canvas sends a
chill down my spine…).
Also, I believe it is right to say single BitmapSource is duplicated for each Image object; that may explain why I saw bigger numbers when I turned on "EnableFrameRateCounter", if there are more Image objects (by the way, can you explain what are the five
numbers shown when "EnableFrameRateCounter" is turned on? I know the first one is frame rate).
I welcome more suggestions and possible ways to eliminate jitters entirely.
Thanks,
Ben.
(Marked both these as answers, both helped in some ways.)
haha i didnt know there was a built in frame counter, i had my own timer going to track it... i found that using WritableBitmap was brilliant and logical and just like making a game on any other platform until i tried running it and it was slower than a
486 running crysis...
I found the only way to get *reasonable* performance (30fps) is to do the trick you mentioned earlier with 2 layers, one being the viewing canvas, and one being the moving canvas. It still juddered hits about 30fps every time you move, but thats alot better
than 10fps it was hitting before... Ive turned GPU acceleration and caching on and noticed no benefit.
In my current game my player is usually at center of the screen unless they are near an edge, ive basically made my own simple translation system, so everything has a game position, then the virtual camera is bound to the players position and everything
else is drawn with the offset of the camera. That way i dont have to worry about maintaining local and render transform things all the time (well i do but its abstracted) i just make sure everything has a game position, and then when the camera gets to close
to an edge it becomes unbound from the player letting them walk to the edges of the screen... still doesnt work 100% way i want it to but its better than nothing...
My net will be off for a few days while i get it setup at the new place but it would be great to see how you get on as you sound like you are doing same stuff as me, if you can post any reports on FPS you are getting with movement and tiles that would be
great!
I am really surprised that the once-in-a-while rendering of primary to secondary WriteableBitmap is causing a lag... A few ideas:
(1) make sure you don't turn on the Bitmap Cache on the WriteableBitmap. I don't think it'll help and in fact may force SL to do more work (keep a separate cached copy of something that takes zero effect to build).
(2) perhaps you can use multiple WriteableBitmaps, like 48x320. So, you avoid the problem of having the blit the entire primary over to the secondary.
(3) i was also wondering about this in the previous reply... can you look ahead and blit to the secondary when it is not as busy? Not sure how to do this though (like an OnIdle)...
(4) yes it sucks that WriteableBitmap can only be touched in the UI thread. I complained about this in the SL4 wishlist as well.
Visit http://www.tagxedo.com, a Silverlight-based word cloud generator. If you like it, please help me spread the word!
Q: Should I be setting Canvas.LeftProperty, Canvas.TopProperty of each Image object, or should I use Image.RenderTransform property? Which one is faster and lighter to Silverlight runtime?
I did some tests on this recently and saw about a 10% performance gain using TranslateTransform instead of Canvas.Left and Canvas.Top. This is probably because of the extra overhead of setting attached properties
using SetValue.
I actually did a test too on this (very simple) and I actually see it performing very equal. But I haven't used any tools for performance testing or anything, just looked at the FPS counter.
For my tests, both used a Canvas, since the Grid has some overhead even if you're just using TranslateTransform and you're not using any of its features anyway, and I was using SL3 with GPU acceleration and about 4000 moving elements. I was getting about
40 FPS with Canvas.Left and Canvas.Top, and 45 FPS with TranslateTransform. With less elements the difference was negligible. I'll blog about my sample once SL3 is released.
Ok great, I would like to read that post, as you can see I have asked my self the same question, about using one over they other. A big difference between Canvas and Grid, is the ease of implementation if you ask me. It's much easier to implement the Canvas
approach. When using the Grid you need to set up all these transforms in the load event of something, and it's pretty much code you need to repeat, as you can't just easially inherit stuff like this in Silverlight. :)
--------------------------
Please mark the post as answered if this answers your question
http://www.laumania.net
HaywireGuy
Member
11 Points
18 Posts
Super Mario, Tile Based Game and Silverlight Performance
Jul 02, 2009 06:05 PM | LINK
Greetings folks, I have couple of questions about implementing tile-based games with Silverlight. In order to better illustrate the problems, implementation details are in order. Consider the case where a tile-based game, Super Mario, is to be done in Silverlight.
As shown above, the visible region is represented in colors, where as the region outside of the view area is in grey-scale.
The following image outlines the different components that make up the rendering system:
In the above image, the View Canvas (in red) defines the visible region of the game world. It clips away region lying outside of it through its clipping properties. Content Canvas (in blue) is a child UIElement of the View Canvas, and naturally it is also subjected to the clipping of View Canvas.
Square bricks in the game scene are made up of individual Image object, some sharing the same BitmapSource so they look the same. These bricks are child UIElement of Content Canvas. These child UIElements are stationary positioned with respect to the Content Canvas, so they appear to scroll along when the Content Canvas scrolls to the left edge of the screen (with respect to the View Canvas).
The following image illustrates all the tiles (child Image objects of Content Canvas) at a given time.
In the illustration above, red tiles have scrolled outside of the visible region, thus becoming “retired tiles”. Green tiles are the ones current visible on screen, and blue color tiles are ones that will soon be visible as the game progresses.
At one point in time Content Canvas would have scrolled to the left-most and its right edge coincides with the right edge of the View Canvas. In this case, the Content Canvas can no longer scroll further to left. It needs to remove retired tiles (red), introduce future tiles (blue), and reposition itself to the right side again, as shown below:
The image above shows that Content Canvas repositions itself (with respect to the View Canvas) to the right-most where its left edge coincides with the left edge of View Canvas. The retired red tiles are being recycled and used as “future tiles”, which are then reinserted as child UIElement of Content Canvas (no recreation, no garbage collection). One thing to note is that as this jump happens, all visible tiles need to be repositioned as their parent Content Canvas jumps, this is to ensure they remain where they are physically on the user screen:
And so, that much about what goes behind the implementation, and here comes the questions:
Thanks a bunch for kind souls who managed to read through this and willing to help clarify things up. Sorry for the lengthy post. [:D]
Thanks and regards,
Ben.
Performance Tile-Based Tiles
ksleung
Contributor
6680 Points
1265 Posts
Re: Super Mario, Tile Based Game and Silverlight Performance
Jul 04, 2009 09:10 AM | LINK
I believe there is no memory or performance penalty.
On the other hand, you may want to consider using WriteableBitmap for background drawing. The advantage is that, if you have large number of background objects, say one tile per cell, it is far more efficient to render them into WriteableBitmap (which is then used as an Image source for the background). Make sure you don't re-render them to the WriteableBitmap on a per-frame basis though, since WriteableBitmap is slow compared to native SL rendering of controls. But if you use it as a background -- say make it 3X the size of the screen and re-render it whenever it is about to scroll out of scope, which is much more infrequent than the frame rate -- it should be fast. I think you can even arrange it to be redrawn (slightly ahead of getting out of scope) during "less busy" times, and this may further avoid jittery.
Grofit
Member
298 Points
342 Posts
Re: Super Mario, Tile Based Game and Silverlight Performance
Jul 04, 2009 09:26 AM | LINK
God i hate these forums, they dont have quote buttons either... who has a forum without paging and quote buttons in this day and age?
A few things based on what you have said so far:
You mentioned that you had them all sharing the same bitmapsource, but unfortunatly behind the scenes i dont think silverlight shares anything, it will just keep copying the data, so you wont gain any speed there (but i could be wrong).
The Canvas.Top/Left vs RenderTransform argument... this is an interesting one and had the exact same problem when i started, and unfortunatly you need to use both... On its own canvas.top/left will position you anywhere you want *On Screen*, however as you will be scrolling you will need to set the render transform to be the offset *Off Screen*. I think i kinda lost my mind at one point and broke it all for the sake of it...
The question about having one rendertransform or multiple... im currently using one for each object on screen but they are shared, (So i have a virtual camera class that contains the current translation and its always updated in there, note that i mean its always updated and not overridden, that way the rest of them dont keep creating new translationtransforms every movement)... but now that you mention it i may be going the completely wrong way and rather than getting each smaller thing to translate get the big thing to translate...
Dont really know about the canvas size question, i only have a small canvas but have a quadtree style structure to define what is on the screen. Rather than looping through all element each movement though i have a rect that is a movement buffer, so rather than saying what is *In View* it will check whats in view and a little bit more, so when i start to scroll im not going to need to update my viewable tiles instantly, so lets say my tiles are 32x32 i would give it a 64x64 boost to the viewing bounds rect so i would pull in some additional tiles that are off screen. This causes a little overhead but gives MASSIVE performance benefits because you only need to update what is on screen after you have moved > 32 pixels. This also is combined with an overridden quadtree object which contains some caching, so rather than going through each list and turning them on and off, it remembers what was previously viewable and pushes them onto a list, then finds what else is viewable then works out what to turn off and on, so it wont turn off anything that is already on then turn it back on, it just does what it needs to.
Im currently moving house atm but hopefully later i will be able to revisit your question with a bit more info!
HaywireGuy
Member
11 Points
18 Posts
Re: Super Mario, Tile Based Game and Silverlight Performance
Jul 04, 2009 10:51 AM | LINK
First of all, thanks for taking time to answer my questions, guys. [:D]
I have omitted some history on this implementation actually. Instead of having individual tiles, I used to have one WriteableBitmap in the Content Canvas. This WriteableBitmap is as big as the Content Canvas and it represents static background data (i.e. those bricks on screen). In DirectX term this is called "primary WriteableBitmap", and there is another "secondary WriteableBitmap" which is not shown on screen.
When the "jump" needs to happen, the unchanged portion of the "primary WriteableBitmap" is blitted to the "secondary WriteableBitmap" through "WriteableBitmap.Render" method. Delta tiles are then rendered on the "secondary WriteableBitmap", followed by a "flip" which displays the "secondary WriteableBitmap" in the Content Canvas. Delta tiles are only a fraction of the entire screen, so blitting them (again, WriteableBitmap.Render) does not take much time, but I found the bottleneck is actually the part where "primary WriteableBitmap" is blitted onto the "secondary WriteableBitmap" (even in Release build with GPU acceleration turned on and bitmap cached them).
If you imagine a smooth scrolling of the Super Mario screen, there will be occasional "pause" happening, for my case, once every second. Though not exactly the same as Super Mario game, my game fixes the player in the middle of the screen, and it is very common for user to say "hey, please walk from (0, 0) to (80, 65)" with a click of the mouse button. Naturally, there will be multiple "jumps" that need to happen, which results in jittery scrolling.
I tried using a BackgroundWorker, but as you may already know, it does not allow access to tile "Image" objects which are required for composing the "secondary WriteableBitmap" in the worker thread. I’d really appreciate if anyone can illustrate how to do "WriteableBitmap.Render" in a non-UI thread.
Then I switched to use independent Image objects on-screen. Currently for the extreme case of my game, I will only have around 500 tiles on screen. It improves the jittery situation significantly, but still the jitter can be felt slightly. So if I am able to eliminate the offsetting of each Image object (by using a humungous Content Canvas), then the problem is likely to be resolved completely. Let me report this back soon as I figure it out (thinking of BitmapCache and the humungous Content Canvas sends a chill down my spine…).
Also, I believe it is right to say single BitmapSource is duplicated for each Image object; that may explain why I saw bigger numbers when I turned on "EnableFrameRateCounter", if there are more Image objects (by the way, can you explain what are the five numbers shown when "EnableFrameRateCounter" is turned on? I know the first one is frame rate).
I welcome more suggestions and possible ways to eliminate jitters entirely.
Thanks,
Ben.
(Marked both these as answers, both helped in some ways.)
Grofit
Member
298 Points
342 Posts
Re: Re: Super Mario, Tile Based Game and Silverlight Performance
Jul 04, 2009 05:40 PM | LINK
haha i didnt know there was a built in frame counter, i had my own timer going to track it... i found that using WritableBitmap was brilliant and logical and just like making a game on any other platform until i tried running it and it was slower than a 486 running crysis...
I found the only way to get *reasonable* performance (30fps) is to do the trick you mentioned earlier with 2 layers, one being the viewing canvas, and one being the moving canvas. It still juddered hits about 30fps every time you move, but thats alot better than 10fps it was hitting before... Ive turned GPU acceleration and caching on and noticed no benefit.
In my current game my player is usually at center of the screen unless they are near an edge, ive basically made my own simple translation system, so everything has a game position, then the virtual camera is bound to the players position and everything else is drawn with the offset of the camera. That way i dont have to worry about maintaining local and render transform things all the time (well i do but its abstracted) i just make sure everything has a game position, and then when the camera gets to close to an edge it becomes unbound from the player letting them walk to the edges of the screen... still doesnt work 100% way i want it to but its better than nothing...
My net will be off for a few days while i get it setup at the new place but it would be great to see how you get on as you sound like you are doing same stuff as me, if you can post any reports on FPS you are getting with movement and tiles that would be great!
ksleung
Contributor
6680 Points
1265 Posts
Re: Super Mario, Tile Based Game and Silverlight Performance
Jul 04, 2009 05:50 PM | LINK
I am really surprised that the once-in-a-while rendering of primary to secondary WriteableBitmap is causing a lag... A few ideas:
(1) make sure you don't turn on the Bitmap Cache on the WriteableBitmap. I don't think it'll help and in fact may force SL to do more work (keep a separate cached copy of something that takes zero effect to build).
(2) perhaps you can use multiple WriteableBitmaps, like 48x320. So, you avoid the problem of having the blit the entire primary over to the secondary.
(3) i was also wondering about this in the previous reply... can you look ahead and blit to the secondary when it is not as busy? Not sure how to do this though (like an OnIdle)...
(4) yes it sucks that WriteableBitmap can only be touched in the UI thread. I complained about this in the SL4 wishlist as well.
Bill Reiss
Contributor
4973 Points
947 Posts
Re: Super Mario, Tile Based Game and Silverlight Performance
Jul 05, 2009 02:59 PM | LINK
Bill Reiss, Coauthor of Hello! Silverlight
My blog
Qbus
Member
611 Points
271 Posts
Re: Re: Super Mario, Tile Based Game and Silverlight Performance
Jul 06, 2009 10:52 AM | LINK
I actually did a test too on this (very simple) and I actually see it performing very equal. But I haven't used any tools for performance testing or anything, just looked at the FPS counter.
http://laumania.net/post/Using-Grid-or-Canvas-as-sprite-container.aspx
Please mark the post as answered if this answers your question
http://www.laumania.net
Bill Reiss
Contributor
4973 Points
947 Posts
Re: Re: Super Mario, Tile Based Game and Silverlight Performance
Jul 06, 2009 12:33 PM | LINK
For my tests, both used a Canvas, since the Grid has some overhead even if you're just using TranslateTransform and you're not using any of its features anyway, and I was using SL3 with GPU acceleration and about 4000 moving elements. I was getting about 40 FPS with Canvas.Left and Canvas.Top, and 45 FPS with TranslateTransform. With less elements the difference was negligible. I'll blog about my sample once SL3 is released.
Bill Reiss, Coauthor of Hello! Silverlight
My blog
Qbus
Member
611 Points
271 Posts
Re: Re: Re: Super Mario, Tile Based Game and Silverlight Performance
Jul 06, 2009 01:59 PM | LINK
Please mark the post as answered if this answers your question
http://www.laumania.net