Is DirectX the best for what i need

Hi,

I have an application that renders large hd bitmaps at around 25fps programmed in C#. Currently i have tried GDI and BitBlt. These are very slow with larger images.

My question is, is DirectX a better way to go to achieve 25fps of large HD bitmap rendering to screen or is another way better.

I am coding in C# and would need to be directed to some examples. I appreciate any help.
drewbuckleyAsked:
Who is Participating?
 
SawinerConnect With a Mentor Commented:
Well there are two things here that I believe make your software "slow".

First, and also the reason why I wrote slow in quotes, is you're not really pushing the draw limit.
Games (or any other application that uses DirectX) is ment to call Draw() 60 frames per second. However you're only using it once when you get a texture. I doubt you could ever time it well.

The good way to handle this is to create some mechanism to make sure Draw() function is called ONLY 60 times per second (it can be called a lot more if not much is done).

The easy way to solve it however, is to just handle the Application.Idle event, and in that event to call the Render() method.

You're render method shouldn't get any arguments. Instead, you should store the texture as a global variable and render it if possible, or do nothing if can't.

The second thing is to use threads, because while you load the texture, you can't actually draw anything. You use the same thread for both actions, hence your application hangs while loading, and only then renders. If you do it on another thread on the other hand, you keep rendering data, while a second thread loads the data.

If you need more help about multi threading only tutorials and information from google can help since this is not a little subject, however i'm gonna write some code, which hopefully gonna help you.

Also note in this case I used double buffer instead of locking.

//in the rendered class..
private Texture _tex1;
private Texture _tex2;
private Int32 _texIndex = -1;

private Sprite _sprite;

public void Initialize()
{
    _sprite = new Sprite(device);
}

public void Draw()
{
     if (_texIndex >= 0)
    {
         Texture tex;
         if (_texIndex == 0)
            tex = _tex1;
         else
            tex = _tex2;

         Render(tex);
    }
}

private void Render(Texture texture)
{
    if (device == null)
        return;

       //Clear the backbuffer to a blue color (ARGB = 000000ff)
       tilePosition = new Rectangle(0, 0, this.Width, this.Height);

       device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0);
       //Begin the scene
        device.BeginScene();

      // Rendering of scene objects can happen here
      sprite.Begin(SpriteFlags.AlphaBlend);

     //draw Sprite at current position.
     sprite.Draw(texture, tilePosition, spriteCenter, spritePosition,Color.White.ToArgb());

     sprite.End();

     //End the scene
     device.EndScene();

     device.Present();
}


//instead of rewriting the FrameReceivedVideo function, just replace the new two lines
" texture = TextureLoader.FromStream(device, stream);
            Render(texture);"
//that are in the function with those lines:
LoadTextureThreaded(device,stream);

private struct ThreadTextureLoaderDataHolder
{
    public Device Device;
    public Stream Stream;

    public ThreadTextureLoaderDataHolder(Device device, Stream stream)
    {
        Device = device;
        Stream = stream;
    }
}

private void LoadTextureThreaded(Device device, Stream stream)
{
     ThreadTextureLoaderDataHolder val = new ThreadTextureLoaderDataHolder(device, stream);
      System.ThreadPool.QueueUserWorkItem(Thread_LoadTexture, val);
}

private void Thread_LoadTexture(Object state)
{
     ThreadTextureLoaderDataHolder val = (ThreadTextureLoaderDataHolder)state;

     Texture tex = TextureLoader.FromStream(val.Device, val.Stream);

     if (_texIndex  == -1 || _texIndex == 1)
     {
        _tex1 = tex;
        _texIndex  = 0;
     }
     else if (_texIndex  == 0)
     {
        _tex2 = tex;
        _texIndex = 1;
     }
}


this should do the trick.
Few notes though:
First, I just wrote this code in this little comment box, so expect a few errors :P
Second, I'm assuming you only want to render one texture one the screen at a time

The general idea of the code is double buffer:

you maintain 2 textuers variables, with index.

at the draw thread:
if the index is -1, no texture is ready and nothing will be drawn.
if the index is 0 we draw the first texture.
if the index is 1 we draw the second texture.

at the load texture thread:
first, we load the texture, not making the draw thread wait for us.
if index is -1, we set our texture to the first texture (_tex1) since it's not used, and tell index to map to it (0).
if index is 0, we set our texture to the second texture (again, not used), and tell index to map to it (1).
If index is 1, we reverse back to 0 (that is set our texture to first, and map index to it).

Note the order of setting the texture and index is important, due to the nature of multi threading,
which is also the need for double buffer (or lock).
That is if we set the index first, the draw thread could try to draw a null texture.

Also note don't worry too much about the time, i'm glad to help.

Good luck.
0
 
SawinerCommented:
First, how are you using GDI through C#?
C# exposes GDI+ through System.Drawing. If you are using GDI+ and not GDI, note GDI+ is not known for it speed, and GDI can be a lot faster and you might want to use it (using a dll reference).

using DirectX can yields better fps, but it does have limits. the texture size is dependant on the specific gpu (might be shader model, not sure about that). The limit as far as I know is around 8192x8192 , which is probably less than what you want. Do know this limit also apply to the GDI, unlike the GDI+ (since GDI uses the GPU much like directx, while the GDI+ uses the CPU).

if GDI+ is still not good enough, you probably need to rewrite something like it, probably in unmanaged C++/unsafe C# (you'll to look deeper into it). Not nice nor easy, but possible.

Either way, good luck.
0
 
drewbuckleyAuthor Commented:
Thanks for the info. I think it wa GDI+ like using(graphics....... g.drawimage(...... very slow. Even BitBlt very slow with large images.

I did try direct3d with textureloader.fromstream and this was even slower. probable the size of the texture.

I've also found directdraw.drawfast but it seems i can only load a bitmap from file. my bitmaps are intptr's that i convert into a bitmap. I don't think i'm familiar with plain GDI but is there a way to use DirectDraw.DrawFast with an IntPtr?
0
The new generation of project management tools

With monday.com’s project management tool, you can see what everyone on your team is working in a single glance. Its intuitive dashboards are customizable, so you can create systems that work for you.

 
SawinerCommented:
I have to admit i'm not very familiar with DirectDraw. Do note DirectDraw is deprecated since version 8.0 of directX, and mostly merged into Direct3D (directX).

The newer replaced version of DirectDraw is now Direct2D though, which is only implemented in windows 7/vista. Don't think you can use them easily inside .NET anyway.

Anyway, how are you using directdraw inside C#?

Also, where are the bitmaps coming from? You might want to consider saving the bitmap to file and then using drawfast. Don't think it's a good choice though.
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
You can also look into using WPF in C# as it's supposed to be fast with graphics...but I don't have any experience with it yet (just been reading books on it).
0
 
drewbuckleyAuthor Commented:
Thanks again,

The application was originally in WPF and it was still slow. I went back to winforms for a comparison of technologies. I only tried Direct3D not DirectDraw as yet. Direct3D was using TextureLoader.FromStream.

The bitmaps are coming from a SampleGrabber in DirectShow. Frames that are small like an AVi or something are fine but with HD size, i only get around 2 to 5 frames per second.

Would there perhaps be a dll in C++ available that i could use to display images to screen that is pretty quick? I am trying to find anything that will work the best.

Thanks again for your help
0
 
SawinerCommented:
Personlly I don't know any, but there is probably one or more out there.

I did however find this:

http://www.codeproject.com/KB/graphics/fastimagedrawing.aspx

Which seem to be what you're looking for. Hopefully it is working.

Good luck.
0
 
drewbuckleyAuthor Commented:
Thanks Sawiner,

This looks promising. Please give me a few days to test (i have to impliment into my app) to test before allocating points. Looks good though.
0
 
SawinerCommented:
No problem, just hope it solves your problem. Please post back if you need any further assistance.
0
 
drewbuckleyAuthor Commented:
I was pretty excited but unfortunately it's only slightly slower than BitBlt. Good for System.Drawing though.

I can only imagine that DirectX (in some way) may be my answer because when i play the video in DirectShow, the rendering is fine. Getting the samples from the SampleGrabber and trying to manually render them to the screen seems to be my issue. I have to do the rendering this way (within my C# app) because it is coming from a network connection. I'm at a loss.
0
 
SawinerCommented:
The fact it comes from network connection shouldn't matter that much.
You can still save it to a file and load it, although I don't think it will help make things faster.

However if you think directX is the answer, why not use you?

If the problem is really big textures you need, I actually got an idea:
texture limit is 8192x8192, but you can use more than one.

e.g. if you want texture size of 20,000 x 20,000
divide the texture into parts where each part is less than the texture size limit,
and render that region. The result is rendering more than 1 quad,
but directX vertices limit is a lot more than you can probably reach with just quads.

0
 
drewbuckleyAuthor Commented:
Thanks Sawiner,

I do remember reading the 8192x8192 limit somewhere. I didn't think that was a concern because a HD image is 1920x1080. Is the 8192 in pixels the same as screen resolution?

I could get it to work with Direct3D but it was very slow (around 2 to 3 fps). Could this be the reason it was slow?

I'm only basing that DirectX may be the best way to go based on my googling. I may be wrong.

Once again, i appreciate your time and effort in helping me with this.
0
 
SawinerCommented:
No proble, happy to help.

The 8192x8192 is texels, not pixel. That is, the amount of pixels in the file it self.
The actual pixels on the screen has no real limit.

Do note however bigger resolution creates more pixels, hence more work for the GPU.
However rendering simple box (4 verts) with texture of 1920x1080 is NOTHING for the GPU.

Games usually use around 2mil of verts, textuers with 2048x2048 and high resolution, all work at more than 60 fps. So I would use using DirectX will give you best performence.

I do know XNA is a nice additional set of tools for C# that wraps directx, although it uses is for games, and integrating it with winforms, even though possible, is not easy. Might be easier to just call DirectX your self if you know C++ / DirectX.

I do have to say you might get good results without DirectX, although something is probably more weird if you are getting 2/3 FPS with Direct3D, since Direct3D is part of DirectX.

Can you post the code where you are using Direct3D?
0
 
drewbuckleyAuthor Commented:
This is what i was using:

public bool InitializeGraphics()
{
try
{
// Create a PresentParameters object
PresentParameters presentParams = new PresentParameters();
// Don't run full screen
presentParams.Windowed = true;
// Discard the frames
presentParams.SwapEffect = SwapEffect.Discard;
// Instantiate a device
device = new Device(0,
DeviceType.Hardware,
this,
CreateFlags.SoftwareVertexProcessing,
presentParams);
return true;
}

private void Render(Texture texture)
{
if (device == null)
return;
//Clear the backbuffer to a blue color (ARGB = 000000ff)
sprite = new Sprite(device);
tilePosition = new Rectangle(0, 0, this.Width, this.Height);

device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0);
//Begin the scene
device.BeginScene();
// Rendering of scene objects can happen here
sprite.Begin(SpriteFlags.AlphaBlend);
                //draw Sprite at current position.
                sprite.Draw(texture, tilePosition, spriteCenter, spritePosition,Color.White.ToArgb());
                sprite.End();
//device.SetTexture(0, texture);
//End the scene
device.EndScene();
device.Present();
}

And this is the part that receives the image data:

        private void FrameReceivedVideo(object sender, RtpStream.FrameReceivedEventArgs ea)
        {
              //Thread.Sleep(10);
              try
              {
              fps++;
              //outFrameCount++;
              if (decoderWidth != pullGraph.encoderWidth)
              {
                  setupDecoder();
              }
              byte[] localDecodeArray = decoder.Decode(ea.Frame.Buffer);
              if (localDecodeArray != null)
              {
                    byte[] rotate = StreamCoders.Tools.Visuals.Array24Rotate((int)decoderWidth, (int)decoderHeight, localDecodeArray, RotateFlipType.RotateNoneFlipY);
                    pBmp = StreamCoders.Tools.Visuals.ByteArrayToBitmap_RGB24(decoderWidth, decoderHeight, rotate);
                        
                     using (MemoryStream stream = new MemoryStream())
                     {
            pBmp.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
            stream.Seek(0, System.IO.SeekOrigin.Begin);
            texture = TextureLoader.FromStream(device, stream);
            Render(texture);
                     }
}


0
 
SawinerCommented:
I'm guessing you are using a third library, since I don't see any Sprite/PresentParameters/etc classes in .NET (nor XNA btw).

I would recommend either making your own wrapper, using XNA, or checking with the third library developers then.

Also, can you post the link to the library that contains those classes?
0
 
drewbuckleyAuthor Commented:
Thanks again,

I was sure that i was using just DirectX

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

these are the only using statements i have. I picked that code up somewhere on the net and don't really have an understanding direct3d so i'm not really sure.

Does this help?
0
 
SawinerCommented:
Yes it does. I'm not too much familiar with this part of DirectX of .Net, so wasn't sure which assembly to reference. Found them though.

Anyway, if you are using DirectX that way (which I believe you do) the frame rate shouldn't be getting to 2 fps. You should either try to find the bottleneck of your application, or upload it so I (or anyone else) can take a look and see where the problem is.

I know there are softwares who can help you by monitoring what the software does and how much times each operation takes, not sure which one works for DirectX for .NET though.
I know PIX is used for xna, but I believe it should work for any program using Direct3D, hence you might want to try it (or again, upload it).

0
 
drewbuckleyAuthor Commented:
Thanks again,

I have set up a timer that gives me a rough FPS. I have narrowed it down to the TextureLoader because if i disable this i get 25fps, with it i get 2-3fps. If i use System.Drawing i get around 19fps and if i use BitBlt i get around 20-21 ish.

it would be hard for me to upload the code as it relies on multiple apps over a network.

I also noted that if i am recieving a video that is lets say 480x320, using either System.Drawing or BiltBlt, everythings works as expected. However, i still have serious issues when trying to use the same size video with the Direct3D code.

Does help you to narrow things down at all?
0
 
SawinerCommented:
As you can see, the TextureLoader is making a lot of problems for you (although 25 fps is still very low for what you are doing, there is someting else wrong).

You can use a second thread or even just "tasks" to load textures when they are coming.
That is, instead of loading the textures you should use ThreadPool.QueueUserWorkItem, passing it a function that gets one argument (of type object), and a class of your choice.

A custom class with all the information is needed is what you'll probably want.
After loading is done, the thread should let go of the "main thread"(draw thread) that it can use this texture. Or just add the new texture to some sort of list (probably using a lock).

I do have to add though: is Render() being called anywhere else in your program?
0
 
drewbuckleyAuthor Commented:
Thanks again,

I feel like i'm taking up a lot of your time. I really do appreciate your help.

I've checked and i am only calling render in the framereceived after converting the frame to a bitmap.
I must appologise as i'm not a top level programmer and only have limited knowledge of threads. Most of what you said there went totally over my head. I have heard of threadpools and queuing but know nothing about them. I am however learning from doing if that makes sense.

Would what you mentioned improve the way my code works?
0
 
drewbuckleyAuthor Commented:
Thank you so very much,

I really want to take my time with this so that i can learn. I'll assign the points now and work through this over some time. I'm sure it will make the difference i have been after. If not, i can post another question based on what i have learnt from you.

Thanks again.
0
 
drewbuckleyAuthor Commented:
Sawiner was extreamely helpful
0
 
drewbuckleyAuthor Commented:
Sorry to disturb you again Sawiner,

i keep getting this error:

System.NotSupportedException: Specified method is not supported.
   at Microsoft.DirectX.Direct3D.TextureLoader.FromStream
   at Microsoft.DirectX.Direct3D.TextureLoader.FromStream
   at LaunchVue_Display_WinForm.MainForm.Thread_LoadTexture in c:\LaunchVue Display WinForm\LaunchVue Display WinForm\MainForm.cs:line 249
   at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context
   at System.Threading.ExecutionContext.Run
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback

i've looked at the stream and it seems to be ok. the device looks ok to. any ideas?
0
 
SawinerCommented:
I believe you deleted the line "stream.Seek(0, System.IO.SeekOrigin.Begin);" as well.

When the FromStream() method checks the stream, it throw not supported if the steram cannot be read. The purpose of this exception is since some streams cannot be read at all, but are only writeable. However when a readable file stream reached the end, it's no longer readable (like you probably already know).

If the line does exists and did you not remove it, try to set breakpoint at the FromStream() method and make sure the position is set to 0.

If both are still true and problem still exists, try to give more information about the exception if exists (code error, inners exception).
0
 
drewbuckleyAuthor Commented:
Hi Sawiner,

The stream.seek is still there and the position (on a breakpoint) is 0.

I tried to use the line: Texture tex = TextureLoader.FromStream(device,stream); directly instead of
LoadTextureThreaded(device, stream) and the texture loads OK.

it seems to be something in the way the Device and Stream are being allocated in the DataHolder perhaps.

Thanks again.
0
 
drewbuckleyAuthor Commented:
I just followed the code through breakpoints and found that ThreadPool.QueueUserWorkItem(Thread_LoadTexture, val); val.stream info is ok but when it gets to Thread_LoadTexture(Object state) the state (val) stream says cannot access closed stream.

does this help? i'll keep looking on google.
0
 
drewbuckleyAuthor Commented:
I found that by using the "using(memorystream.............it closed the stream. i removed that and the code is going through now without the error.

i tried to setup the Application.Idle += new EventHandler(OnIdle); to do the draw function and the screen is just black. i also tried setting up a thread to the draw function and the screen is still black. it seems to be running through the renderer function ok but no image.

getting there and learning a lot thanks.
0
 
drewbuckleyAuthor Commented:
Got somewhere, i tried another pc and now i have an image however it is still slow and jumpy. I am also getting memory leakes on the tex texture. if i dispose it after it allocates to either _tex1 or _tex2, i get no image but no memory leak. not sure where to dispose. now that i'm not using the using(memorystream...... i'm also not sure where to dispose this. not disposing it doesn't seem to have any issues.

getting close though.
0
 
SawinerCommented:
Ok you got a few posts there, didn't notice it. sorry for not responding a bit.

Anyway, you're right you should dispose it.
At dispose we got a bit of a problem:
we don't want to dispose an object that is being tried to drawn, however we can't really know if the other thread is still drawing it or not.
However let me try and extend my code from before to acomplish this.

(this time I attached the code to the message. a bit new to the site, hope it looks good).


few things to note:
first, can you post the code actually used to draw (the idle + any code relative) ?
You should also call the Draw() method we talked about on the Paint event (both OnIdle and Paint event).



//in the rendered class..
private Texture _tex1;
private Texture _tex2;
private Int32 _texIndex = -1;
private In3t2 _lastTexIndex = -1;
private AutoResetEvent _are = new AutoResetEvent(false); //not signaled at first

private Sprite _sprite;

public void Initialize()
{
    _sprite = new Sprite(device);
}

public void Draw()
{
     if (_texIndex >= 0)
    {
         Texture tex;
         if (_texIndex == 0)
            tex = _tex1;
         else
            tex = _tex2;

         if (_lastTexIndex != _texIndex)
         {
	_lastTexIndex = _texIndex;
	_are.Set(); //this signals the other thread we are no longer using the last texture,
                  //and can dispose it
         }

         Render(tex);
    }
}

private void Render(Texture texture)
{
    if (device == null)
        return;

       //Clear the backbuffer to a blue color (ARGB = 000000ff)
       tilePosition = new Rectangle(0, 0, this.Width, this.Height);

       device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0);
       //Begin the scene
        device.BeginScene();

      // Rendering of scene objects can happen here
      sprite.Begin(SpriteFlags.AlphaBlend);

     //draw Sprite at current position. 
     sprite.Draw(texture, tilePosition, spriteCenter, spritePosition,Color.White.ToArgb());

     sprite.End();

     //End the scene
     device.EndScene();

     device.Present();
}


        private void FrameReceivedVideo(object sender, RtpStream.FrameReceivedEventArgs ea)
        {
              //Thread.Sleep(10);
              try
              {
              fps++;
              //outFrameCount++;
              if (decoderWidth != pullGraph.encoderWidth)
              {
                  setupDecoder();
              }
              byte[] localDecodeArray = decoder.Decode(ea.Frame.Buffer);
              if (localDecodeArray != null)
              {
                    byte[] rotate = StreamCoders.Tools.Visuals.Array24Rotate((int)decoderWidth, (int)decoderHeight, localDecodeArray, RotateFlipType.RotateNoneFlipY);
                    pBmp = StreamCoders.Tools.Visuals.ByteArrayToBitmap_RGB24(decoderWidth, decoderHeight, rotate);

	MemoryStream stream = new MemoryStream();
	pBmp.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
	stream.Seek(0, System.IO.SeekOrigin.Begin);
	
                         
	LoadTextureThreaded(device,stream);
              }

private struct ThreadTextureLoaderDataHolder
{
    public Device Device;
    public Stream Stream;

    public ThreadTextureLoaderDataHolder(Device device, Stream stream)
    {
        Device = device;
        Stream = stream;
    }
}

private void LoadTextureThreaded(Device device, Stream stream)
{
     ThreadTextureLoaderDataHolder val = new ThreadTextureLoaderDataHolder(device, stream);
      System.ThreadPool.QueueUserWorkItem(Thread_LoadTexture, val);
}

private void Thread_LoadTexture(Object state)
{
     ThreadTextureLoaderDataHolder val = (ThreadTextureLoaderDataHolder)state;

     Texture tex = TextureLoader.FromStream(val.Device, val.Stream);
     //let's close the stream now it's gone, we don't really need it
     val.Stream.Close();

     Int32 disposeTexIndex = _texIndex; //save the one currently used, which will be disposed later (if not -1)
     if (_texIndex  == -1 || _texIndex == 1)
     {
        _tex1 = tex;
        _texIndex  = 0;
     }
     else if (_texIndex  == 0)
     {
        _tex2 = tex;
        _texIndex = 1;
     }
     
     if (disposeTexIndex != -1) //if there isn't any texture to dispose, we don't need to wait
     {
          _are.WaitOne(); //this method will block until draw thread will signal us we can dispose other texture

          if (disposeTexIndex == 0)
                _tex1.Dispose();
          else if (disposeTexIndex == 1)
                _tex2.Dispose();
     }
}

Open in new window

0
 
drewbuckleyAuthor Commented:
Hi and thanks again,

i am setting Application.Idle += new EventHandler(OnIdle); in the onload and letting this happen                 public void OnIdle(object sender, EventArgs e)
                {
                      Draw();
                }

the rest of the code is the same as your original except i had to init sprite as opposed to _sprite. I think it's the same.

I will try your new code with the reset event. At least if nothing else, i am learning a lot from you and i thank you for that.
0
 
SawinerCommented:
No problem, I still hope to help you solve the problem beside the learning though :}

What do you mean you have to init the sprite?
The reason I used a _sprite variable is because you are creating the spirte EVERY TIME.
Something you don't want to do. It shouldn't affect the FPS too much, but I don't know much about the Sprite class of .net DirectX, so it could be affecting the fps dramatically , or do almost nothing, depends what the actual sprite class does. Either way it's not a good idea to keep creating classes unless you have to.
0
 
drewbuckleyAuthor Commented:
Thanks for that,

What i meant by init the sprite was that in your initialise function you have _sprite and in the Renderer function it is just sprite which is not declared anyware else so i just put the line in the Initialize in the Renderer function before the sprite.draw.

Does that maybe make a big difference.

I know i'm saying thanks a lot but i do appreciate your time.
0
 
drewbuckleyAuthor Commented:
Allright i found some things.

With the AutoResetEvent i get the first frame and that's it. the memory seems stable but then goes up and down and then i get an out of memory. I'm still trying to understand your level of knowledge so i'm really not sure yet what may be going wrong.

does this help?
0
 
SawinerCommented:
You shouldn't create a Sprite class every time you render, no. Not sure how big the difference is, but it's not good that's for sure.

What do you refer to in first frame?
you should render around 60 fps (or 25 like you said), resulting in 25 fps for second. so only the first Render() call works? how did you figure? what happens in the other not in the rest?
I doubt you can see the difference between the first frame and the other 24 frames, which are all in the same second ..

Either way I don't see why you should get either OutOfMemory exception or only first frames.
On what line do you get OutOfMemoryException?
0
 
drewbuckleyAuthor Commented:
Thanks for everything,

I think i really need to study your work a lot more. You have given my everything there that i should now go off and with the help of google, i'm sure i can learn and understand more.

Thanks again.
0
 
SawinerCommented:
No problem. Try and update this thread once you found a solution. Would be good for me too to learn what was your mistake.

Good luck!
0
All Courses

From novice to tech pro — start learning today.