The latest Delphi version called XE2 was released recently and one of the major new features is 64-bit compiler support. I wanted to try this out so here are some notes on what I had to do to make ZGameEditor run in 64-bit mode.
ZGameEditor is a cross platform open source game engine based on OpenGL. It compiles on 32-bit Windows, Linux and OS X with Delphi or Freepascal compiler.
There are two parts of the project: The runtime engine and the editor. This blog entry will cover the update of the runtime engine.
Pointers
ZGameEditor (ZGE) has a component model that can modify properties (fields) in objects based on their names or ordinal index. It is like RTTI but heavily stripped down because one of the design goals is to minimize exe-sizes (compressed zge-generated empty project exe is only 28kb). I have code that looks like this:
var
P : pointer;
begin
P := pointer(integer(Self) + Prop.Offset);
This is a problem in 64-bit mode because Self is a 64-bit pointer and integers are still 32-bits.
It can be solved like this:
P := pointer(NativeInt(Self) + Prop.Offset);
NativeInt is the integer datatype that has the same size as pointers. I think it is a good decision by Embarcadero to keep the integer datatype 32-bit in 64-bit mode but it would have been helpful if casting pointer to integer would generate a "suspicious cast" warning like it does for certain ansistring/unicode casts. Right now I may still have casts like this lurking in my code and I won't find them until it generates a runtime error. Funnily enough it will still work in 64-bit when the pointer has a value that fits in 32-bit (which seems to be the case when running in the Delphi debugger), so it may not be immediately obvious that the error is there.
Alternatively I could have used:
{$POINTERMATH ON}
P := pointer(PByte(Self) + Prop.Offset);
OpenGL
ZGE use the OpenGL API for graphics. The ZOpenGL unit contains the module headers needed to dynamically link to OpenGL. It use $IFDEFs to separate differences between Windows and Linux, like this.
{$IFDEF Win32}
opengl = 'opengl32.dll';
{$ENDIF}
I thought I would have to change the above to 'opengl64.dll' but it turns out that it is still called opengl32 in 64-bit mode (the same with Windows module user32.dll).
In 64-bit mode the name Win32 is not defined. Since most of the opengl definitions are the same in 32 and 64 I changed my ifdefs to "{$IF Defined(Win32) or Defined(Win64)}".
There are also IFDEFs to use different calling conventions depending on platform but I did not have to change these. I seem to remember reading somewhere that calling convention directives are ignored in 64-bit compilation but I don't find that information now when I google or search the help files.
Unit names
In Delphi XE2 the standard units have namespaces so I had to replace "uses Windows" to "uses Winapi.Windows" in a couple of places. I could alternatively have solved this with the new -NS compiler directive that allows old-style shorter unit names.
Scripting
ZGE use a custom scripting language that is compiled to a binary format and interpreted at runtime. This is the area that required most work to run in 64-bits because I had taken some shortcuts in relying on the fact that pointers are the same size as other datatypes such as integers and single floating point values.
The runtime stack was declared like this:
var
ZcStack : array[0..ZcStackSize div SizeOf(Integer)] of integer;
ZcStackPtr : PInteger;
I changed this to:
type
TStackElement = NativeUInt;
PStackElement = ^TStackElement;
var
ZcStack : array[0..ZcStackSize div SizeOf(TStackElement)] of TStackElement;
ZcStackPtr : PStackElement;
That was necessary for the stack to be able to hold pointer data types that the scripting supports (currently
string and
model).
Then I had to find code that expected pointers to be the same size as single and make changes so that pointers were given a separate execution path instead. This required changes in the compiler too so it would generate correct code.
Google code links:
-
Diff for ZExpressions-unit.
-
Diff for Compiler-unit.
Using single precision math
Eric Grange
discovered that expressions involving the single datatype generates slow code in 64-bit mode. This is because the compiler by default converts all values to double precision during the computation and then back to single again when storing the result.
That behavior is fine when you want maximum precision but bad when you prefer speed over precision like I do in this project. It is also not very smart implemented because expressions like "y:=x*x*x" will generate code to convert x to double three times, that is once for every use. However good news came after a few days when an undocumented compiler directive related to this was revealed:
{$EXCESSPRECISION OFF}
This disables the automatic promotion of single precision expressions to double. Adding this line at the top of my dpr-file made the resulting binary 5kb smaller. That's a lot of conversion instructions that got removed and the code became faster too :)
Summary
With the changes above I got the runtime working in 64-bit mode! I think Embarcadero has made a great job with the Delphi XE2 product and I recommend all Delphi users to upgrade. I hope they get some new users too because this must be the most productive tool on the market for making native 64-bit Window exe-files.
The image at the top of this page shows the ParticleToy sample ZGE project running in 64-bit mode. Only glitch is the caption of the window which displays garbled characters, I hope to solve this soon!