Handmade Hero Day 003
NOTE(bk): They notes will be changed. At the moment they are my opinion or interpretation of what Casey says during the video. I will change so that it will be from what he describes in the video, with me making the rare aside note.
Recap of 002
Something to keep in mind is the reason we need the callback function (Win32MainWindowCallback) is because windows reserves the right to call us back at any time.
In our main message loop, after we get a message that is > 0, we first translate the message (TranslateMessage), then we dispatch it (DispathMessageA).
TODO(bk): Find out really what translate and dispatch does. My guess is that dispatch message means to execute on that message, in other words, to call our callback function. But what does translate do?
GetMessage loop
(this was shown in 002)
The idea is that we are looping until GetMessage returns either 0, which is WM_QUIT, or less than zero (-1).
TODO(bk): find out if it can be anything other than -1. In the example they have in the MSDN documentation, they only check for -1.
In this case, -1 means an error which we should then handle for an error and report appropriately.
PostQuitMessage function
VOID WINAPI PostQuitMessage(
_In_ int nExitCode
);
In a game, which hardly if ever interacts with windows, we do not need to set the exit code anything. Perhaps if it failed we could, but is there really value in it?
Perhaps we should to be consistent but it is not like people will need the exit value.
PostQuitMessage - optional
Casey says that there are two ways you can exit a program: either call PostQuitMessage, or exit out of our infinite GetMessage loop.
In our simple example, Casey puts PostQuitMessage(0) in our WM_CLOSE case, which I am guessing will get windows to send the WM_QUIT message.
You can also put this message in WM_DESTROY, but perhaps you should know how you got this message for maybe you received this in an error.
So instead, Casey decides to use a global variable Running, and instead of an for (;;)
for the message loop, we have while (Running)
.
At the moment, we do not handle things such as a warning pop-up box to get confirmation from the user if WM_CLOSE is called, or to handle the error when WM_DESTROY is called.
Acquisition and Release of their resources
This is more a note that Casey brought up. How people coming from Object Oriented world most likely have a symmetrical way of acquiring and releasing resources.
Resource Acquisition is Initialization is the term.
In this example, he pointed out that we do not close the window. In other words, he lets Windows close the window when we quit.
He views RAII to be a very bad idea because he feels that things should be released in aggregate. What he means is instead of thinking of things individually, you always think about them part of a group that gets handle together.
You can’t be myopic and focus at things individually of its creation and destruction and start thinking about creation and destruction in waves.
He makes note that we do not need to worry so much at the moment, but that it will come up more down the line when we have a lot more resources.
NOTE(bk): myopic - nearsighted. Lacking imagination, foresight or intellectual insight.
DestroyWindow function
BOOL WINAPI DestroyWindow(
_In_ HWND hWnd
);
hWnd [In] Type: HWND
A handle to the window to be destroyed.
One way that you can properly close your application is to put the DestroyWindow call within the WM_CLOSE message, and within the WM_DESTROY, that is where you have the PostQuitMessage call.
case WM_CLOSE:
{
DestroyWindow(Window);
} break;
case WM_DESTROY:
{
PostQuitMessage(0);
} break;
static - global versus local_persist versus internal
Depending on what scope you declare something static, actually has a different result of the variable. Due to this, Casey will actually name them specifically:
#define internal static
#define local_persist static
#define global_variable static
One of the reasons he does this is that it makes it easier to search for one that he does not want to go in the shipped code (local_persist).
If you use static to declare a global variable, it is automatically initialized to 0.
If you declare something with static within a local scope, the line will be executed once, and every time after will be skipped.
In handmade hero’s example from Day 002:
static DWORD Operation = WHITENESS;
PatBlt(DeviceContext, X, Y, Width, Height, Operation);
if (Operation == WHITENESS)
{
Operation = BLACKNESS;
}
else
{
Operation = WHITENESS;
}
The third type is when declaring a function. This means that this function is only available from this file. And by file, it means translation unit.
Interaction with GDI
GDI is windows graphical API. In Handmade Hero, since we want to do everything from scratch, we want to interact with GDI as little as possible. So we want to is take a bitmap that we have already rendered using our own code and just display it to our window.
In order to do that, we need to ask windows to allocate a bitmap buffer for us so that we can write into, a format that it is comfortable with, then we will write into it and tell it to display.
Win32ResizeDIBSection function
(Handmade Hero function)
internal void
Win32ResizeDIBSection(int Width, int Height);
DIB = Device Independent Bitmap
This function’s purpose is whenever we need to resize our window (a WM_SIZE message), this function will be called.
For now, we are allowing it to be resized to any size, but later on, we will probably only want a limited range of sizes which depend on the assets that we have been given.
GetClientRect function
BOOL WINAPI GetClientRect(
_In_ HWND hWnd,
_Out_ LPRECT lpRect
);
Gets the size of the client rectangle of the window. In other words, the window size would also include the borders and other stuff that windows controls, so we only want to edit the client portion of the window.
Win32UpdateWindow function
(Handmade Hero function)
internal void
Win32UpdateWindow(HDC DeviceContext, int X, int Y, int Width, int Height);
Casey: This is a rectangle to rectangle copy. We are going to take one size and copy it to another, and scale it along the way if we need to.
Win32UpdateWindow will call StretchDIBits.
left, right, top, bottom versus X, Y, Width, Height
Casey just simply prefers using X, Y, Width, and Height instead of left, right, top, bottom. It is easier for him to remember.
CreateDIBSection function
HBITMAP CreateDIBSection(
_In_ HDC hdc,
_In_ const BITMAPINFO *pbmi,
_In_ UINT iUsage,
_Out_ VOID **ppvBits,
_In_ HANDLE hSection,
_In_ DWORD dwOffset
);
CreateDIBSection Parameters
hSection [In]
Type: HANDLE
Casey: This is if we wanted to do something fancy with our DIBSection, like writing out to a file or something. We don’t care. We just want to allocate one in memory for us. He has actually never used this parameter.
dwOffset [In]
Type: DWORD
This value is ignored if hSection is NULL.
Casey: This parameter references hSection, and since we do not use that, we can ignore this one.
CreateDIBSection Return value
If the function succeeds, the return value is a handle to the newly created DIB, and *ppvBits points to the bitmap bit values.
If the function fails, the return value is NULL, and *ppvBits is NULL.
CreateDIBSection Example
We use CreateDIBSection from within Win32ResizeDIBSection:
global_variable BITMAPINFO BitmapInfo;
global_variable void *BitmapMemory;
global_variable HBITMAP BitmapHandle;
global_variable HDC BitmapDeviceContext;
internal void
Win32ResizeDIBSection(int Width, int Height)
{
// TODO(casey): Bulletproof this.
// Maybe don't free first, free after, then free first if that fails.
if (BitmapHandle)
{
DeleteObject(BitmapHandle);
}
if (!BitmapDeviceContext)
{
// TODO(casey): Should we recreate these under certain special circumstances?
BitmapDeviceContext = CreateCompatibleDC(0);
}
BitmapInfo.bmiHeader.biSize = sizeof(BitmapInfo.bmiHeader);
BitmapInfo.bmiHeader.biWidth = Width;
BitmapInfo.bmiHeader.biHeight = Height;
BitmapInfo.bmiHeader.biPlanes = 1;
BitmapInfo.bmiHeader.biBitCount = 32;
BitmapInfo.bmiHeader.biCompression = BI_RGB;
// TODO(casey): Based on ssylvan's suggestion, maybe we can just
// allocate this ourselves?
BitmapHandle = CreateDIBSection(
BitmapDeviceContext, // HDC hdc,
&BitmapInfo, // BITMAPINFO *pbmi,
DIB_RGB_COLORS, // UINT iUsage,
&BitmapMemory, // VOID **ppvBits,
0, // HANDLE hSection,
0); // DWORD dwOffset);
}
StretchDIBits function
int StretchDIBits(
_In_ HDC hdc,
_In_ int XDest,
_In_ int YDest,
_In_ int nDestWidth,
_In_ int nDestHeight,
_In_ int XSrc,
_In_ int YSrc,
_In_ int nSrcWidth,
_In_ int nSrcHeight,
_In_ const VOID *lpBits,
_In_ const BITMAPINFO *lpBitsInfo,
_In_ UINT iUsage,
_In_ DWORD dwRop
);
Casey: This one takes our DIBSection and it blits it, and it allows it to scale.
StretchDIBits Parameters
iUsage [In]
Type: UINT
Casey: What kind of a picture this is, what kind of buffer you have made. There are two kinds of buffers: one is palletized, and the other is RGB. RGB is kind of straight forward, there are individual bits for each colour so you can specify exactly the colour that you want. A palette is if you would like to be more indirect. Lets say you want to use a limited number of colours, so instead you will have a number that corresponds to a value in a table. This allows you to reduce the size of the type (and thus the buffer) that you are passing around. Not really seen now-a-days. He says that it might come up in texture compression, but we don’t need to deal with that.
Value | Meaning |
---|---|
DIB_PAL_COLORS | The array contains 16-bit indexes into the logical palette of |
the source device context. | |
——————– | ————————————————————— |
DIB_RGB_COLORS | The color table contains literal RGB values. |
——————– | ————————————————————— |
TODO(bk): How do I like the format of the tables?
dwRop [In]
Type: DWORD
A raster-operation code.
Casey: We don’t want it to do anything fancy. We just want it to copy from the source, thus we will just use SRCCOPY.
StretchDIBits Example
We called StretchDIBits from within Win32UpdateWindow:
global_variable BITMAPINFO BitmapInfo;
global_variable void *BitmapMemory;
internal void
Win32UpdateWindow(HDC DeviceContext, int X, int Y, int Width, int Height)
{
StretchDIBits(DeviceContext, // HDC hdc,
X, // int XDest,
Y, // int YDest,
Width, // int nDestWidth,
Height, // int nDestHeight,
X, // int XSrc,
Y, // int YSrc,
Width, // int nSrcWidth,
Height, // int nSrcHeight,
BitmapMemory, // VOID *lpBits,
&BitmapInfo, // BITMAPINFO *lpBitsInfo,
DIB_RGB_COLORS, // UINT iUsage,
SRCCOPY); // DWORD dwRop);
}
BITMAPINFO structure
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO, *PBITMAPINFO;
Casey: The RGBQUAD bmiColors[1] is old school syntax used in C, if you define an array at the end of something, you can make it a variable length. He does not recommend doing that really.
BITMAPINFO Members
bmiHeader
type: BITMAPINFOHEADER
bmiColors[1]
type: RGBQUAD
Casey: This is basically where the palette goes. This is a variable size array that you will put all the colours that you will reference in your buffer. And since we do not plan to reference any colours, we just plan to write the colours directly to the pixels to where they belong, we don’t have to worry about that at all.
BITMAPINFOHEADER structure
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
BITMAPINFOHEADER Members
biSize
Type: DWORD
The number of bytes required by the structure.
Casey: This is referring to the size of the BITMAPINFOHEADER.
biPlanes
Type: WORD
The number of planes for the target device. This value must be set to 1.
Casey: An old school notion when you would specify multiple chunks of memory that get combined to give you the colour. We don’t have to do that anymore, thankfully all of the pixel RGB values are not stored in a red plane, green plane and blue plane. Or where you have one bit per each part of the red value or anything like that. Everything is actually per pixel packed together now-a-days.
biBitCount
Type: WORD
Casey: Is the number of bits per pixel. 24 is actually the number we need, but he’s gonna ask for 32 for other reasons (not explained yet). 8 bits for red, 8 bits for green and 8 bits for blue. He wants 32 for he wants it to be DWORD aligned, but won’t explain that just yet.
biCompression
Type: DWORD
Casey: We do not want this compressed at all. We want to write to it as fast as possible and blit to it as fast as possible. We do not want it to be doing any fancy compression or even thinking about it. So we are just going to use BI_RGB.
biSizeImage
Type: DWORD
The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps.
Casey: This would actually show up if you actually have a compression format for windows wouldn’t know how big the thing was going to be because you were going to do some compression on it.
biXPelsPerMeter
Type: LONG
Casey: This is more if you are going to be using some sort of print output. He is just going to set them to 0. For printing or for when you actually care about the physical size.
biYPelsPerMeter
Type: LONG
Casey: Same as biXPelsPerMeter, simply going to set it to 0.
biClrUsed
Type: DWORD
The number of colour indexes in the colour table that are actually used by the bitmap.
Casey: This is the number of colours used in the colour table and we are not having a colour table.
biClrImportant
Type: DWORD
Casey: Also not needed for we do not have any of the index palette “stuff”.
HBITMAP type
A handle to a bitmap.
This type is declared in WinDef.h
as follows:
typedef void *PVOID; // A pointer to any type. Defined in WinNT.h
typedef PVOID HANDLE; // A handle to an object.
typedef HANDLE HBITMAP; // A handle to a bitmap.
Needing to free memory
Every time we resize the window, we would be allocating memory which would be bad, thus we need to free what was originally allocated in order to allocate more.
He says there are two ways we can do this: we can free first, or we can wait and see if we can get the new one first and if we can’t, we can keep using our old one.
There is a bunch of trade-offs for each one (he does not list them).
DeleteObject function
BOOL DeleteObject(
_In_ HGDIOBJ hObject
);
Casey: GDI is a little wonky, when it creates a bitmap handle, it actually deletes everything through the same call. So you pass bitmap handles to DeleteObject instead of something like DeleteBitmap and it just figures out what type it was.
CreateCompatibleDC function
HDC CreateCompatibleDC(
_In_ HDC hdc
);
Casey: We need something that is compatible with the other thing that we are trying to use. We have something that we want to draw into that we are going to try to blit to the screen, and so what we want to do is create a device context that is compatible with the screen’s device context, even though we are not drawing with the screen’s device context.
CreateCompatibleDC Parameters
hdc [In]
Type: HDC
A handle to an existing DC. If this handle is NULL, the function creates a memory DC compatible with the application’s current screen.
CreateCompatibleDC Return value
If the function succeeds, the return value is the handle to a memory DC.
If the function fails, the return value is NULL.
ReleaseDC
int ReleaseDC(
_In_ HWND hWnd,
_In_ HDC hDC
);
Brian: We ended up not using this. At the moment, he changed the BitmapDeviceContext to a global variable that looks like will get set once. So in the end if the BitmapHandle exists, he will delete it (using DeleteObject), but only if BitmapDeviceContext does not exist will he call CreateCompatibleDC.
He is actually not sure if we don’t need to release it. He uses the example what if someone unplugs the monitor and plugs a new one in, what would happen?
Check if leaking memory
Casey: As a good practice, whenever I am doing something that is going through a lot of memory allocations, I like to bring up the task manager to see if everything is kosher. In this case, check to see if the memory is not increasing (not freeing properly).
003 - Q & A
Not needing to use CreateDIBSection/CreateCompatibleDC
ssylvan, who works at Microsoft, commented that you do not need to use the CreateDIBSection or CreateCompatibleDC functions and simply allocate the array ourselves.
He later goes on to say after looking at the code, that it can take custom arrays as long as it is DWORD aligned. If it is not, it will do a local copy.
Casey will need to test this out, but he views this is kind of neat.
What exactly is a void *
?
Since I know what that is, I am not taking notes. But from this discussion led to a remark from Casey that he does not like passing as a parameter the address of something to change, when in fact you want to return multiple things.
Casey notes that a lot of the Win API seems to pass in an address into a function so that it will get modified. In the case he is referring to, it is because multiple things need to be returned.
The example he used was CreateDIBSection, where it returns the BitmapHandle (HBITMAP), but it also needs to return the BitmapMemory (void*
).
Casey used the example that he would rather do something like this:
struct create_dib_section_result
{
HBITMAP Handle;
void *Memory;
};
create_dib_section_result CreateDIBSection(DeviceContext, BitmapInfo, usage, x, x);
Pre-ANSI C didn’t allow return structs
Sean Barrett remarked that in pre-ANSI C, it did not allow for return structs.
ctime
D:\work\handmade\local\handmade>ctime -stats project\handmade.ctm
project\handmade.ctm Statistics
Total complete timings: 29
Total incomplete timings: 0
Days with timings: 2
Days between first and last timing: 2
Timings marked successful (21):
Slowest: 1.855 seconds
Fastest: 1.046 seconds
Average: 1.245 seconds
Total: 26.154 seconds
Timings marked failed (8):
Slowest: 1.342 seconds
Fastest: 0.968 seconds
Average: 1.121 seconds
Total: 8.969 seconds
All (0.066667 days/bucket):
| * 1.855 seconds
|* *
|* *
|* *
|* *
|* *
|* *
|* *
|* *
|* *
+------------------------------ 0.000 seconds
|* 18
|*
|*
|*
|* *
|* *
|* *
|* *
|* *
|* *
+------------------------------ 0
Recent (1.000000 day/bucket):
| * 1.855 seconds
| * *
| * *
| * *
| * *
| * *
| * *
| * *
| * *
| * *
+------------------------------ 0.000 seconds
| * 18
| *
| *
| *
| * *
| * *
| * *
| * *
| * *
| * *
+------------------------------ 0
Total time spent: 35.123 seconds
Extra
Color Cop
Casey recommends Color Cop, a free utility that looks like it can get the colour of what it selects.