Articles About Bird
Table of Contents
1st Sample Program: Squares
In
this sample I draw squares that are fading gradually. When the mouse is over
it, it appears.
I created some constants and an array that contains that how much a square
is visible:
using System
using BlitzMax
namespace SquaresSample
const var SquareSize = 32,
Width = 24,
Height = 20
float[Width, Height] Array
The IsMouseOver
function returns true if the the mouse is
over the specified area:
bool IsMouseOver(int x, y, w, h)
return x <= MouseX() < x + w and y <= MouseY() < y + h
The Update
function draws the squares and updates the values of
the array. It stores the brightness in the Value
variable, the
position of the square is stored in XPos
and YPos
:
void Update()
for var x, y in 0 .. (Width, Height)
var Value = Array[x, y]
var XPos = x * SquareSize
var YPos = y * SquareSize
If the mouse is over the square it sets the Value
to
1
, which is the maximal brightness, and draws a
white rect:
if IsMouseOver(XPos, YPos, SquareSize, SquareSize)
Value = 1
SetColor 255, 255, 255
DrawRect XPos, YPos, SquareSize, SquareSize
If the mouse is not over the square and it's not black, it
draws the rect with the appropriate color for its position and
brightness:
else if Value > 0
var Color = 255 * Value
var Red = (int)(((float)x / Width) * Color)
var Greed = (int)(((1 - (float)y / Height)) * Color)
var Blue = (int)(((float)y / Height) * Color)
SetColor Red, Greed, Blue
DrawRect XPos, YPos, SquareSize, SquareSize
It decreases
the Value
variable until 0
and stores it in the
array:
Array[x, y] = Math.Max(Value - 0.02, 0)
The program starts with the Main
function. It creates the
graphics window with Graphics
. In a cycle it constantly clears,
redraws and updates the screen (60 times in a second by default). It can be
interrupted with pressing Escape or closing the window:
void Main()
Memory.Zero Array, sizeof(Array)
Graphics SquareSize * Width, SquareSize * Height
while !KeyHit(Keys.Escape) and !AppTerminate()
Cls
Update
Flip
2nd Sample Program: Circles
This
sample calculates the color of each pixels, so it's useable for a small
comparison with C++. In order to increase the performance, it computes on an
image that is smaller than the window and it scales it up.
I made some helper functions. The ArgbColor
function combines
the four 8 bit components of a color to a single 32 bit number:
using System
using BlitzMax
namespace CirclesSample
int ArgbColor(byte a, r, g, b)
return (a to int) << 24 | (r to int) << 16 |
(g to int) << 8 | b
The GetDistance
calculates the distance of two points:
double GetDistance(double x1, y1, x2, y2)
var x = x2 - x1, y = y2 - y1
return Math.Sqrt(x * x + y * y)
The Clamp
function creates a byte number that is between
0
and 255
from an integer:
byte Clamp(int x)
if x < 0: x = 0
if x > 255: x = 255
return (byte)x
The main task is done by UpdateImage
. It calls the
LockImage
first to make the image modifiable, and it stores how many
seconds elapsed since computer startup:
void UpdateImage(IntPtr Image)
var Pixmap = LockImage(Image, 0, true, true)
var Width = ImageWidth(Image)
var Height = ImageHeight(Image)
var Time = MilliSecs() / 1000.0
It goes through all pixels with a for loop, calculates its relative
position, and its distance to the midpoint of the screen with the time
subtracted from it:
for var x, y in 0 .. (Width, Height)
var RelX = (x to double) / Width
var RelY = (y to double) / Height
var Value = GetDistance(RelX, RelY, 0.5, 0.5) * 3 – Time
From the value it calculates a color:
var Light = ((RelY * 100) * Math.Abs(Math.Sin(Value / 1.5)) to int)
var Red = ((RelX * 255) * Math.Abs(Math.Cos(Value)) to int) + Light
var Green = (((1 - RelY) * 255) * Math.Abs(Math.Sin(Value)) to int) + Light
var Blue = ((RelY * 255) * Math.Abs(Math.Cos(Value / 3)) to int) + Light
It stores the pixel and at the end of the cycle it calls the
UnlockImage
function to make the image drawable again:
var Color = ArgbColor(255, Clamp(Red), Clamp(Green), Clamp(Blue))
WritePixel Pixmap, x, y, Color
UnlockImage Image
The Main
function only differs from the previous sample that
it creates the image that it draws scaled up, and it shows the FPS:
void Main()
Graphics 1024, 720
var Image = CreateImage(512, 360)
while not KeyHit(Keys.Escape) and not AppTerminate()
Cls
UpdateImage Image
DrawImageRect Image, 0, 0, 1024, 720
DrawFrameStats
Flip
Here are the performance results (frames per second) on my Intel Core 2 Dou
1.8 Ghz processor:
Image Size | G++ 4.6.2 | Clang (LLVM 3.1) | Visual C++ 11 | Bird |
320×240 | 17 | 19 | 27 | 34 |
Analyzing the Created Assembly Code
The ArgbColor Function
This function has four parameters that are stored in al
,
ah
,
dl
,
dh
registers in this order, the return
value is stored in
eax
. The
(a to int) << 24
expression is stored in
eax
too, because the destination variable
is the same register. But it overwrites the value of
ah
, so it has
to be copied to
cl
. The other sub-expression can be stored in
ecx
without overwriting anything.
_CirclesSample_ArgbColor:
mov cl, ah
and eax, 0xFF
shl eax, 24
and ecx, 0xFF
shl ecx, 16
or eax, ecx
movzx ecx, dl
shl ecx, 8
or eax, ecx
movzx ecx, dh
or eax, ecx
ret
The GetDistance Function
The first three parameter is stored in xmm0
, xmm1
,
xmm2
, the
4th is on the stack. The return value is stored in xmm0
. The stack
pointer is not saved to ebp
in all cases for performance
increasement:
_CirclesSample_GetDistance:
movsd xmm3, xmm0
movsd xmm0, xmm2
subsd xmm0, xmm3
movsd xmm3, qword[esp + 4]
subsd xmm3, xmm1
movsd xmm1, xmm0
mulsd xmm1, xmm0
movsd xmm2, xmm3
mulsd xmm2, xmm3
addsd xmm1, xmm2
sqrtsd xmm0, xmm1
ret 8
The Clamp Function
The compiler uses conditional moves in this case to avoid a conditional jump.
For the cmov
instruction needs the second parameter to be a memory
location:
_CirclesSample_Clamp:
cmp eax, 0
cmovl eax, dword[_25]
cmp eax, 255
cmovg eax, dword[_27]
ret
3rd Sample Program: Fire
In
this sample I created a float array for pixels. These values are
increased if the mouse is close to it while blurring the image too. I store
the colors for these values in another array. These are the constants and
arrays I created:
using System
using BlitzMax
namespace FireSample
const var
WindowSize = (1024, 768),
ImageSize = (560, 420),
RectSize = 80,
ColorNumber = 768
float[ImageSize.0, ImageSize.1] Array
int[ColorNumber] Colors
I this sample I use tuples, the type of WindowSize
and
ImageSize
is
(int, int)
.
I use two functions from the
previous sample:
int ArgbColor(byte a, r, g, b)
return (a to int) << 24 | (r to int) << 16 | (g to int) << 8 | b
byte Clamp(int x)
if x < 0: x = 0
if x > 255: x = 255
return x to byte
I changed the parameter types of the GetDistance
function to
tuples:
float GetDistance(float2 P1, P2)
var x = P1.x - P2.x, y = P1.y - P2.y
return Math.Sqrt(x * x + y * y) to float
The GetValue
function returns with the value of the array at the specified position
if they are valid, otherwise it returns zero:
float GetValue(int x, y)
if 0 <= x < ImageSize.0 and 0 <= y < ImageSize.1
return Array[x, y]
return 0f
The UpdateValues
function updates the values of Array
.
First it blurs the image with the weighted average of the nearby pixels. I
dived the sum with 7.1
instead of 7
to make it
fading:
void UpdateValues()
for var x, y in 0 .. ImageSize
var Value = Array[x, y] * 5
Value += GetValue(x + 1, y + 1) * 0.5
Value += GetValue(x - 1, y + 1) * 0.75
Value += GetValue(x - 2, y + 2) * 0.5
Value += GetValue(x - 3, y + 3) * 0.25
Array[x, y] = Value / 7.1
It calculates the relative position of the mouse and the square that
it will be work inside. The Max
functions returns the
bigger from two numbers, the Min
returns the smaller.
If the parameters are not scalar numbers, it does the same operation
with all their members. The parameter 0
is
automatically converted to (0, 0)
:
var Mouse = (GetMousePosition() to float2) * ImageSize / WindowSize
var P1 = Math.Max((Mouse to int2) - RectSize, 0)
var P2 = Math.Min((Mouse to int2) + RectSize, ImageSize - 1)
Gets the distance for every point and increases the brightness of the
pixel based on the distance. The maximal value is one:
for var x, y in P1 .. P2
var Dist = 1 - GetDistance(Mouse, (x, y)) / RectSize
if Dist >= 0f: Array[x, y] = Math.Min(Array[x, y] + Dist / 10f, 1)
The UpdateImage
function updates the image with the values
stored in Array
:
void UpdateImage(IntPtr Image)
var Pixmap = LockImage(Image)
for var x, y in 0 .. ImageSize
var Color = Array[x, y] * (ColorNumber - 1) to int
WritePixel Pixmap, x, y, Colors[Color]
UnlockImage Image
This function calculates the colors for the values:
void Initialize()
for var i in 0 .. ColorNumber
var Value = (i to float) / (ColorNumber - 1)
var Red = (Math.Min(Value, 1f / 3)) * 3 * 255 to int
var Green = (Math.Min(Value, 1.65f / 3) - 0.65f / 3) * 3 * 255 to int
var Blue = (Math.Min(Value, 1f) - 2f / 3) * 3 * 255 to int
Colors[i] = ArgbColor(255, Clamp(Red), Clamp(Green), Clamp(Blue))
The program's main function is similar to the ones in the previous
samples. It calls the UpdateValues
and UpdateImage
function:
void Main()
Initialize
Graphics WindowSize
var Image = CreateImage(ImageSize)
while not KeyHit(Keys.Escape) and not AppTerminate()
Cls
Update
UpdateImage Image
DrawImageRect Image, (0, 0), WindowSize
DrawFrameStats
Flip
The performance is alike for all the compilers, but the C++ version is much
longer.
4th Sample Program: Reflection
This sample shows how low-level reflection can be used. I plan to make a
higher level layer based on it in the future.
I had made two kind of identifiers. The first one is the declared
identifier that are usually classes, functions, variables, etc. They have a
name. The other group is that are not declared, the compiler generates them,
e.g. when getting the address of the variable or making a tuple.
Writing the members to screen:
The first function outputs five members of the Internals.Reflection.Reflection
class. To do this it reads the reflection data made by the compiler for it
with the ReadDeclaredId
functions that returns a
DeclaredIdData
structure. It contains the name of the identifier, its
members, etc. It can be called for the members too to get their name:
using System
using Internals
using Internals.Reflection
namespace ReflectionSamples
void ListMembers()
Console.WriteLine "-ListMembers-----------------------------------------"
var IdData = Reflection.ReadDeclaredId(id_desc_ptr(Reflection))
var Count = Math.Min(5u, IdData.Members.Length)
for var i in 0 .. Count
var IdData2 = Reflection.ReadDeclaredId(IdData.Members[i])
Console.WriteLine IdData2.Name
IdData2.Free
IdData.Free
Console.WriteLine
Output:
-ListMembers-------------------------------------------
EntryAssembly
GetAliasBase
GetRealId
IsEquivalentHelper
GetDeclaredEquivalent
Comparing Two Types
The next function outputs the type of an object and compares it with
another. The WriteLine
calls the object's ToString
method if the object is not a string. It returns the name of the type by
default:
void TypeCompare()
object Obj = (0, 1, 2)
Console.WriteLine "-TypeCompare-----------------------------------------"
Console.WriteLine Obj
IDENTIFIER_PTR Type = id_desc_ptr((int, int, int))
Console.WriteLine Reflection.IsEquivalent(ObjectHelper.GetType(Obj),Type)
Console.WriteLine
Output:
-TypeCompare-------------------------------------------
(int, int, int)
True
Modifying a variable
This function modifies the value of a variable with the help of DeclaredIdData.Address
:
int GlobalVariable
void SettingGlobal()
Console.WriteLine "-SettingGlobal-----------------------------------------"
var Id = id_desc_ptr(GlobalVariable)
var IdData = Reflection.ReadDeclaredId(Id)
*(int*)IdData.Address = 123123
IdData.Free
Console.WriteLine GlobalVariable.ToString()
Console.WriteLine
Output:
-SettingGlobal-----------------------------------------
123123
Calling a function
The CallingFunction
converts the DeclaredIdData.Address
member to a
function pointer and calls it:
void CallingFunction()
Console.WriteLine "-CallingFunction-------------------------------------"
var Id = id_desc_ptr(Function)
var IdData = Reflection.ReadDeclaredId(Id)
var FuncPtr = IdData.Address to (static string -> void)
IdData.Free
FuncPtr "Function called"
Console.WriteLine
void Function(string Str)
Console.WriteLine "Str = " + Str
Output:
-CallingFunction---------------------------------------
Str = Function called
Function Parameters
The type of all variables are undeclared identifiers. They are described by
the UndeclaredIdData
structure. The Type
member specifies what kind of identifier it
is. e.g. it could be UndeclaredIdType.Pointer
. All declared
identifiers can be referenced with UndeclaredIdType.Unknown
value, the UndeclaredIdData.DeclaredId
member stores its pointer.
The FunctionData
first makes sure that the given identifier is a
function:
void FunctionData(IDENTIFIER_PTR Id)
Console.WriteLine "-FunctionData----------------------------------------"
var IdData = Reflection.ReadDeclaredId(Id)
if IdData.Type != DeclaredIdType.Function
Console.WriteLine "Not a function"
IdData.Free
If it's a function, then it reads the identifier's type data. The type of a
functions contains the return value, parameters, etc. It uses the GetUndeclIdName
function
to determine the name of their type that are undeclared identifiers too:
else
UNDECLARED_ID_PTR FuncType = IdData.BaseUndeclaredId
IdData.Free
var TypeData = Reflection.ReadUndeclaredId(FuncType)
Console.WriteLine "Function: " + Reflection.GetFullName(Id)
Console.WriteLine "Return type: " + GetUndeclIdName(TypeData.BaseUndeclaredId)
Console.WriteLine "Parameters:"
for var i in 0 .. TypeData.Parameters.Length
var ParamType = GetUndeclIdName(TypeData.Parameters[i].UndeclaredType)
var ParamName = TypeData.Parameters[i].Name
Console.WriteLine ParamType + " " + ParamName
TypeData.Free
Determining the name of an undeclared identifier is not simple, so it
only returns its name, if it refers to a declared one or it has a declared
equivalent like UndeclaredIdType.String
:
string GetUndeclIdName(UNDECLARED_ID_PTR UndeclId)
var UndeclData = Reflection.ReadUndeclaredId(UndeclId)
var UndeclType = UndeclData.Type
if UndeclType == UndeclaredIdType.Unknown
var DeclId = UndeclData.DeclaredId
UndeclData.Free
return Reflection.GetFullName(DeclId)
else
UndeclData.Free
var DeclId = Reflection.GetDeclaredEquivalent(UndeclType)
if DeclId != null: return Reflection.GetFullName(DeclId)
return "???"
Output:
-FunctionData------------------------------------------
Function: ReflectionSamples.GetUndeclIdName
Return type: System.String
Parameters:
Internals.Reflection.UNDECLARED_ID_PTR UndeclId
The Main function
It calls the previous sample functions:
void Main()
ListMembers
TypeCompare
SettingGlobal
CallingFunction
FunctionData id_desc_ptr(GetUndeclIdName)