In the previous tutorial, we looked at creating a render buffer. In his tutorial, we will look at resizing our window and scaling up our buffer.
When we resize our window, including when we make it fullscreen, we want to resize our buffer so that it is scaled up to fill as much of the window as possible. Remeber that we are software-rendering here. Therefore, our rendering process is significantly slower than it would be if we were using a hardware accelerated API like OpenGL or DirectX (although, then we wouldn't necessarily learn as much). This means that we want to render as few pixels to the buffer as possible, as rendering to the buffer can take a long time. The most efficient thing to do here, is to render to a fixed-sized buffer, and to then have the StretchDIBits function scale up our buffer as much as possible.
We can have the StretchDIBits function scale up our buffer, simply by changing the on-screen width and height (the fourth and fifth parameters). The rest is handled automatically, and is a lot faster than manually allocating more memory for our larger buffer and rendering to it. We should only ever multiply our buffer by an integer scale, as, while it is possible to scale the buffer by a fractional amount, it will create an annoying flickering effect, which we would like to avoid. The disadvantage of using integer scales only is that we are left with a grey border around our buffer, but I feel this is better than having flickering on-screen.
We can handle our window resizing by handling the WM_SIZE message in our window procedure. Note that the WM_SIZE message is also called when the window is created. We want to first get the new size of the window's client area. Then, we want to calculate the largest possible integer our renderBuffer can be scaled by, for which it still fits inside said client area. When we draw the StretchDIBits function, we want to centre our buffer in the client area, and then increase its width and height by our maximum scale.
To make our changes, we need to add three new properties to our structure. All three are integers. The first is the scale by which to multiply the width and height of our buffer. We will call this "scale". The second is the window client area width, which we will call "windowClientWidth". The last is the window client area height, which we will call "windowClientHeight".
We can handle window resizing with a WM_SIZE message. Inside our window procedure, inside the "switch(messageID){..." statement, we will want to add a case for the WM_SIZE event. I will add this below the WM_DESTROY event case, but you could order them either way around. We could add this case with the line "case WM_SZE:{".
We now need to get the window client area size. We can create a RECT (rectangle) structure to store the client area. I will call this structure "windowClientRect". We can then get the size of the client rect with the function GetClientRect. GetClientRect takes two arguments. The first is the window handle. We can substitute in the window handle we passed into our window procedure (which we called "windowHandle") here. The second is the pointer to the RECT structure.
Now we want to set the renderBuffer properties. We can set "renderBuffer.windowClientWidth" to "windowClientRect.right - windowClientRect.left". We can set "renderBuffer.windowClientHeight" to "windowClientRect.bottom - windowClientRect.top".
Next, we need to calculate the maximum scale we can multiply our renderBuffer by, for which it will still fit within the window's client area. We can use a while loop for this. First, declare an integer, i, and set it to 1 (we do not want our scale to be less than 1). We want our loop to increment i so long as our buffer width multiplied by i is smaller than the client area width, and our buffer height multiplied by i is smaller than the client area height. We can accomplish this with the condition "while(BUFFER_WIDTH * i <= renderBuffer.windowClientWidth && BUFFER_HEIGHT * i <= renderBuffer.windowClientHeight){". Inside our loop, we want to increas i by 1, using the line "i += 1;". Since i is now the smallest integer scale for which our scaled buffer is too large to fit within the window client area, we want to subtract 1 from i, so that it becomes the largest integer value for which the scaled buffer can fit within the client area.
Once we have the maximum scale, i, we want to set our renderBuffer.scale property to it. We can do this with the line "renderBuffer.scale = i;". Now we can finish this WM_SIZE case, so be sure to add the "break;" line at the end of the case block.
We can now edit our StretchDIBits function. The second argument of StretchDIBits is the starting x coordinate on the client area. This is currently set to 0, indicating the leftmost side of the screen. We want our buffer to be in the centre of the screen. Therefore, we want to pass in the value "renderBuffer.windowClientWidth / 2 - (BUFFER_WIDTH * renderBuffer.scale) / 2". This is because "renderBuffer.windowClientWidth / 2" is the x coordinate of the centre of the window client area. We then want to move our buffer leftwards by half of its width to position it exactly in the centre, hence why "(BUFFER_WIDTH * renderBuffer.scale) / 2" is subtracted. For similar reasons, we will set the third parameter to "renderBuffer.windowClientHeight / 2 - (renderBuffer.scale * BUFFER_HEIGHT) / 2". The fourth and fifth arguments are the width and height of the buffer on screen. We can set these to "BUFFER_WIDTH * renderBuffer.scale" and "BUFFER_HEIGHT * renderBuffer.scale" respectively.
This finishes our theory for this section. As with the previous tutorials, the code for this section is shown below.
Here is the code required to scale and centre a render buffer on screen. You could add the previously mentioned changes to your code from the previous tutorial, but, if you have the time, I would recommend writing the program out from scratch as it may help you understand and memorise the code for this process. Writing it out again may also help you avoid bugs where you may have forgotten to change a section of code.
If you are using MinGW, you can compile this code with "gcc main.c -lgdi32 -o app". If you are using Visual Studio, be sure to link the gdi32 library or else this code will not work! (I believe you can do this using: #pragma comment(lib, "gdi32"))
main.c
Running the code should first produce a black screen. This is our render buffer. We should be able to resize this window and have our buffer always be visisble in the centre. If we make our window large enough to contain a buffer twice as big, we should see our buffer scale.
Here is the initial output:
Here is the buffer when made fullscreen:
That's all for this tutorial. In the next tutorial, we will look at drawing rectangles on our buffer.