smallpt Turbo Pascal 3 port
TL;DR
This is a Turbo Pascal 3 compatible port of the C++ smallpt global illumination renderer.
Source and binaries are available below (along with a ready-to-use archive of Bochs to run a Mode-13 version), along with some pretty pictures and timing information.
Also, I am an idiot.
Introduction
A month ago I saw an article entitled "Things that Turbo Pascal 3 is smaller than" and stumbled upon the smallpt renderer in the same week. Perhaps the fact that both had "small" in the title made my brain decide it was a good match... who knows?
There’s little I enjoy more than going on a random coding spirit-quest in my spare time, because it’s my time, I don’t need to adhere to “best practices” or think about how best it would be to do something, or how neat my code is, I can type at the keyboard like a crazed chimp free of the shackles of expectations!
So I hurriedly and blindly went about converting the smallpt renderer C++ code to Pascal, with an idea of keeping it as backwardly-compatible as possible and boy, did I learn a load of stuff!
From C++ to Turbo Pascal 5.5
I started in Turbo Pascal 5.5, as it's the latest version offered for free from Borland/Embarcadero and would be a good starting point.
Trying to run TP5 under Windows 7 64-bit unsurprisingly failed. Good starting point indeed, ass.
So I set about getting an easy to use DOS environment set up, immediately thinking of DOSBox ("It runs games perfectly!").
It turns out that I made a great decision as it just worked out of the box (hur hur), and I could access files in my DOSBox area easily (no surprise as you simply mount a local folder in DOSBox).
Upon starting TP5, I was a little bit shocked that I couldn't have multiple files open at once in the IDE, so despite my nostalgic memories of TP6 I decided to use a Windows text editor to edit the files... which was dumb, because compiling the main Pascal file meant the the units were not recompiled despite being changed by an external program ("Why isn't the behaviour changing, oh").
Initial changes to the code:
- there are no classes in vanilla Pascal, so I had to make records/structs with utility functions like you normally would... it was then that I realized how many operations had been crammed into some of the C++ lines. Some single lines from the original smallpt expanded to 15+ lines in Pascal (I was surprised that I sometimes struggled to understand why the order of some operations happened as they did).
- variables cannot be declared wherever the hell you want in Pascal, so they all had to be moved to the "
VAR
" section of the functions... leading to some variable declaration sections that were almost longer than the original smallpt source in the first place - the 8087 maths processor option had to be enabled before I could start doing anything floating point related
- it soon became clear that even if I maxed the stack out at the generous 65520 bytes (hooray for real mode!), I would soon run out of stack space due to all the recursive calls to the "
Radiance()
" function, so I had to change to the forward-modelled version of smallpt - likewise, due to memory restrictions the resulting image couldn't be very large at all, so taking a dig around the random box for some numbers, I decided to make it a 128x128 image (powers of 2 always just “feel right”, because you know, coding is magic).
- I implemented an “
erand48()
” function from this source, but I wasn't sure if it was doing the right thing because of all the casts in it (“unsigned long in 16-bit... I... I can't remember!”), resorting to use the built-in "random()
" function.
This was the first time I'd be wanting DOSBox to execute as fast as it could, so changed the setting "
cycles=max
", confident that this would rock my world.Yeah, my world wasn't so much rocked as beaten to death with a mud-covered rock.
9 minutes.
It took 9 minutes to render that tiny 128x128 image at 4spp that the Visual Studio compiled C++ version took a few seconds to do.
"Wow, compilers nowadays really know how to optimise code!" my brain ignorantly said, "TP5 must have lousy floating point support, wow" it continued, "I wonder what Gina Gershon looks like nowadays?".
The resulting image had some artifacts and looked something like this:
Again, my ever-naive brain went, "Pfft, how lousy is Turbo Pascal's floating-point support? I wonder what that could be...".
So I went off searching for floating-point articles and got lost in a maze of horror until my brain could take it no more and clung on to this Wikipedia article about Kahan addition as an escape route.
After implementing it and seeing no improvement, I figured it was Turbo Pascal's fault (I’m prefect afterall) and went further back in time to 1986...
To Turbo Pascal 3
Where I was shocked with the IDE in TP5, here I was mind-blown that there was no IDE, or so it seemed anyway. Once you set your main "work" file to
SMALLPT.PAS
and try to compile it, if it hits any error it tells you to press ESC
(seemingly your only option anyway), at which point it brings up some sort of editor...This is where I admit that I had no idea how to exit the editor (VIM commands didn't even work!
ESC! ESC!
My Mac keyboard doesn't have a SYSREQ
or BREAK
key!), so I embarrassingly kept closing and re-opening DOSBox and typing the mount command a bazillion times. See, the thing is: I always think "It's just going to work! I don't need to do this many more times, next time it'll work for sure!".Needless to say, I now know how to auto-mount things in DOSBox (edit the DOSBox options and add the mount command under the "
autoexec
" section), and after a bit of searching I found a TP3 manual online which tells you that CTRL-K-D
exits the editor and takes you back to the main "menu".Further changes to the source included:
- there are no units in TP3, you can "include" external files using
{$I+}
, but I figured screw it,smallpt.cpp
is all in one file anyway “WORD”
is not a valid data type, so I changed them to“INTEGER”
- there is no
"CONTINUE"
command to skip the rest of a loop and continue with the next iteration, so I used a boolean to indicate whether or not the rest of the code should be executed - No crazy-long lines are allowed
- As things were relatively working-ish now, I decided to implement tiling, e.g., breaking the image up into 64x64 blocks that can fit into memory, and rendering them in order through the image to be able to handle images of any size.
- I used
TURBO-87.COM
to compile the code with floating point support (no doubles, just single precision) toSMALLPT.COM
(which was smaller than TP3 itself, woohoo!)
The program ran without problem ("Wow, such old technology still works!", SHUT UP condescending brain), albeit as slowly as before, AND with those annoying artifacts.
To (other versions)
Turbo Pascal 1 | Turbo Pascal 1 was pretty much more of the same as with TP3 "IDE"-wise (except the arrow keys didn’t work), requiring just a few more code changes:
The resulting SMALLPT.COM ran just fine... a lot quicker than the previous ones, but it resulted in a mostly black-and-white image as you can see on the left.I probably messed up the floating point constants or something silly like that, but I decided that my original goal was TP3 anyway, so I had nothing to fix here and my job was done, like a boss. |
Turbo Pascal 4 Mode 13 | The Mode 13 version required Turbo Pascal 4 as it was the first version that came with unit support, providing the “DOS ” unit necessary to access registers required to switch to mode 13.(Even Turbo Pascal 5 doesn’t support inline assembler, so instead I had to use the “ REGISTERS ” data type in conjunction with the “INTR() ” function to enable mode 13.)It also required me to come up with some 24bit to 8bit colour solution, something I had always run away from before... I didn’t do much better this time either, implementing a pretty-sure-it’s-broken colour version scheme of 24bit RGB values to WebSafe colours. |
Lazarus | The Lazarus (FreePascal) port didn’t require any changes from the Turbo Pascal 3 version, but realtime display of the render was added, and as it turned out the tiling I did earlier for memory management purposes was perfect for making making the application multi-threaded. |
PascalScript | Having already pushed my luck with Turbo Pascal 1, I thought I’d see how well a RemObjects PascalScript would work. There were minimal changes required:
The resulting image looked scarily like the Turbo Pascal 1 version and having tried a larger image at more samples, it’s obvious that there’s some colour there, but it’s still nothing like what it should be. Still, no harm done. It was fun trying the scripting engine out, and I ended up making a funky test-bench kind of thing that you can use to run Pascal code instantly with, it even comes with syntax highlighting courtesy of the SynEdit control! I’m so awesome. Except I’m not. Again, had I looked a bit more, I would have found that the scripting engine comes with an example providing almost exactly what I did myself, numbskull. Oh well, my version provides methods of drawing pixels, lines and other primitives... so... so’s your face! |
DWScript | Having seen a call for some showcase examples, I decided to give DelphiWebScript a shot. As I was interested in how the speed would compare to PascalScript (above), I took that source and tried to compile it in DWS. Again, minimal changes were required:
Running the script for the first time made me do a double-take and make sure I hadn't messed anything up... I hadn't, the script just completed in an amazingly short time. Not only that, but the image looked perfect! |
DWScript->JavaScript | After mailing the ever patient and polite author of DWScript, Eric Grange, he very kindly replied with this JavaScript version of the code, created using Smart Mobile Studio. As Smart Mobile Studio uses the DWScript engine no changes to the code were needed (but he did add a necessary GUI). Not only that, it's fast, really really fast (using Chrome on my PC anyway). Thanks Eric! |
Sanity prevails... ish.
When I did the Lazarus (the FreePascal version of Delphi) port, I found that it was fast, despite initially drawing pixels to the canvas one-by-one while rendering! The speed confirmed once again that I was smart and that Turbo Pascal just wasn't up to the task.
Except... something didn't seem right, so I decided to install DOS somewhere and give the original Turbo Pascal
SMALLPT.COM
a fair chance. The problem I had was that I had no spare hardware, and I wouldn't know how to transfer files between a DOS VM and a floppy-drive-free host...I settled on a VirtualBox and FreeDOS combo, with the idea of using the network support built in to FreeDOS to transfer files (“SSH on DOS? No way! Awesome!”)
I. Am. A. Tool.
SMALLPT.COM
completed in a few seconds.My "great decision" to use DOSBox was -like most of my sugar-fueled decisions-, pretty poor. Sure, it meant I could easily access the files and edit them in place, but the time I would have saved in testing... Sigh.
There was little wrong with Turbo Pascal itself, DOSBox is just slower than most other options.
I also finally actually READ the smallpt web-page and saw that there are known issues when using single-precision floating-point numbers, resulting in the circular artifacts I was seeing earlier. Sadly the site the smallpt page links to is broken and seemingly no archive of it exists, so I randomly changed some floating-point constants, figuring it was an overflow or something (because that’s all that exists in my head), and what do you know, it workses!
(
forward.cpp
, line 130, change *.25
to *0.24
)Render Times
Notes
- The FreePascal version is slower than the C++ version, but I’m guessing that’s largely due to my port being a horrendous mess.
- The Turbo Pascal version is 666 lines long as opposed to the original 99 lines (666 is like 99, it's just upside-down, has a minor stutter, and contains a bit of evil).
- Despite all my mistakes and condescending thoughts, the Turbo Pascal version is dramatically slower than the FreePascal/Visual C++ version.
- As the code compiles happily in Turbo Pascal, Delphi and FreePascal, the same source is capable of being built for CP/M, DOS, Windows, OSX, Linux, the GBA, iPhone, Android...
SMALLPT.COM
is smaller than Turbo Pascal 3, huzzah!- To write out to file, you need to render with a Tile_Height of 1 so that it writes things out in the correct order.
- While trying out different random() implementations, I found that returning values >=0 and <1 would cause slow simulations, sometimes resulting in an infinite loop in the Radiance() function, so I gather it's important to include 1 in the return value.
- Returning values via reference parameters in a procedure is faster than returning values as a function result, but I never got round to verifying the impact.
- If you're angered by why I would "waste" my time on this then you can rest easily tonight safe in the knowledge that it's a personal attack against you.
Downloads
NOTE: I use "sample count" to be the actual sample count in these ports, so if you want 25000spp, you need to enter 6250 as a value (spp / 4)
Turbo Pascal 3 source and executable
Turbo Pascal 4 Mode-13 source and executable (also available: ready-to-use Bochs virtual PC to test it out on Windows machines, just run go.bat!)
Multi-threaded Lazarus source and executable
PascalScript environment executable
DelphiWebScript environment executable
DelphiWebScript->JavaScript environment page