-->

How to add Thrust (GPU parallel computing) to your Visual Studio project

This tutorial assumes you know your way around Visual Studio’s options and have the ability to Google any errors you encounter.

First things first: what is GPU parallel computing? The basic idea was developed by NVIDIA (the graphics card manufacturer) and it means: get large chunk of data -> perform operation -> get output chunk. They called it CUDA. Other manufacturers have released similar systems, but we’re going to focus on CUDA today.

In the world of computing in which we live, we’re only using half our computer for rendering: the CPU. The GPU is perfectly capable of doing calculations, especially when it’s not doing anything else (for instance in raytracing).

We’re going to use Thrust 1.1, which is a wrapper to CUDA, giving it similar functionality to Boost. In fact, if you’re familiar with Boost, you’ll have no trouble adapting to Thrust. Thrust is open source and and freely available at Google Code.

Keep in mind that CUDA will only work on newer NVIDIA hardware. In fact, you might have hardware that supports CUDA, but NVIDIA simply hasn’t released drivers for it.

Finally, every single programming teacher I asked thought this was a bad idea, because GPU’s are going to fade out and everybody’s going to focus on the CPU again.

Alright, everybody clear? Let’s do this.

1. Compatibility and drivers

Go get the latest drivers for your videocard. And even then, even though you just updated your videocard (NVIDIA 9600M GT in my case), the drivers may not support the latest version of CUDA! I had to go to http://laptopvideo2go.com/ and get experimental drivers to get it to work. Why don’t you release those drivers for every one of your cards, NVIDIA?

2. Downloads

Download and install the NVIDIA driver with CUDA support, the CUDA Toolkit and the CUDA SDK

Download and install the CUDA Visual Studio Wizard, which gives you a new option in Visual Studio’s new projects (CUDAWinApp) and, more importantly, a set of build rules.

Download Thrust 1.1 from the Google Code site. Unpack the zip and copy the “thrust” folder to the include folder in the CUDA Toolkit. For instance: F:\Programs\CUDA\include.

3. Setting up your project

First, you will need some dependencies:

Go to the lib folder of the CUDA Toolkit (F:\Programs\CUDA\lib) and copy “cudart.lib” to your project’s lib folder in a folder labeled “CUDA”. (F:\Files\C++\Raytracer\lib\CUDA). Go to the bin folder and copy “cudart.dll” to your project’s release folder (F:\Files\C++\Raytracer\output).

Do the same for “cutil32.lib”, which can be found in the CUDA SDK folder. (F:\Programs\NVIDIA GPU Computing SDK\C\common\lib).

While you’re there, also grab a copy of “cutil32.dll” and copy it to your project’s release folder (F:\Files\C++\Raytracer\output).

Go back to Visual Studio.

Right click on the project (Solution “Raytracer” -> Project “Main”) and select “Custom Build Rules”. At the top should be an option labeled “CUDA”. If it doesn’t show up, close Visual Studio and reload your project. Enable it.

Go to the properties page of your project. In Configuration Options -> Linker -> Input, add “cudart.lib cutil32.lib”.

Go to Options -> Linker -> General and add “lib\CUDA” to the Additional Library Dependencies.

Now, at the bottom of the properties page, a new option has appeared labeled “CUDA”. Go to CUDA -> Backends and add “/MD” to Compiler Options. This will prevent a linker error when compiling later on.

Go to CUDA -> Output File Name and enter a obj name, for instance “$(IntDir)\cuda.obj”

Alright, everything is set up! Time to start coding! But wait… there’s a catch.

4. Enabling syntax highlighting

CUDA doesn’t compile from .cpp files, it compiles from .cu files. And those don’t have syntax highlighting enabled by default. But we can fix that. Go to Visual Studio’s program folder and then Common7 -> IDE (F:\Programs\Microsoft Visual Studio 9.0\Common7\IDE).

Find a file labeled “usertype.dat”. If it doesn’t exist, create it.

Append the following: usertype.dat (1 kb)

Restart Visual Studio and you will have syntax highlighting in .cu files.

5. Examples

Create two new files in your project: “example.cu” and “example.h”.

Go to example.cu’s options and select CUDA in Configuration Options -> General -> Tool.

Now EVERYTHING is set up and we can start coding.

In example.cu, add the following:

#include <thrust/version.h>
#include <thrust/thrust_version.h>
#include <iostream>

#include "example.h"

void HelloWorld()
{
   printf("Hello World!\nThrust version: %i.%i", THRUST_MAJOR_VERSION, THRUST_MINOR_VERSION);

   // load some stuff into a host vector
   thrust::host_vector hostA(4);

   // add data
   hostA[0] = "1.f";
   hostA[1] = "23.2f";
   hostA[2] = "3.14f";
   hostA[3] = "666.f";
}

This works pretty much like a .cpp file, and it needs a header:

#ifndef _EXAMPLE_H_
#define _EXAMPLE_H_

extern "C" void HelloWorld();

#endif

Treat the header the same way you would a class definition, except with the syntax outlined above.

In your main cpp, include example.h.

Now you can call “HelloWorld()” and it will display the following:

Hello World!
Thrust version: 1.1

I can do GPU computing mate. :)

6. Done

For an introduction to Thrust, start with the tutorial pages.

Happy coding. :D

AT&T invokes Godwin’s Law

HOLY FUCKING SHIT.

That was my reaction when I woke up this morning and checked the comments I made last night. One of my Reddit comments had skyrocketed from 58 points to 1149 points. You know you’ve done something right when somebody decided to steal it on Digg, it’s featured on Encyclopedia Dramatica and it’s quoted on insurgen.info.

What had happened? Apparently, AT&T had decided to block access to img.4chan.org, where /b/ resides, the most popular and well-known image board on the planet. I recognized this as the first kill in the battle for net neutrality, and decided to post this:

First they came for the pedophiles, and I said nothing, for I was not a pedophile.

This is the eloquent version of “Dun dun duuuuun”. I was paraphrasing this poem about World War II:

When the Nazis came for the communists,
I remained silent;
I was not a communist.

Then they locked up the social democrats,
I remained silent;
I was not a social democrat.

Then they came for the trade unionists,
I did not protest;
I was not a trade unionist.

Then they came for the Jews,
I did not speak out;
I was not a Jew.

When they came for me,
there was no one left to speak out for me.

The technique described here is known as boiling frog, and it goes a little something like this: first you declare something that everybody hates a public enemy. People go along with that. Then you declare something a little less questionable a public enemy, and people go along with that because they see it as an extension of the first. And finally, you declare something seemingly harmless a public enemy. And people will go along with that.

Except, if you had started with the last group, people would have called you batshit insane.

I don’t like that shit at all, especially when applied to net neutrality.

Encyclopedia Dramatica finishes what I started:

First they came for the pedophiles, and I did not speak out, because I was not a pedophile.

Then, they came for the pirates, and I did not speak out because I was not a pirate.

Then they came for anonymous, and I did not speak out because I was not anonymous.

Then they came for me, and there was no one left to speak out for me.

(Note: I really only thought of the WWII poem, and applied the first line to net neutrality. I really think this version is much, much better.)

First they’ll block questionable websites “to protect the children”. Who can argue with that? Or are you some kinda kiddy-porn lover, huh? Then they’ll go after piracy, full force. Obviously these pedophiles need to get their porn from somewhere, and they get them at sites that support piracy. Therefor, a pirate is nothing more than an enabler for child pornography. And finally, anyone who is anonymous on the Internet will be suspect. If you’ve got nothing to hide, what are you afraid of?

This will be the end of the Internet as we know it.

And what are you going to do about it?

Are you going to enjoy it while it lasts and tell your children about how the Internet used to be a dangerous and awesome place, or are you going to make sure it stays that way?

Optimising on the Casio CFX-9850C

Hello everyone,

I reckon it’s time for an update.

The Casio CFX-9850C is not exactly known for its RAW POWAH. The front has a text proudly proclaiming it’s a COLOR POWER GRAPHIC 64 KB device, and it indeed does have three colors (orange, blue and green combine into black) and a whopping 64 KB of RAM memory. And even though I’ve had it for three years, I’ve only managed to use up 20,127 bytes in the Program section (22,529 bytes free) without deleting much programs. 64 KB is a lot of plain text.

As you may have guessed, I enjoy making programs for this little calculator. Even though it sucks donkey balls.

Features programming-wise:

  • 128 x 64 pixel display giving you 21 characters in 7 rows (the 8th row is reserved for system menu’s)
  • 26 letter variables (A-Z) to store values in
  • 6 lists (one dimensional arrays) of undetermined length
  • 26 matrices (two dimensional arrays) of undetermined size

Not so cool features:

  • The luxurious Program menu gives you three choices: either you figure out the way you want your programs to be organized before you actually start typing, you retype one program into the other and in reverse and rename the files or you use the search function.
  • Functions are quite a few characters which means you will end up with lots and lots of rows of code very quickly.
  • Functions are ridiculously hidden: Shift -> F4 -> F6 -> F2 -> F2 for F-Line for instance.
  • There’s no easy way to backtrack in your code, so you’ll be using the down button. A lot.
  • The data cable can’t be bought anywhere apparently so back up your code by hand.

And yet, I love it.

In short, it makes me feel like a hacker on an 80’s terminal. Programming on the 9850C plus has taught me a lot about optimizing. Some of my findings:

  • If you don’t need to redraw the entire scene, don’t! Only redraw the portions you’re actually using.
  • Keep your code organized, or you’ll lose readability.
  • Reading a variable is much lighter than calculating the same value.

Now that last one is a biggy. To demonstrate this (and what this blog is about), I will show you one of the programs I’m most proud of: COS and its cousins.

COS in its craptastic phone quality glory!

COS in its craptastic phone quality glory!

COS started out as something to keep me busy during boring math classes. I already knew about cosine and sine and I wanted to show off my mad skillzzz with a rotating 3D cube. The problem with this implementation is that every step (every rotation of the cube) every point (x and y) is being calculated using sine and cosine for every line being drawn. The rotation is done with 5 degree intervals. The code comes in at 579 bytes. Pretty reasonable.

Here’s what some of the code looks like:

F-Line 16+Intg (cos (
A)xW),16+Intg (sin (A
)xH),16+Intg (cos (A)
xW),32+Intg (sin (A)x
H)&lt;|
A+90-&gt;B&lt;

Do that times eight and you’ll realize why this is so slow.

I timed it with my phone just now. It takes 36 seconds to render 10 frames which means it has a seconds per frame (spf) of 3.6. If you don’t know what that means, games are usually run at 60 frames per second (fps). It’s kinda slow.

And I knew I could do better.

So the next version, COS3 (there is no COS2, I must’ve deleted it) calculates the x and y of each point, puts it in a letter variable and puts that in the F-Line functions.

COS3 is twice as big and therefor automatically better.

COS3 is twice as big and therefor automatically better.

Sample code:

X+Int (cos (Z+270)xW)
-&gt;G:Y+Int (sin (Z+270)
xI)-&gt;H&lt;|
F-Line A,B,C,D&lt;|
F-Line C,D,E,F&lt;|
F-Line E,F,G,H&lt;|
F-Line G,H,A,B&lt;|
F-Line A,B+J,C,D+J&lt;|

This makes it much faster; twice as fast in fact. It takes 18 seconds to do 10 frames, which gives 1.8 spf. Still, I wasn’t pleased with it. I wanted it do at least 1 frame per second.

I was still optimistic, and that’s why a lot of values that could’ve been a constant (x and y of the cube, width and height, rotation speed) were left as variables.

But optimisation also means stripping out unnecessary functionality, and that’s what I did with COS4.

Displaying the text actually takes about one third as long as calculating the values.

Displaying the text actually takes about one third as long as calculating the values.

COS4 is one of the newer implementations that I started working after actually learning about optimisation in school. One of the things I was taught is to use look up tables. So what I did was put the values into a list and read them out when needed.

1-&gt;Z&lt;|
While Z&lt;37&lt;|
64+Int (cos((Z-1)x10
)x32+0.5)-&gt;List 1[Z]&lt;|
Int (sin ((Z-1)x10)x1
6+0.5)-&gt;List 2[Z]&lt;|
Z+1-&gt;Z&lt;|
WhileEnd&lt;|

The rest of the code was pretty much the same, except I read the values from a list instead of the letter variables. This didn’t result in the dramatic as it did before though; this code renders frames at 1.5 spf and it has a start up time of about 5 seconds. It also dramatically increased the filesize to 687 bytes.

Optimizing at this point didn’t really seem to have much point.

So I tried anyway.

As it turns out, the A-Z variables work a bit like a the N1 cache in a normal computer: the memory is closer to the processor so it can read values a bit quicker than from RAM (the lists and matrices). The N1 cache is extremely limited, but faster than the big RAM.

Translating that bit of computing knowledge to the calculator: putting the values into letter variables before putting them into F-Line actually speeds up things a bit!

Here’s the complete code of COS5 for your viewing pleasure:

"MAKE TABLE..."&lt;|
Deg&lt;|
0-&gt;Z:1-&gt;Y&lt;|
While Z&lt;360&lt;|
64+Int (cos (Z)x32)-&gt;L
ist 1[Y]&lt;|
Int (sin (Z)x16)-&gt;List
 2[Y]&lt;|
Z+10-&gt;Z:Y+1-&gt;Y&lt;|
WhileEnd&lt;|
ClrGraph&lt;|
CoordOff:GridOff:Axes
Off:LabelOff&lt;|
1-&gt;W:10-&gt;X:19-&gt;Y:28-&gt;Z&lt;|
ViewWindow 0,127,1,63
,0,1&lt;|
StoV-Win 1&lt;|
Lbl 1&lt;|
RclV-Win 1&lt;|
'&lt;|
List 1[W]-&gt;A&lt;|
List 1[X]-&gt;B&lt;|
List 1[Y]-&gt;C&lt;|
List 1[Z]-&gt;D&lt;|
'&lt;|
List 2[W]+16-&gt;E&lt;|
List 2[X]+16-&gt;F&lt;|
List 2[Y]+16-&gt;G&lt;|
List 2[Z]+16-&gt;H&lt;|
'&lt;|
List 2[W]+47-&gt;I&lt;|
List 2[X]+47-&gt;J&lt;|
List 2[Y]+47-&gt;K&lt;|
List 2[Z]+47-&gt;L&lt;|
'&lt;|
F-Line A,E,B,F&lt;|
F-Line B,F,C,G&lt;|
F-Line C,G,D,H&lt;|
F-Line D,H,A,B&lt;|
'&lt;|
F-Line A,I,B,J&lt;|
F-Line B,J,C,K&lt;|
F-Line C,K,D,L&lt;|
F-Line D,L,A,I&lt;|
'&lt;|
F-Line A,E,A,I&lt;|
F-Line B,F,B,J&lt;|
F-Line C,G,C,K&lt;|
F-Line D,H,D,L&lt;|
'&lt;|
W+1-&gt;W&lt;|
X+1-&gt;X&lt;|
Y+1-&gt;Y&lt;|
Z+1-&gt;Z&lt;|
W&gt;36=&gt;1-&gt;W&lt;|
X&gt;36=&gt;1-&gt;X&lt;|
Y&gt;36=&gt;1-&gt;Y&lt;|
Z&gt;36=&gt;1-&gt;Z&lt;|
Goto 1

The biggest bottleneck is F-Line. You can actually see every line being drawn. Unfortunately, this is as deep as I can go (it’s not possible to go to assembler) and I can’t really do anything about it.

Final thoughts:

Video of COS5 running (crap quality):

Code deconstruction:

  • COS - 579 bytes - 3.6 spf
  • COS3 - 374 bytes - 1.8 spf
  • COS4 - 687 bytes - 1.5 spf
  • COS5 - 495 bytes - 1.4 spf

As you can see, the actual results (the spf) follows a very nice downward graph. The more time I put into it, the less result I got from it.

The first optimization resulted in a 100% speed boost, while the second gave 20% and the third a measly 7%.

But what that means for game developers is two things: either you can run the same rendering at a higher speed or you can put in more renderings.

If this were  a 3D engine it would mean that I could either put 2 cubes in the game and have it run at the same speed as 1 cube used to or that the 1 cube now runs twice as fast.

But secretly, we also do it because it’s awesome.

It’s like looking at a maze from above. First you find a route to the exit, then you find the most efficient route, then you start knocking down some unnecessary walls until you end up with a maze that looks nothing like it used to, yet still does the same thing.

It’s the ultimate programmer challenge.

-knighty

StickManager 1.5

What you can do with it:

  • Automatically back up folders on your USB drive
  • Encrypt your device
  • Give it a cool name and icon
  • Add personal information to enable someone who finds it to return it to you!

Here it is:

game017

You can download it here (763 kB).

It comes with the complete source and was written in AutoIt.

It’s goddamn 2:37, I’m going to bed.

-knighty

Three things everyone should do this month.

  1. Talk to someone you haven’t spoken in a long time.
  2. For instance this dude who I can't really remember.

    For instance this dude who I can't really remember.

    You know the feeling: “Hey some guy/gal just signed on. Hmm who was that anyway? What’s he/she doing in my MSN list? Mentally ignore.”

    Just, you know, instead of doing that, actually talk to the person this time. Find out why you added him to your list. You must have had something in common. Come on, it’ll be fun! Like finding hidden treasure in the attic! Except with people!

  3. Read a book you haven’t read in ages or play a game you haven’t touched in a while.
  4. I'm not a book type person.

    I'm not a book type person.

    Make sure you pick something that you haven’t touched in quite a while, for the correct dose of nostalgia. Did you know that a shortage of nostalgia is a prime cause of death amongst older people? It’s true, I read it on the Internet.

  5. Make a playlist with a mood.
  6. This one is called "Cafeine.m3u"

    This one is called "Cafeine.m3u"

    First, pick a mood. Is it a playlist you want to play while you’re working? When you come home from a long day? When it’s 7 AM, rain is pouring down, your bike has given up the will to bike and you missed your train? Painstakingly go over every song in your library. Make a bunch of playlists. It’s a bit of work, but it pays off.

OMG IT’S ALIVE

Sup.

Turns out there’s a new season of the IT Crowd AND Screenwipe! Liek omg! The GF think they’re both boring as hell, but she’s a girl. And we all know they have cooties and stuff.

Granted, the IT Crowd can be extremely dull at times. And it can be absolu-fucking hilarious.

“Wait, wait, wait. There are Elders of the Internet? And they KNOW me?”

That’s so lame and yet so awesome at the same time! Brilliant.

Screenwipe is Charlie Brooker’s “Charlie Brooker’s Screenwipe” (by Charlie Brooker) and it explores the wonders of television. I spend waaaaaay too much time on Reddit (there are days when I don’t do much else) so I catch most of the references, but it’s good even if you don’t.

Oh yeah and I guess I’m doing C++ but blah blah blah.

Let’s talk about that later.

-knite

Wine + Games!

Okay, some back story.
A month or so ago, I felt like total, absolute shit.
So I figured: what the hell, I’m going to be sick for a week or more, I can finally try my hand at installing Linux!
So I got the most popular distro I could think of, Ubuntu 7.10, and ordered a Live CD because the ISO’s weren’t burning properly.
I really, really, REALLY like Ubuntu.
In fact, if I could just get Adobe Flash CS3 Professional, Photoshop CS3 and all my (hentai) games to work on Linux, I’d switch my laptop to Ubuntu too!
But that’s okay, I’ll manage… with Wine!

Wine is supposed to be totally awesome at running Windows games and programs, but so far, it doesn’t really work for me. ;[
Here’s a list of games I’ve tried so far:

Test system:
Wine 0.9.58
Ubuntu 7.10 Gutsy Gibbon
AMD Athlon 1 GHz
512 MB RAM
Aeolus nVidia GeForce FX5200 256 MB

(Yes, it’s crap, shut up, I’m poor. >:()

Tips:
-in Wine options, select the option “Emulate a virtual desktop” which will prevent games from running in fullscreen, fucking your shit, right up.

Diablo II (action RPG)
Wine refused to even run the install.exe.
A nice start indeed.

Halo (FPS)
I looked on the Intertubez and downloaded a dll you needed, but to no avail, you could see the install image, but it crashed. :(

Prince of Persia: Warrior Within (Action)
Installer wouldn’t run. :(

Splinter Cell: Pandora Tomorrow (Snipey Spyey Action)
Another Ubisoft game, same problems.

Red Alert (Only the best goddamn RTS ever made)
Wouldn’t install. ;_;

Black & White (Cutesy RTS)
Error during installation.

Battlefield 1942
Yes, a game that will install!!!
Except when you come to the second disk, insert it, and it claims it can’t find it because of some drive letter issue. ;_;

Age of Empires Collector’s Edition (AoE, AoE:expansion, AoE II, AoE II:expansion) (RTS)
Holy crap it installed!!!
And it runs too!!!
The fonts are all black which makes it kinda hard to read and the fullscreen mode is fucked up so you still see the two Ubuntu bars, but it’s working!

Grand Theft Auto: Vice City
Nope, no installation possible.

Microsoft Motocross Madness (Racing)
Another game that installs!
If you have never heard of it, that isn’t exactly a crime, it’s not a really good game.
Just kind of funky that it runs. :O
Bit of lag, but it’s okay. >^_^<
There’s just one problem: no textures are loaded. :\

Neverwinter Nights (Hardkoer RPG)
Nope, error on installation.

Grand Theft Auto 2 (Action game)
Yeaaaaah, it’s installing!
Oh, no, wait, never mind, error at 76%.

WWII Desert Rats (Action)
This game is totally awesome.
It’s so crappy it’s adorable!
It installs just fine, it just won’t run. :(

Aaaaaaand I’ve gone through my collection.

Games that installed:
Age of Empires II
Motocross Madness
Desert Rats

Games that were playable:
Age of Empires II

Kind of a sad score. :\

-knite

Tutorial Thursday - Global variables in ActionScript 3.0

Ohi.
Been a while, ain’t it?
Here’s a small and sweet post about Flash.

A global variable is a variable that you can call throughout your program.
For instance, if you are making a game, and you want to add points to the score and display it in the top right of the screen, you would make a global variable varScore and call it twice.

Now, in ActionScript 2.0, this was fairly straightforward:

//define your variable
_global.varScore = 0;
//call your variable (adding 100 points)
_global.varScore += 100;

Now, apparantly, Adobe figured this was too easy or something, and they gave the programmers a big fat middle finger when it came to global variables.
This, apparantly, because if you have a global variable in your code, and you don’t know where each instance is, and you change it somewhere, you might not know its value.
Even though, you could, like, do

trace(_global.varScore); //this will give you its value!!!

So they decided to not have global variables alltogether.
Yes, that’s right.

Thank the Flying Spaghetti Monster, there is a way to have global variables in ActionScript 3.0.
-make a new ActionScript file, call it GlobalVars.as
-in this file, write the following:

package
  {
  public class GlobalVars
  {
  //stuff!
  }
  }

-at the stuff, write your global variables like so:

public static varScore:Number = 100;

-to call your variable, first import your ActionScript file, like so:

import GlobalVars;

-and then, finally, you have global variables:

GlobalVars.varScore += 100;

This took me three days and 12 forum posts to figure out.

Fuck you Adobe.

-knite

Tags: , ,

New Year’s Eve woooooooot!

Ear!

So, last night, I figured, “What the hell, it’s New Year’s Eve, I’m going to hang out with my friends. Yeeeh!”
This was the non-tipsy, non-dazed version of me.

The Plan:

  • Go pick up (female) friend
  • Use bicycle to tranport us to friends
  • Go clubbin’!

There were, at the start, several things wrong with The Plan:

  • Picking up someone on New Year’s Eve when that person lives about 2 km from your house in an urban area with explosives all around without a working headlight and a very thick fog and fumes mixture is frankly not a very bright idea.
  • Multiple persons makes things only a tiny bit safer
  • I hate clubbin’

Here’s the official List Of Reasons I Hate Clubbin’:

  • Café’s and clubs are in sections of town I don’t usually come
  • There’s lots of people there
  • That invade your very clearly defined personal space
  • I mean LOTS of people
  • Not drink alcohol means you are a pussy
  • THE MUSIC IS SO LOUD YOU END UP USING YOUR PHONE AS A WRITING UTENSIL BECAUSE WHEN SOMEONE SHOUTS IN YOUR EAR YOU SIMPLY HEAR STATIC
  • There’s a reason the fire department has rules about how many people can enter a club
  • People! Lots of ‘em!

We went into the club at 1:30 AM and I was the first to go home at 4:30 AM.
Ofcourse, I tried to stay sober and just ordered cola’s, but, you knooooow, friends will be friends, so they gave me a “dropshot”.
It doesn’t work in English, but it’s a “shot” of “licorice”, that you “drop”, you see.
Basically it’s goddamn ethanol with a licorice taste.
I took a sip first, and it burned my lips and mouth, so I figured, what the hell!
And dropped the whole thing down my throat.

…where it burned everything as it went down.

Yeeeh, alcohol!
Like I said, there were WAY too many people inside, even when I left, and dancing was limited to “tapping my foot and OMG SOMEONE TOUCHED ME AGAIN”.
It was a fun learning experience.

And, you know, everything sounded like I was inside an aquarium when I came out and proceeded to go home.

I’m not doing that shit again, that’s for sure.

Good thing, bad thing!

Good thing!
Getting linked by your girlfriend’s who actually has people that read it.

Bad thing!

# rtsoftbt Says:
December 13th, 2007 at 2:49 am

Thanks to Oprah, Obama camp claims biggest crowd yet

Comments will have to be audited once more!