Monday, August 8, 2011

Back to square one again!

Well, I spent a lot of time scratching my head over a problem that turned out to be a simple dumb issue.

As I detailed in my last post, my models consists of pointers to video memory where the actual information is stored. In a fit of genius, I realized that if I deleted the model but forgot to clean up the information, I'd be leaking video memory. That would be bad. So I made sure that, before a model is destroyed, the information is cleaned up.

This seemed like a clever solution, and the subtle problem didn't become obvious to me until I spent two weeks wondering what I was doing wrong.

The problem happened with copying the model. I created the model in one part of the program, and copied it to the cache that would store it. This preserved the pointers, but when the original model was cleaned up it cleaned up the video memory. Just as I told it to. The copy was then left with pointers that were no longer valid. So when I then asked the program to render the model, it couldn't.

Thankfully the problem wasn't hard to solve. I instead created the model in a space in memory where I have more control of when it gets destroyed, and copy instead a pointer to it. This way the model object itself is never copied or destroyed and I can store it directly. In the process I learned a bit, so I call it a success.

I also had to add a shader to the program, which I should have probably worked into the framework before I did the work on the models. Thankfully I had a ready made function that handles all the aspects of reading the shader from file, loading it to video memory, compiling, linking and binding attributes.

So now, after a couple of months, I'm able to render a red triangle to the screen. Which is about what I was at before the rewrite. So, yay?

Well, to be honest I have a lot more than before. I can read input, and configure it (just need to add more input to be read). My storage is a lot more organized and extensible. I need to do some adjustments to my design to account for lessons learned, but I'm very happy with what I have.

Next up, I'll be adding some actual interactivity. Well, first deal with matrices, then interactivity.

2 comments:

  1. I'm so glad that you made a breakthrough! Nice to have progress again.

    Despite previous advertisement, there was nothing here about stacks and heaps which I find disappointing since I can always use the refresher on those concepts. Because I'm trying to figure out how to trash the heap and overflow the stack. You know, for the good guys.

    ReplyDelete
  2. I did so talk about stacks and heaps! Here: "I instead created the model in a space in memory where I have more control of when it gets destroyed". The problem was I was creating the original object on the stack, and the lifetime of objects in the stack depends on the scope where they are defined. If you create a variable in a function, for example, the variable is stored in the stack and once the function returns the variable is destroyed.

    The way to create something within a function which persists is either to pass along a holding variable as a parameter, so the scope of the variable is greater than the scope of the function, or to allocate it on the heap. To allocate on the heap, you create a variable that holds a memory address (a pointer), and then ask the system for an amount of memory. The system assigns you a bit of memory from the heap and returns the address where you can find it, which you store in your pointer.

    The caveat is that, in an unmanaged system (such as a c++ application where there's no garbage collection), you are responsible for cleaning up after yourself. That memory remains allocated to you for as long as the program runs, until you free it.

    Leaking memory is what happens when you forget the address of the bit of memory you where given before you free it.

    For example, if within a function you create a pointer, ask for memory, and exit the function, your pointer is destroyed when you exit the function but the memory you asked for is still reserved. Unless you return the value of the pointer, however, you have no way of finding that memory address again.

    As for trashing the heap and overflowing the stack. Programs are given a certain amount of memory to run in. I'm not exactly sure how that is determined. If you have a recursive function (a function that calls itself, which is perfectly fine and can simplify code for certain algorithms), and get caught in a recursive loop, you'll eventually run out of stack memory to use and the program will crash due to overflowing the stack (trying to allocate a bit of stack when you have no more to use).

    The OS is careful about making sure you don't touch the memory of other applications, but within the heap of your own program you can do whatever you want. If you use the wrong address when writing to memory, you could overwrite some other bit of code's information. For example, say you reserve memory for something, store the address properly, then free the memory but forget to delete your pointer. That's called a dangling pointer, and it is now pointing at free memory. If you then reserve memory for some other thing, your pointer might end up pointing at the middle of some other variable. Then when you use the pointer, you destroy your own information.

    That's how you trash the heap. If you're lucky, the program crashes immediately (with a minimum of destruction done). If you are not, you might end up corrupting a lot of information before you crash.

    ReplyDelete