Someone Said it Was Impossible: Hue Shift in Pixel Shader 2.0 (EasyPainter, Silverlight)

Feb 9, 2010

I read somewhere online that Hue changes can't be done in pixel shader 2.0, due to limitation of 64 instructions per slot.

Here's the sample that proves otherwise:

Download source code

Indeed the RGB-to-HSL-to-RGB conversion takes about 100 instructions in its typical implementation. PS 2.0 which is the shader model supported by Silverlight 3 only allows for 64 arithmetic instructions, as outlined in this comparison between pixel shaders on Wikipedia

How can we optimize it?

Optimizing pixel shader instruction slots is nice - in a typical C# world, you'd be adding if() statements to make your code run faster like this:

    if ( HSV.y != 0 )
       QUAD_REAL var_h = HSV.x * 6;
       QUAD_REAL var_i = floor(var_h);   // Or ... var_i = floor( var_h )
       QUAD_REAL var_1 = HSV.z * (1.0 - HSV.y);
       QUAD_REAL var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));
       QUAD_REAL var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));
       if      (var_i == 0) { RGB = QUAD_REAL3(HSV.z, var_3, var_1); }
       else if (var_i == 1) { RGB = QUAD_REAL3(var_2, HSV.z, var_1); }
       else if (var_i == 2) { RGB = QUAD_REAL3(var_1, HSV.z, var_3); }
       else if (var_i == 3) { RGB = QUAD_REAL3(var_1, var_2, HSV.z); }
       else if (var_i == 4) { RGB = QUAD_REAL3(var_3, var_1, HSV.z); }
       else                 { RGB = QUAD_REAL3(HSV.z, var_1, var_2); }
   }

Not with pixel shaders. If you look carefully at the bold if statement, removing it does not change the program logic. It just takes an extra instruction slot. In reality, I think the pixel shader code will run with the same speed with or without the if() (not 100% sure so correct me if needed).

With this knowledge, I decided to do these optimizations:

1. Instead of HSL-to-RGB, use HSV-to-RGB. The reference NVidia Shader Library implementation (source code here) of HSV-RGB-HSV takes ~70 or so slots.

2. Combine the min_channel() and max_channel() functions into 1 - saves a couple if() statements

3. Take out the if (x < 0) (x += 1) checks in the RGB-HSV function, and execute them once instead of twice, after the hue is modified.

4. Remove the "obsolete" if()-s like the one above

I was very happy to see that it just fit in the 64-instruction slot of PS 2.0! Note that it hits the limit and more complex Hue stuff may need further optimizations! :) If you do so, please let me know! Anyway hue tricks that don't use more slots are OK.

Here's the complete Shazzam-friendly source of the .fx file (also included in the sample project source above).

/// <summary>Hue shift</summary>
/// <minValue>0</minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0</defaultValue>
float HueShift : register(c0);
sampler2D Samp : register(S0);
#define QUAD_REAL float
#define QUAD_REAL3 float3
QUAD_REAL3 rgb_to_hsv_no_clip(QUAD_REAL3 RGB)
{
    QUAD_REAL3 HSV;
   
 float minChannel, maxChannel;
 if (RGB.x > RGB.y) {
  maxChannel = RGB.x;
  minChannel = RGB.y;
 }
 else {
  maxChannel = RGB.y;
  minChannel = RGB.x;
 }
 
 if (RGB.z > maxChannel) maxChannel = RGB.z;
 if (RGB.z < minChannel) minChannel = RGB.z;
   
    HSV.xy = 0;
    HSV.z = maxChannel;
    QUAD_REAL delta = maxChannel - minChannel;             //Delta RGB value
    if (delta != 0) {                    // If gray, leave H & S at zero
       HSV.y = delta / HSV.z;
       QUAD_REAL3 delRGB;
       delRGB = (HSV.zzz - RGB + 3*delta) / (6.0*delta);
       if      ( RGB.x == HSV.z ) HSV.x = delRGB.z - delRGB.y;
       else if ( RGB.y == HSV.z ) HSV.x = ( 1.0/3.0) + delRGB.x - delRGB.z;
       else if ( RGB.z == HSV.z ) HSV.x = ( 2.0/3.0) + delRGB.y - delRGB.x;
    }
    return (HSV);
}
QUAD_REAL3 hsv_to_rgb(QUAD_REAL3 HSV)
{
    QUAD_REAL3 RGB = HSV.z;
    //if ( HSV.y != 0 ) { // we don't really need this since it just adds an obsolete instruction slot
       QUAD_REAL var_h = HSV.x * 6;
       QUAD_REAL var_i = floor(var_h);   // Or ... var_i = floor( var_h )
       QUAD_REAL var_1 = HSV.z * (1.0 - HSV.y);
       QUAD_REAL var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));
       QUAD_REAL var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));
       if      (var_i == 0) { RGB = QUAD_REAL3(HSV.z, var_3, var_1); }
       else if (var_i == 1) { RGB = QUAD_REAL3(var_2, HSV.z, var_1); }
       else if (var_i == 2) { RGB = QUAD_REAL3(var_1, HSV.z, var_3); }
       else if (var_i == 3) { RGB = QUAD_REAL3(var_1, var_2, HSV.z); }
       else if (var_i == 4) { RGB = QUAD_REAL3(var_3, var_1, HSV.z); }
       else                 { RGB = QUAD_REAL3(HSV.z, var_1, var_2); }
   //}
   return (RGB);
}
float4 main(float2 uv : TEXCOORD) : COLOR
{
 float4 col = tex2D(Samp, uv);
 float3 hsv = rgb_to_hsv_no_clip(col.xyz);
    hsv.x+=HueShift;
    //if ( hsv.x < 0.0 ) { hsv.x += 1.0; }
    if ( hsv.x > 1.0 ) { hsv.x -= 1.0; }
    return float4(hsv_to_rgb(hsv),col.w);
}
 
btw, Visual Studio 2010 RC is out for MSDN subsribers (public tomorrow) and I'm going to publish all samples in VS 2010 from now on :)

Hope you like it!

      

EasyPainter Source Pack1: Color Picker, RangeSlider, Small Expander, Fast Drop Shadow and more!

Feb 8, 2010

I'm very happy to publish the EasyPainter source code pack. The controls in the pack are optimized for usability, speed and size.

All of them have been succesfully used in real-life apps which is the best way to test if something works.

Download Source Code

Here's a live demo:

This pack contains the following controls:

  • Color Picker - feature complete control that allows to pick any color, transparency or type in hex color
  • Editable, auto-formatting slider - if you want to give the user the ability to quickly choose values with previews, this control is for you. It will also try and self-adapt the number format (spaces after the zero), based on the scale you specify.
  • Fast drop shadow frame - consists of few rectangles, without customizations. But you can have a lot of those, in many sizes and not worry about performance.
  • Minimalistic Expander - the best thing about this control is that it's small (12KB). You don't need to get the whole 300+KB Microsoft.Windows.Controls from the Silverlight Toolkit.
  • The toolbar buttons used in EasyPainter on the side and top toolbar
  • The toolbar radio buttons used in EasyPainter for tool selection

Hope you like it!

btw, here's a nice trick that I used to do the rectangle background for the alpha picker:

         <Rectangle Fill="White" />
        <Line Stroke="Gray" StrokeThickness="4" StrokeDashArray="1,1" X1="2" Y1="0" X2="2" Y2="300"  />
        <Line Stroke="Gray" StrokeThickness="4" StrokeDashArray="0,1,0" X1="2" Y1="0" X2="2" Y2="300" Grid.Column="1"  />

In the posts coming up, I'll talk about compositing the image on screen, filters, and drawing with pixel shaders! There's also adorner, cursor code, and Flickr integration coming up!

 

  

EasyPainter Beta: Top Secret Online Image Editor Announced

Feb 4, 2010

Today I am announcing EasyPainter Beta: The Artist Within. An online image editor that is: Fast. Easy. Made for non-designers. And FREE! :)

Click here to open EasyPainter

Note: send feedback in the comments section of this post.

Unlike other online (or many offline) image editors, EasyPainter offers online effects comparable to powerful desktop apps, integrated search for millions of free-for-commercial or -personal use photos, and instant previews of any changes you make.

Find below:

  • Quick intro to easy painter
  • Many Screenshots
  • Getting Started Tutorials
  • Effect Tutorials
  • Tech Stuff

What Does Easy Painter Offer?

The Basics

  • Reveal the true-to-life colors in photos. Make them look like shot with professional camera. Your friends will be wowed!
    • The life-like photo filter combines several steps of popular GIMP/Photoshop "how to enhance an image" tutorials into one.  Save time with a 1-step effect instead of following an online tutorial.
  • Preview effects and changes instantly. It's OK to change your mind – many times per second. That's why we have computers for :)
  • Find millions of free for commercial use photos for your project from Flickr
    • Also filter by "government use", "non-commercial use", and other
  • Quickly resize, email size. Flip. Rotate. Draw to select objects and crop with a single click.
  • Manipulate huge 25 megapixel panoramas, fast. Powered by bleeding edge Silverlight and Pixel Shader engines.
  • Ease of use: tired of figuring out image programs with "Edge Detect", "Bumpiness factor" and "Directional Blur parameters"?
    • Work with more intuitive language like: "Hollow Outline", "Fast Motion", "Painting"
    • Mix-and-match. Draw on the image while an effect is open at the same time. Switch between effects and see your changes applied instantly

Effects

  • "Paint" with any effect, such as blur, emboss, bloom, saturation, and more
  • Make curves in trees, fluff clouds or perform complex photo manipulations easily by clicking and "drawing" effects with the mouse
    • Preview and change at will. For example, move a slider to bend a straight tree back and forth
  • Make changes that look good every time. Even if you are not a designer. All EasyPainter filters suggest the best values for your image.
  • Compare before and after with a single click on the currently open effect

Screenshots

All images combined are made in less than 25 minutes

The screenshots below are intended to show samples of the functionality. For more tutorials, see the Getting Started and Effects Tutorials sections

Open From Flickr:

.OpenFromFlickr

Painting With Effect:

PaintWithEmboss

Original:

This is the original image being modified in the next screenshots:More...

  

The Most Important Silverlight WriteableBitmap Gotcha - Does It Lose/Change Colors?

Jan 27, 2010

Edit Nov 21, 2011: changed 256/a to 255/a due to Gert’s comment – thanks Gert!

The first time I tried saving WriteableBitmap to png, the transparencies didn't look right. The color was "washed out" and replaced with gray:

 

Moreover: Let's say you wanted to set a half-opaque red pixel color inside a WriteableBitmap.

To my biggest surprise this didn't work:

bitmap.Pixels[i] = (255 << 24) + (255 << 16)

I got a redder pixel than expected! What's going on?

After long search and speaking with people, it turned out it's because WriteableBitmap for Silverlight uses pARGB (pre-multiplied alpha) format.

If you look at MSDN carefully, you'll notice the single place (as far as I know) that mentions it: http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap(VS.95).aspx

It reads: "When assigning colors to pixels in your bitmap, use pre-multiplied colors."

The reason for this initially unintuitive choice of format is speed. Blending with pre-multiplied colors can save significant time as compared to just ARGB.

For more info and blending formulas, read here: http://www.teamten.com/lawrence/graphics/premultiplication/

Here is the source code to call before saving WriteableBitmap to PNG in order to convert the colors to "regular" ARGB:

public static void CompensateForRender(int[] bitmapPixels)
{
    int count = bitmapPixels.Length;

    for (int i = 0; i < count; i++)
    {
        uint pixel = unchecked((uint)bitmapPixels[i]);
        // decompose
        double a = (pixel >> 24) & 255;
        if ((a == 255) || (a == 0)) continue;

        double r = (pixel >> 16) & 255;
        double g = (pixel >> 8) & 255;
        double b = (pixel) & 255;

        double factor = 255 / a;
        uint newR = (uint)Math.Round(r * factor);
        uint newG = (uint)Math.Round(g * factor);
        uint newB = (uint)Math.Round(b * factor);
        // compose
        bitmapPixels[i] = unchecked((int)((pixel & 0xFF000000) | (newR << 16) | (newG << 8) | newB));
    }
}

If you look at the previous post, you'd call CompensateForRender(_screen.Pixels) just before TgaWriter.Write(_screen, fileStream);

Obviously the above function can be optimized, and should if you use it often (e.g. several times per second or as part of batch processing).


You know that speed is a benefit, the drawback is that there is some loss of precision. It's nice that for completely opaque images there is no precision loss and the pARGB format is equivalent to the ARGB format. So in most cases you wouldn't care :)

In the cases where the alpha gets below 255, you have to go all the way to 128 alpha to lose one bit-per-pixel from the color information. Not that bad, given how many CPU cycles can be saved in blending and used for something else.

Hope this helps, please comment - if you had a choice, which format would you choose and why?

  

Quick and Dirty Output of WriteableBitmap as TGA Image

Jan 21, 2010

If you ever needed to save a WriteableBitmap to an image file without too much hassle (e.g. for debugging purposes), you could use this simple source code to save it as TGA and then open with Paint.NET:

From Wikipedia: "TGA files are in widespread use due to their simplicity and ease of implementation and lack of encumbering patents."

public static class TgaWriter {
    public static void Write(WriteableBitmap bitmap, Stream output) {
        int width = bitmap.PixelWidth;
        int height = bitmap.PixelHeight;
        int[] pixels = bitmap.Pixels;
        byte[] pixelsArr = new byte[pixels.Length * 4];

        int offsetSource = 0;
        int width4 = width * 4;
        int width8 = width * 8;
        int offsetDest = (height - 1) * width4;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int value = pixels[offsetSource];
                pixelsArr[offsetDest] = (byte)(value & 255); // b
                pixelsArr[offsetDest + 1] = (byte)((value >> 8) & 255); // g
                pixelsArr[offsetDest + 2] = (byte)((value >> 16) & 255); // r
                pixelsArr[offsetDest + 3] = (byte)(value >> 24); // a

                offsetSource++;
                offsetDest += 4;
            }
            offsetDest -= width8;
        }

        byte[] header = new byte[] {
            0, // ID length
            0, // no color map
            2, // uncompressed, true color
            0, 0, 0, 0,
            0,
            0, 0, 0, 0, // x and y origin
            (byte)(width & 0x00FF),
            (byte)((width & 0xFF00) >> 8),
            (byte)(height & 0x00FF),
            (byte)((height & 0xFF00) >> 8),
            32, // 32 bit bitmap
            0 };

        using (BinaryWriter writer = new BinaryWriter(output)) {
            writer.Write(header);
            writer.Write(pixelsArr);
        }
    }
}

 Here's a sample usage of the above class:

Important Edit: if you want to support transparency, you'd have to call CompensateForRender() from this post: http://nokola.com/blog/post/2010/01/27/The-Most-Important-Silverlight-WriteableBitmap-Gotcha-Does-It-LoseChange-Colors.aspx

private void btnSave_Click(object sender, RoutedEventArgs e) {
    WriteableBitmap _screen;
    // ... do something with the WriteableBitmap here...

    SaveFileDialog dialog = new SaveFileDialog();
    dialog.DefaultExt = ".tga";
    dialog.Filter = "TGA Image (*.tga)|*.tga";
    dialog.FilterIndex = 1;

    if (dialog.ShowDialog() != true) return;
    using (Stream fileStream = dialog.OpenFile()) {
        TgaWriter.Write(_screen, fileStream);
        fileStream.Close();
    }
}

The benefit of the above code is that you don't have to worry about image libraries, external binary dependencies and such - just paste & go :)

The TGA file format: http://en.wikipedia.org/wiki/Truevision_TGA

 

  

Calculating Shader Effects in WriteableBitmap (Fast Float Computations And More)

Jan 11, 2010

Here's a small code snippet that allows you to calculate any pixel shader and get the output into a WriteableBitmap!

Stream stream = Application.GetResourceStream(new Uri("SilverlightApplication1;component/Images/test.jpg", UriKind.Relative)).Stream;
WriteableBitmap result = new WriteableBitmap(1, 1); // avoid creating intermediate BitmapImage
result.SetSource(stream);

Image image = new Image();
image.Effect = new BlurEffect(); // any pixel shader effect
image.Source = result; // set the image to the original
result.Render(image, null);
result.Invalidate(); // render the pixel shader
image.Source = null; // release the bitmap reference
image.Effect = null;
GC.Collect(); // remove all obsolete bitmaps from memory [optional]
// result contains the image with shader effect applied! assign it to any Image.Source!
myImage.Source = result;

For the sample, I used InvertColorEffect() that I had on my PC, but you could try with your own (or just use the BlurEffect() to see how it works).

Note that the image doesn't even have to be part of the UI tree, and you don't have to wait for a render pass!

Why is this good?

In an apples-to-apples comparison (float computations) pixel shaders are much faster than managed code. See http://kodierer.blogspot.com/2009/08/silverlight-3-writeablebitmap.html for performance comparison. This means that you can now run even faster computations from managed code in Silverlight!

Also in case Silverlight eventually executes shaders on the GPU (I don't know what is the plan about that), your applications will really be able to benefit from the fast computations!

There is also one other reason why is it so good: but I'll mention it in a later post (working on secret project now...) :)

 

  

Game Cutscenes Part 1 - The Sequencer (No Code Yet, Just Concept)

Jan 4, 2010

This post is a call for initial feedback, before I go deeper into coding and stuff, just to make sure I'm not missing anything and capture cool ideas...

I've been working on a new game recently, and thought it would be really nice to have some cutscenes in the game.

Here's a sample frame such a scene: 

Obviously, the above frame is not too advanced, but it looks pretty exciting and fun to create, especially when combined with the right soundtrack

Credits:

   Image background: http://shadow-trance.deviantart.com/art/Moonshine-Bay-88935567

   Ninja warriors: http://tinrobo.deviantart.com/art/Little-Samurai-Warriors-80135868 

   Both image sources are by-nc-sa CC license: http://creativecommons.org/licenses/by-nc-sa/3.0/

The Sequencer

What we really need is a way to control all game "events" (such as encounters, text on screen, music, etc). The Sequencer is a central point (e.g. a C# class), that "knows" about high-level game events, such as "Level1 Start", or "Battle 2 Encounter".

The sequencer controls all in-game events, such as text being displayed, cutscenes, music sync (for example, when a cutscene starts, the sequencer will switch the music).

Here's a small high-level diagram of what I think is needed to implement a single-screen cutscene:

 

I'm assuming that Sequencer, Sequence and the Action-s will end up as C# classes that know how to operate on the game world.

In the above diagram, Sequence1 might represent the complete cut-scene, with all dialogue that occurs.

Interface

 

Above is a draft interface (made with Paint, if it's not obvious :))  for creating the cutscenes.

The initial version of the interface will allow for:

  • Choosing music (1 only)
  • Selecting background
  • Adding characters, dialogue, movements, synced to the music
  • Save as XML

That's all for now.

Any ideas that you'd like to share or other feedback? Please comment.

  

Improving Download Time (2-3x) for Full Screen Background Images

Jan 4, 2010

Getting your backgrounds quickly is very important for games, especially at startup time when you're downloading a lot of stuff anyway (like menu buttons, simple effects, such as explosions and other things that didn't make it into the "core" xap to reduce initial load time).

Since you are full screen, it is a good idea to detect the resolution (that would be the ActualWidth and ActualHeight properties of your root control) and programatically return a suitable sized background.

For example, a 1920x1080 JPG image is about 200K while the same image 800x600 is about 50K.

The source code is simple, but I post it in case someone needs it and doesn't have to write it from scratch:

/// <summary>
/// The background resolutions available
/// </summary>
private static int[] _resAvailable = new int[] { 800, 1024, 1280, 1600, 1920 };

/// <summary>
/// Returns the most appropriate resolution from the list of available resolutions for the background images
/// Best resolution is defined as the closest resolution that is bigger than the resolution input as an argument
/// </summary>
/// <param name="currentXResolution"></param>
/// <returns></returns>
private static int GetBestStockResolution(int currentXResolution) {
    int count = _resAvailable.Length;

    for (int i = 0; i < count; i++) {
        if (_resAvailable[i] >= currentXResolution) {
            return _resAvailable[i];
        }
    }

    // no suitable resolution found, return largest
    return _resAvailable[count - 1];
}

/// <summary>
/// Determines the correct url for an image based on teh current resolution and short image name
/// </summary>
/// <param name="imageShortName">the short name of the image (e.g. "shockLord"</param>
/// <returns>url to the image, including resolution ("http://.../image1920.jpg">http://.../image1920.jpg) </returns>
public static string GetBackgroundImageUrl(string imageShortName) {
    return String.Format("">http://www.mysite.com/images/{0}{1}.jpg", imageShortName, GetBestStockResolution((int)Globals.XResolution));
}

 

In the above code Globals.XResolution is set to the current full screen resolution when the player clicks play to go full screen.

To use the code, you need to have the same image with various resolutions, e.g. mysite.com/images/background1920.jpg, mysite.com/images/background1024.jpg and so on.

Last but not least, you can use a low-quality background image when the game goes full screen for the first time, and then replace it with the high-quality image once available. I made a special control just for that: SmoothImage in the Controls Pack on Nokola.com. The SmoothImage control will preserve its previous image, whenever you change it's Source and will display the new image once it's available. You can see the SmoothImage sample by launching http://nokola.com/shock and switching between the classes - note how the backroung changes "smoothly" and you don't see a period of transparent/missing image background while the new background gets downloaded.

 

  

Tips/Tricks: Short Sounds, Fireworks and BitmapCache Speed

Jan 4, 2010

This blog post is a follow-up of the previous one dedicated to the Shock improvements.

I'll just list all tips or other interesting findings/updates in no particular order:

Playing Short Sounds in Silverlight (Improved)

You probably know by now that Silverlight (2,3) has an annoying issue with short sounds - sometimes the sound is not played correctly. The simple workaround is here

If you happen to play too many sounds/sec, the above workaround can cause your memory usage to grow more than you want to for short time. The easiest way to avoid the surge in mem usage is to cache the streams that contain the .mp3 sounds, like this:

private static Dictionary<string, Stream> _streams = new Dictionary<string, Stream>();

public static void PlayShortSound(string embeddedSoundName, double volume)
{
    Stream stream;
    if (!_streams.TryGetValue(embeddedSoundName, out stream))
    {
        stream = Application.GetResourceStream(new Uri(String.Format("/Shock;component/Sounds/{0}", embeddedSoundName), UriKind.Relative)).Stream;
        _streams[embeddedSoundName] = stream;
    }
    MediaElement media = new MediaElement();
    _soundContainer.Children.Add(media);
    media.MediaFailed += media_MediaFailed;
    media.MediaEnded += media_MediaEnded;
    media.Volume = volume;
    media.AutoPlay = true;
    media.Position = TimeSpan.FromMilliseconds(0);
    media.SetSource(stream);
}

Then, just call the function like this example: PlayShortSound("explosion.mp3"). You can use this trick, because multiple sounds using the same stream don't cause issues with each other, which is nice.

"Shatter" Effect

In my previous blog post I mentioned the "shatter" brick effect, which is based on this one:  http://www.shinedraw.com/animation-effect/flash-vs-silverlight-colorful-fireworks/

I changed the effect and made it self-recycling: removes itself from the parent after a given time period. It will also display "fireworks" in an area, not  a dot. All of these are really small modifications, but help make the effect "run and forget", and easy to use for "shattering" stuff like bricks.

Here's an usage sample:

public static TimeSpan ShatterEffectTimeSpan = TimeSpan.FromSeconds(0.3);              

Fireworks fireworks = new Fireworks(TimeSpan.FromSeconds(0.3), (int)width, (int)height, brickAppearance == BrickAppearance.Ice);
Canvas.SetLeft(fireworks, centerX);
Canvas.SetTop(fireworks, centerY);
Globals.BoomContaier.Children.Add(fireworks);
fireworks.Start();

And here's the source code. Note: get the complete sample from the shinedraw.com link above and then change the 2 files below. The code won't compile, but you just have to call your own Random() function, and maybe fix few other basic things.

Fireworks.xaml.cs (4.98 kb)  MagicDot.cs (2.89 kb)

I want to call out something very interesting in the above effect (not made by me originally) - each MagicDot is created within its own container. I believe it will cause less layout work than putting all dots within the same container (this is a gut-feeling unverified speculation)

If you want to see the shatter effect in action, break the ice bricks on level 2 of Shock: http://nokola.com/shock

When (Not) To Use BitmapCache, Even For Images That Don't Move

I found out that if I use CacheMode="BitmapCache" for each brick (100+ per level), the performance suffers. This is very interesting, and is something to keep in mind when using bitmap cache.

Seems like the larger the image, the better.

More On Speed

Some blogs (don't remember where) mention that images with Opacity=0 still render and take valuable CPU/GPU time - I tested it and it seems that in Silverlight 4 Beta this is not the case.

I observed no noticeable speed change when a UserControl (the highscore) had Opacity=0 vs Visibility=Invisible. Note, however that Opacity=0 means you get hit testing, so there is some speed difference - don't just blindly overuse Opacity=0 instead of Visibility=Invisible.

 

      

Shock Game got new character - Old Man (the one with the raindeer)

Dec 25, 2009

Now with music (again), special effects. Read more about the effects and other improvements here:

Yes! Thanks to few donations and some out-of-pocket money, I was able to buy back some of Shock's music.

  • Each character has a theme song now. The new character - Old Man, is named after that guy that flies raindeers and distributes gifts through his Christmas distribution chain.
  • The music has new engine now, with "announcing" control (a popup that displays the song title with information).
  • Other improvements include sounds for everything - ball hit, shattering glass, etc. To make this work, I had to create an AutoPlay MediaElement for each mp3 and play it. There are few tricks here that you can use to minimize objects created and mem usage - code will follow!
  • New visual effects include "shatter" effect which is based on this: http://www.shinedraw.com/animation-effect/flash-vs-silverlight-colorful-fireworks/, with the following simple changes/improvements:
    • Fireworks consist of 2 ellipses instead of 5 - speed optimization
    • The effect has "range" - e.g. you can make the dots appear everywhere on the brick, not just one point
    • The effect takes TimeSpan (e.g. 0.5 sec), and plays for a specific time, then the Canvas removes itself from it's parent. This allows to just add the effect anywhere and then start it and forget it. Nice and self-manageable :)
  • Many speed improvements related to GPU and BitmapCache. For some weird reason, I've left the bitmap cache off many of the images before...most notably the background image that takes a lot of space

I have few more improvements in the pipeline - mainly perf and resolving few minor issues (I doubt if anyone will notice most of them)

It is a busy Christmas :) I'll post some code from v3 of the game soon...first let's wrap it up.

Yes, the game is here: http://nokola.com/shock

I strongly advise playing with the Old Man, as it provides the most festive experience! :)

Please let me know your opinion - does it play well? What do you think about it and what would you change or improve?

Thanks!

Note: donations are still open, since the game hasn't paid for itself just yet and I'm planning more games :)

    

nokola.com | Terms | Log in

Recent

About the author

Happy & enjoying life. Software enthusiast.
The opinions I express here and on nokola.com are mine and not my employeer's (Microsoft).
This is the official blog of nokola.com. You can find Silverlight samples, coding stuff, and hopefully other interesting things here.