Hi guys, I'm in the process of scripting a memblock tutorial for my usual DarkBASIC Professional video's but I might be a little shaky in my understanding of them because I've never needed to use them in any of my own projects so far. So rather than just blindly producing a video that turns out to be mostly wrong, I thought I'd post a draft of the opening script here.
Feel free to point out all the bits it I got wrong!
********* Script v0.6 *******
Memblocks
Because I’m going to be wrapping up any DarkBASIC Professional tutorials for the next six months, in preparation for DarkPRINCIPLES 2.0, I thought it was about time I tackled the tricky subject of memblocks.
So first of all, what is a memblock and why is it useful?
A memblock, simply put. Is a block of memory used to store some kind of data. In many ways it’s like using an array, or creating an identifier in that it’s purpose is to hold some kind of information in memory. While identifiers and arrays have a very structured way of doing this, memblocks give complete freedom to the user by allowing direct access to raw information. You could for instance create model data in a memblock and then export that data as a .x file. You can create or manipulate image files, or even produce sounds. You can send network data using memblocks, or even create your own unique file format. A boon to any user worried about anyone ripping their content from their games. A memblock is completely freeform, but it does require one thing. Knowledge of the data structure in question. You can’t put any old mess into a memblock or the computer simply will not know what to do with it no matter what file extension you save it with. If you do decide to create your own file format, then it’s simply a matter of instructing the computer on how to understand it. DarkBASIC Professional has memblock specific commands that help with all of this, and make your life much easier if you are happy with standard formats. But this is all pretty advanced stuff, and for now we’re just going to look at the basics. Namely how to get data into a memblock, and how to pull data out of it.
So how does memory work?
As we all should know, computer technology is based on the humble bit. A bit is a simple switch with an on or off state. This is what we call binary data. The computers memory is formed into groups of 8 bits, which we call bytes. Every byte is indexed by a 32-bit memory address. These memory addresses simply count up from 0 to whatever until there not more memory to access.
In theory we can at any time write to any memory location on your computer, but in real life you can’t do this. Windows, as well as it’s applications and services are already using a whole heap of memory, so if the user was allowed to simply address memory directly there would be no knowing what trouble they could get themselves in. So, this is why we create memblocks. By creating a memblock, we isolate a block of memory, and tell windows to reserve that block for our usage. By doing it this way, we don’t interfere with windows nor it’s services, processes or applications.
Pointers
So what are pointers and how do they work? Pointers work as memory location references. That is, they store the specific 32-bit memory address that the data is held at. But they don’t only do this, if you put an astrix in front of a pointer then that pointer is deferenced and instead accesses the data stored at that memory location. Because memory addresses are always 32-bit, any pointer created must be assigned as a DWORD. A DWORD has the numerical range of 0 to 4,294,967,295 so there can never be any addresses that exceed that number. It also can’t handle negative numbers. Because DarkBASIC Professional and indeed most operating systems are 32-bit this does limit the amount of ram you can access. But for the majority of software, this is more than enough.
The basic memblock commands
Before we can use pointers, we need to reserve a block of memory for our usage. To do this we use the Make memblock command. The make memblock command works in the usual DarkBASIC Professional way. You assign a memblock ID, and a size.
MAKE MEMBLOCK memblock number, size
The memblock number references the memblock you created, this allows DarkBASIC Professional to manage memory addresses for you and it allows you to reference those memory addresses when you need them. Please note that you can only create 255 memblocks at any one time, though I doubt you’ll ever need more than two or three at any on time.
Size is fairly straight forward, it’s managed in bytes, so a vaule of 1 equates to a value of 1 byte, if you put in 256 bytes then you get 256 bytes which is a lot of data.
This command will create the memblock but you also need to retrieve data from that memblock, and to do this we’re going to use the GET MEMBLOCK PTR command. This command passes a the memblocks address into our pointer value as an integer.
The practical bit
Now lets create a simple program that will create a memblock, retrieve that memblocks memory address, pass data into that memblock and then display both that memblocks address and it’s data.
We start off by creating a varible called pointer, and assigning it as a DWORD.
pointer as DWORD
Next we create a memblock and assign it the ID of 1, and a value of 1 byte.
Now we get the memory address from that memblock we just created and pass that value into pointer.
pointer = GET MEMBLOCK PTR(1)
Next, using the atrix, before the varibles identifier, we deregister that pointer and pass our programs value into memory.
Now we call a couple of print statements to display both the memory addresses value (note, this will chance will every running of the program) and reference the data within the pointer.
print "Memory address: "; pointer
print "Pointer content: "; *pointer
Finally we use wait key to pause our program.
The complete code
pointer as DWORD
Make memblock 1, 1
pointer = GET MEMBLOCK PTR(1)
*pointer = 96
print "Memory address: "; pointer
print "Pointer content: "; *pointer
wait key
Some more theory about memblocks
So far in this tutorial, we’ve simply grabbed a memory location and written data to it using pointers. Now we’re going to do the same thing using memblocks. With pointers we can store virtually any kind of numerical data we like. We don’t have to worry about the size of the value we put into it. Memblocks are far stricter so you’ll need to understand the differences between the four types of data you can use.
Byte, as specified before is 8 bits, in binary the way a bit works is by adding together different combinations of bits to form a value. For example:
00000000 = 0
00000001 = 1
00000010 = 2
00000100 = 4
00001000 = 8
00010000 = 16
00100000 = 32
01000000 = 64
10000000 = 128
By adding additional 1’s into the sequence we can make any number for example, the maximum value possible in a byte is:
11111111 = 255
if I wanted to represent 129 then it would be 10000001
10000010 = 130
10000011 = 131 etc
The upshot of all this, is that the maximum value you can placed into a single byte is 255, if you exceed that number than you will get an error saying:
“Runtime error 5105 - Memblock position outside range at line x”
what this means is that you’ve attempted to put something larger than a byte into a memblock that’s not big enough to support it.
The next size up from a Byte is a WORD. A word works in the same way as a byte, except it supports much larger numbers. In the same way a byte works in a sequence of 1 2 4 8 16 32 64 128 a WORD adds another 8 bits, so we’re moving from 8-bit to 16 bit, this gives us 1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 which gives us a maximum possible number of 65535.
The next size up is a DWORD which follows the same pattern and allows for a maximum size of 4294967295
Finally there’s the Float which is a 32-bit value. However you can only write a float value with eight decimal places if you wish that float to remain accurate. For example. 0.999999 will remain accurate, while an extra 9 will cause the program to round it off at 1. If you lose a 9 off the end, you can also have 9.99999 or you can have 99.9999 or 999.999 I’m sure you see the pattern here. As you move the decimal point backwards or forwards, you lose from one end and gain on the other.
What this all means is that, to host a byte you only need 1 BYTE created in your memblock, to host a WORD you need two bytes, to host a DWORD you need four bytes and for a FLOAT you also need four bytes.
Read and write memblocks
Now that you understand the different types of data we can use lets have a look at the memblock commands. The first command we’ll need is write memblock which comes in four flavours.
Write Memblock Byte Memblock ID, Position, Data
Write Memblock Word Memblock ID, Position, Data
Write Memblock Dword Memblock ID, Position, Data
Write Memblock Float Memblock ID, Position, Data
Each of these commands work in exactly the same way, only the nature of the data changes which was covered above. The first parameters, memblock ID refers to the memblock number you created, the position refers the the byte’s position. In the same way arrays do, memblocks start at 0, so if you create a memblock with the size of 1 byte, then it’s position will be 0.
The next commands are:
Memblock Byte(memblock ID, position)
Memblock Word(memblock ID, position)
Memblock Dword(memblock ID, position)
Memblock Float(memblock ID, position)
To use these commands just pass them into a varible for example.
MyByte = Memblock(1, 0)
MyFloat# = Memblock(1, 0)
etc. This command simply retrives the data, in the specified format and passes it into an identifier.
Writing and reading memblocks practical code
We need to assign our tmpdw as a DWORD or it will simply reference as an integer, and cycle to 0 rather than giving the correct number.
First we start our practical program off by creating a series of memblocks, remember that bytes require 1 byte, Word requires two bytes, dword requires four bytes and floats require four bytes as well.
Make memblock 1, 1
Make memblock 2, 2
Make memblock 3, 4
Make memblock 4, 4
Next we write the maximum data values we can to each of our created memblocks. We’re keeping this example simple and writing four different memblocks rather than mixing the data into one big one.
write memblock byte 1, 0, 255
write memblock word 2, 0, 65535
write memblock dword 3, 0, 4294967295
write memblock float 4, 0, 0.999999
Now we’re going to take the data we just placed into our memblocks and pass them into identifiers ready to display on the screen.
tmpb = memblock byte(1, 0)
tmpw = memblock word(2, 0)
tmpdw = memblock dword(3, 0)
tmpf# = memblock float(4, 0)
We finish up our program by printing the results of each, and waiting for the user to press a key to end the program.
print tmpb
print tmpw
print tmpdw
print tmpf#
wait key
Complete code:
tmpdw as DWORD
Make memblock 1, 1
Make memblock 2, 2
Make memblock 3, 4
Make memblock 4, 4
write memblock byte 1, 0, 255
write memblock word 2, 0, 65535
write memblock dword 3, 0, 4294967295
write memblock float 4, 0, 0.999999
tmpb = memblock byte(1, 0)
tmpw = memblock word(2, 0)
tmpdw = memblock dword(3, 0)
tmpf# = memblock float(4, 0)
print tmpb
print tmpw
print tmpdw
print tmpf#
wait key
Combining all this data into one single memblock.
In the last example we used four seperate memblocks, now we’re going to store all this information in one single memblock. To do this we need to work out the positions within that memblock based on size and previous data stored. We’ll do it in the same order, a byte, a word, a dword and finally and integer, so that means we need:
1 byte = byte
3 bytes = byte + word
7 bytes = byte + word + dword
11 bytes = byte + word + dword + float
So instead of simply using Make memblock 1, 1 we instead create a memblock with 11 bytes in it.
Now we need to consider the starting position of those bytes. We already know that memblocks work in the same way as arrays in that they start at a position of 0. So that means, our first byte starts at 0. Then we add the size of the preceeding byte, to get the next slot.
byte = 0, word = 1, dword = 3 and float = 7.
So now that we’ve worked out the position of this data within our memblock we can simply save and load the information into memory at will.
Final practical bit, combining the data into one memblock
This program largely follows the previous version, with one key difference. Everything now refers to memblock one. Two three and four are removed completely from our program and instead we use the postion information we just worked out to place and store our data. In the same way we did before we’re opening with tmptw as DWORD so we get the right value printed.
Now we create our single memblock with 11 byte’s.
Next we write the same data as before, using positions instead of different memblocks. We’re using the positions we worked out just now.
write memblock byte 1, 0, 255
write memblock word 1, 1, 65535
write memblock dword 1, 3, 4294967295
write memblock float 1, 7, 9.99999
Finally we call the memblock data into identifiers, and use the position of the memblock rather than referencing several different memblocks.
tmpb = memblock byte(1, 0)
tmpw = memblock word(1, 1)
tmpdw = memblock dword(1, 3)
tmpf# = memblock float(1, 7)
And finally we print the results for testing and wait for the user to press their key.
print tmpb
print tmpw
print tmpdw
print tmpf#
wait key
Memblock and bitmaps
Now we have the basic in’s and out’s of memblocks we can start to do something more interesting, up until now we’ve just been placing data into a memblock and and taking data out of it. But what if we wanted to create an image using a memblock instead?
As I previously stated, before you can do something like this you need to understand how the data is formatted, for this example we will be working directly with bitmaps so the data is formated like this.
byte 0 to 3 = bitmap width
byte 4 to 7 = bitmap height
byte 8 to 11 = bit depth
byte 12 to ∞ = pixel data
Everything here is a DWORD so you need to allow four bytes of data for each entry including the pixel data.
The width, height and bit depth is pretty self explanatory. With a little bit of math we won’t even need to know our projects bitmap dimensions for this program to work. The art of good programming is allowing the computer to do all the work for you.
The pixel data is easy to manipulate, in fact you can use the rgb() command to do so, for example, WRITE MEMBLOCK DWORD 1, 12, RGB(255, 255, 255) will create a white pixel in our memblock. It’s as easy as that. It gets a little tricker if you want to position your pixel data precisely, for this we need a little bit of mathematics but again it’s straight forward enough.
The sum I use comes in two parts, because we’re working with X and Y, we’ll do this in two parts. First, we need to work out the y position on the screen. This is done with.
y = (pixel you desire)
y = y * width * 4 + 12
The y represents how far down the screen you want to go, but to get there we need to multiply it by the number of pixels from left to right, and then multiply it by four. Remember each pixel location is a dword, so that’s four bytes, we add an addition 12 to compensate for the first 12 pixels discribing the images settings.
Imagine you’re using a word processor, and for some reason the return key is broken. To get to the next line, you have to press space until your cursor appears on the next line, the same thing is happening here, we’re simply skipping the memory locations we don’t want to adjust, until we hit the line we desire. This is the Y axis.
The next part is to add the X axis to the sum, this is easy enough, it’s simply as follows.
X = (x location)
X = X * 4
And that’s it for the X axis, now all we do is add them togther to get the memory location we wish to alter.
(y * width * 4) + (x * 4) + 12
And there we have it, our memory location.
A simple drawing program
Now you know how to find the correct pixel within the memblock and update it, we can proceed onto our program. But before we do lets go over it’s structure.
First we’re going to start by producing a memblock from an existing bitmap, DarkBASIC Professional provides this for you readily, and at the size and bit depth of your programs viewing area. So if you create a program that’s 640 x 480 x 32-bit then that’s the bitmap that’s automatically generated. We do this with the MAKE MEMBLOCK FROM BITMAP command, this basically imports the background as a bitmap into a memblock giving you an image file in memory ready to edit.
Next we’re going to extract the width, height and bit depth from the created memblock, and bring it into our program as a set of variables. Once this basic data has been imported we move onto the main bit of our program.
For our main loop, we’re going to take the mouses location, and draw a dot at that location when the left, middle or right mouse button are pressed. Left will produce a nice cyan colour, the middle yellow and right purple.
Once per cycle we will then convert our memblock back to the bitmap which will then update the background so the user can view their art.
The art program
We start our program by using the existing bitmap backdrop and converting it into a memblock ready for us to edit.
make memblock from bitmap 1, 0
Next we extract the data from our bitmap ready to use, but bit value won’t be used in this program, but it’s useful data to know how to extract so I’ll leave it in there just for reference.
width = memblock dword(1, 0)
height = memblock dword(1, 4)
bit = memblock dword(1, 8)
Next we start our do and loop sequence, there’s no need for sync with this example, when we update the bitmap we update the screen’s background.
This part of the program will basically be repeated three times with different colour settings and different mouse buttons. But the math and logic are the same throughout. We start by asking if the left mouse button has been pressed.
If it has, then we need to get the mouse pointer’s location, and assign it to the value of X and Y using our previously discussed mathematics. There’s no need to compensate for the 12 extra bytes here, because we’ll do that in the memblock command instead.
y = mousey() * width * 4
x = mousex() * 4
So now we have the raw data sorted, we then need to bring it into our memblock command.
write memblock dword 1, 12 + x + y, rgb(0, 255, 255)
And finally we end the if statement.
Now lets repeat the process for our two other buttons, 2 is the right mouse button and 3 is the middle. I’m using a wheel mouse, so I’m clicking that instead. This means I map that button to 4 instead of three.
If mouseclick() = 2
y = mousey() * width * 4
x = mousex() * 4
write memblock dword 1, 12 + x + y, rgb(255, 0, 255)
EndIf
If mouseclick() = 4
y = mousey() * width * 4
x = mousex() * 4
write memblock dword 1, 12 + x + y, rgb(255, 255, 0)
EndIf
Now that we have our memblock data entered and ready to use, we then convert it from a bitmap into a memblock, this automatically updates the screen for us allowing us to view our masterpeices.
make bitmap from memblock 0, 1
And finally we loop the program and repeat these actions.
The complete code
make memblock from bitmap 1, 0
width = memblock dword(1, 0)
height = memblock dword(1, 4)
bit = memblock dword(1, 8)
do
if mouseclick() = 1
y = mousey() * width * 4
x = mousex() * 4
write memblock dword 1, 12 + x + y, rgb(0, 255, 255)
EndIf
If mouseclick() = 2
y = mousey() * width * 4
x = mousex() * 4
write memblock dword 1, 12 + x + y, rgb(255, 0, 255)
EndIf
If mouseclick() = 4
y = mousey() * width * 4
x = mousex() * 4
write memblock dword 1, 12 + x + y, rgb(255, 255, 0)
EndIf
make bitmap from memblock 0, 1
loop
Some other interesting things you can do with memblock images
Parallax starfield
While our art program was very pretty, we can also produce pre-prepared images using memblocks, using the function we just created. This parallax starfield example will produced a “layered” starfield, were more distant stars are darker in colour and closer stars are brighter. I will do this So we’re going to start this program off by defining the globals.
Global width as DWORD
Global height as DWORD
Global bit as DWORD
Now we assign our globals and create our memblock
make memblock from bitmap 1, 0
width = memblock dword(1, 0)
height = memblock dword(1, 4)
bit = memblock dword(1, 8)
Next we draw our starfield by starting double layered a for and loop sequence. Para controls the different “layers” of stars we produce up to 8 levels. C controls the colour of those stars, every time the para loop loops, we increase the star’s brightness by 32 units. But because the RGB range is 0 to 255 not 1 to 256 I’m also subtracting one to bring the total value within that range. Next we start drawing the starfield layer. We do this within the stars loop which we create using a randomized number that can be no less than 128 stars. I then write the memblock data using the XYConvert function. I subtract 1 from width and height, because memblocks go from 0 to whatever, this compensates for that. The rgb value is of course the colour we setup in the para loop.
For para = 1 to 8
c = 32 * para - 1
For stars = 0 to RND(128) + 128
write memblock dword 1, XYConvert(RND(width-1), RND(height-1)), rgb(c, c, c)
Next stars
Next para
Now that our stars have been drawn, we can simply create the bitmap, and end the tutorial. The rest of this program we’ve already gone over, so you’ll now how it all works.
make bitmap from memblock 0, 1
wait key
end
Function XYConvert(a, b)
Offset = 12
a = a * 4
b = b * width * 4
output = Offset + a + b
EndFunction output
Complete code
Global width as DWORD
Global height as DWORD
Global bit as DWORD
make memblock from bitmap 1, 0
width = memblock dword(1, 0)
height = memblock dword(1, 4)
bit = memblock dword(1, 8)
For para = 1 to 8
c = 32 * para - 1
For stars = 0 to RND(128) + 128
write memblock dword 1, XYConvert(RND(width-1), RND(height-1)), rgb(c, c, c)
Next stars
Next para
make bitmap from memblock 0, 1
wait key
end
Function XYConvert(a, b)
Offset = 12
a = a * 4
b = b * width * 4
output = Offset + a + b
EndFunction output
Fractal flower
This is a code snippet I pulled from
HERE and then modified to use our created function. I won’t go into the mathematics myself because that’s a whole different tutorial, but you can use this code to produce something a little more interesting than our basic art program using the memblock’s function we’ve already created, and by replacing the dot command with it.
The complete code
Sync On
Sync : Sync
Global width as DWORD
Global height as DWORD
Global bit as DWORD
c as dword
make memblock from bitmap 1, 0
width = memblock dword(1, 0)
height = memblock dword(1, 4)
bit = memblock dword(1, 8)
time = timer()
for y= 0 to 479
for x= 0 to 639
a#=0.0
c#=0.0
s#=x/100.0-3.0
t#=y/80.0-3.0
n=0
repeat
inc n
h#=s#*s#
k#=t#*t#
c#=s#*(h#-3.0*k#)+0.5
a#=t#*(3.0*h#-k#)
t#=a#
s#=c#
until n=16 or fastabs(c#)>9.0 or fastabs(a#)>9.0
if fastabs(c#)<9.0 or fastabs(a#)<9.0
select n
case 1 : c=rgb(255,255,255) : endcase
case 2 : c=rgb(255,255,0): endcase
case 3 : c=rgb(255,0,255): endcase
case 4 : c=rgb(0,255,255): endcase
case 5 : c=rgb(255,0,0): endcase
case 6 : c=rgb(0,255,0): endcase
case 7 : c=rgb(0,0,255): endcase
case 8 : c=rgb(255,200,0): endcase
case 9 : c=rgb(0,255,200): endcase
case default : c=rgb(0,0,0) : endcase
endselect
`memloc = XYConvert(x, y)
write memblock dword 1, Offset + (x * 4) + (y * width * 4), c
endif
next x
next y
make bitmap from memblock 0, 1
sync
totaltime = timer() - time
Set Window Title str$(totaltime)
wait key
end
function fastabs(n#)
if n# < 0 then n# = -n#
endfunction n#
Function XYConvert(a, b)
Offset = 12
a = a * 4
b = b * width * 4
output = Offset + a + b
EndFunction output