Home > Articles

This chapter is from the book

Basic 2D Graphics

Before you can implement the “generate outputs” phase of the game loop, you need some understanding of how 2D graphics work for games

Most displays in use today—whether televisions, computer monitors, tablets, or smartphones—use raster graphics, which means the display has a two-dimensional grid of picture elements (or pixels). These pixels can individually display different amounts of light as well as different colors. The intensity and color of these pixels combine to create a perception of a continuous image for the viewer. Zooming in on a part of a raster image makes each individual pixel discernable, as you can see in Figure 1.2.

Figure 1.2

Figure 1.2 Zooming in on part of an image shows its distinct pixels

The resolution of a raster display refers to the width and height of the pixel grid. For example, a resolution of 1920×1080, commonly known as 1080p, means that there are 1080 rows of pixels, with each row containing 1920 pixels. Similarly, a resolution of 3840×2160, known as 4K, has 2160 rows with 3840 pixels per row.

Color displays mix colors additively to create a specific hue for each pixel. A common approach is to mix three colors together: red, green, and blue (abbreviated RGB). Different intensities of these RGB colors combine to create a range (or gamut) of colors. Although many modern displays also support color formats other than RGB, most video games output final colors in RGB. Whether or not RGB values convert to something else for display on the monitor is outside the purview of the game programmer.

However, many games internally use a different color representation for much of their graphics computations. For example, many games internally support transparency with an alpha value. The abbreviation RGBA references RGB colors with an additional alpha component. Adding an alpha component allows certain objects in a game, such as windows, to have some amount of transparency. But because few if any displays support transparency, the game ultimately needs to calculate a final RGB color and compute any perceived transparency itself.

The Color Buffer

For a display to show an RGB image, it must know the colors of each pixel. In computer graphics, the color buffer is a location in memory containing the color information for the entire screen. The display can use the color buffer for drawing the contents screen. Think of the color buffer as a two-dimensional array, where each (x, y) index corresponds to a pixel on the screen. In every frame during the “generate outputs” phase of the game loop, the game writes graphical output into the color buffer.

The memory usage of the color buffer depends on the number of bits that represent each pixel, called the color depth. For example, in the common 24-bit color depth, red, green, and blue each use 8 bits. This means there are 224, or 16,777,216, unique colors. If the game also wants to store an 8-bit alpha value, this results in a total of 32 bits for each pixel in the color buffer. A color buffer for a 1080p (1920×1080) target resolution with 32 bits per pixel uses 1920×1080×4 bytes, or approximately 7.9 MB.

Some recent games use 16 bits per RGB component, which increases the number of unique colors. Of course, this doubles the memory usage of the color buffer, up to approximately 16 MB for 1080p. This may seem like an insignificant amount, given that most video cards have several gigabytes of video memory available. But when considering all the other memory usage of a cutting-edge game, 8 MB here and 8 MB there quickly adds up. Although most displays at this writing do not support 16 bits per color, some manufacturers now offer displays that support color depths higher than 8 bits per color.

Given an 8-bit value for a color, there are two ways to reference this value in code. One approach involves simply using an unsigned integer corresponding to the number of bits for each color (or channel). So, for a color depth with 8 bits per channel, each channel has a value between 0 and 255. The alternative approach is to normalize the integer over a decimal range from 0.0 to 1.0.

One advantage of using a decimal range is that a value yields roughly the same color, regardless of the underlying color depth. For example, the normalized RGB value (1.0, 0.0, 0.0) yields pure red whether the maximum value of red is 255 (8 bits per color) or 65,535 (16 bits per color). However, the unsigned integer RGB value (255, 0, 0) yields pure red only if there are 8 bits per color. With 16 bits per color, (255, 0, 0) is nearly black.

Converting between these two representations is straightforward. Given an unsigned integer value, divide it by the maximum unsigned integer value to get the normalized value. Conversely, given a normalized decimal value, multiply it by the maximum unsigned integer value to get an unsigned integer value. For now, you should use unsigned integers because the SDL library expects them.

Double Buffering

As mentioned earlier in this chapter, games update several times per second (at the common rates of 30 and 60 FPS). If a game updates the color buffer at the same rate, this gives the illusion of motion, much the way a flipbook appears to show an object in motion when you flip through the pages.

However, the refresh rate, or the frequency at which the display updates, may be different from the game’s frame rate. For example, most NTSC TV displays have a refresh rate of 59.94 Hz, meaning they refresh very slightly less than 60 times per second. However, some newer computer monitors support a 144 Hz refresh rate, which is more than twice as fast.

Furthermore, no current display technology can instantaneously update the entire screen at once. There always is some update order—whether row by row, column by column, in a checkerboard, and so on. Whatever update pattern the display uses, it takes some fraction of a second for the whole screen to update.

Suppose a game writes to the color buffer, and the display reads from that same color buffer. Because the timing of the game’s frame rate may not directly match the monitor’s refresh rate, it’s very like that the display will read from the color buffer while the game is writing to the buffer. This can be problematic.

For example, suppose the game writes the graphical data for frame A into the color buffer. The display then starts reading from the color buffer to show frame A on the screen. However, before the display finishes drawing frame A onto the screen, the game overwrites the color buffer with the graphical data for frame B. The display ends up showing part of frame A and part of frame B on the screen. Figure 1.3 illustrates this problem, known as screen tearing.

Figure 1.3

Figure 1.3 Simulation of screen tearing with a camera panning to the right

Eliminating screen tearing requires two changes. First, rather than having one color buffer that the game and display must share, you create two separate color buffers. Then the game and display alternate between the color buffers they use every frame. The idea is that with two separate buffers, the game can write to one (the back buffer) and, at the same time, the display can read from the other one (the front buffer). After the frame completes, the game and display swap their buffers. Due to the use of two color buffers, the name for this technique is double buffering.

As a more concrete example, consider the process shown in Figure 1.4. On frame A, the game writes its graphical output to buffer X, and the display draws buffer Y to the screen (which is empty). When this process completes, the game and display swap which buffers they use. Then on frame B, the game draws its graphical output to buffer Y, while the display shows buffer X on screen. On frame C, the game returns to buffer X, and the display returns to buffer Y. This swapping between the two buffers continues until the game program closes.

Figure 1.4

Figure 1.4 Double buffering involves swapping the buffers used by the game and display every frame

However, double buffering by itself does not eliminate screen tearing. Screen tearing still occurs if the display is drawing buffer X when the game wants to start writing to X. This usually happens only if the game is updating too quickly. The solution to this problem is to wait until the display finishes drawing its buffer before swapping. In other words, if the display is still drawing buffer X when the game wants to swap back to buffer X, the game must wait until the display finishes drawing buffer X. Developers call this approach vertical synchronization, or vsync, named after the signal that monitors send when they are about to refresh the screen.

With vertical synchronization, the game might have to occasionally wait for a fraction of a second for the display to be ready. This means that the game loop may not be able to achieve its target frame rate of 30 or 60 FPS exactly. Some players argue that this causes unacceptable stuttering of the frame rate. Thus, the decision on whether to enable vsync varies depending on the game or player. A good idea is to offer vsync as an option in the engine so that you can choose between occasional screen tearing or occasional stuttering.

Recent advances in display technology seek to solve this dilemma with an adaptive refresh rate that varies based on the game. With this approach, rather than the display notifying the game when it refreshes, the game tells the display when to refresh. This way, the game and display are in sync. This provides the best of both worlds as it eliminates both screen tearing and frame rate stuttering. Unfortunately, at this writing, adaptive refresh technology is currently available only on certain high-end computer monitors.

Implementing Basic 2D Graphics

SDL has a simple set of functions for drawing 2D graphics. Because the focus of this chapter is 2D, you can stick with these functions. Starting in Chapter 5, “OpenGL,” you’ll switch to the OpenGL library for graphics, as it supports both 2D and 3D.

Initialization and Shutdown

To use SDL’s graphics code, you need to construct an SDL_Renderer via the SDL_CreateRenderer function. The term renderer generically refers to any system that draws graphics, whether 2D or 3D. Because you need to reference this SDL_Renderer object every time you draw something, first add an mRenderer member variable to Game:

SDL_Renderer* mRenderer;

Next, in Game::Initialize, after creating the window, create the renderer:

mRenderer = SDL_CreateRenderer(
   mWindow, // Window to create renderer for
   -1,      // Usually -1
   SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
);

The first parameter to SDL_CreateRenderer is the pointer to the window (which you saved in mWindow). The second parameter specifies which graphics driver to use; this might be relevant if the game has multiple windows. But with only a single window, the default is -1, which means to let SDL decide. As with the other SDL creation functions, the last parameter is for initialization flags. Here, you choose to use an accelerated renderer (meaning it takes advantage of graphics hardware) and enable vertical synchronization. These two flags are the only flags of note for SDL_CreateRenderer.

As with SDL_CreateWindow, the SDL_CreateRenderer function returns a nullptr if it fails to initialize the renderer. As with initializing SDL, Game::Initialize returns false if the renderer fails to initialize.

To shut down the renderer, simply add a call to SDL_DestroyRenderer in Game::Shutdown:

SDL_DestroyRenderer(mRenderer);

Basic Drawing Setup

At a high level, drawing in any graphics library for games usually involves the following steps:

  1. Clear the back buffer to a color (the game’s current buffer).

  2. Draw the entire game scene.

  3. Swap the front buffer and back buffer.

First, let’s worry about the first and third steps. Because graphics are an output, it makes sense to put graphics drawing code in Game::GenerateOutput.

To clear the back buffer, you first need to specify a color with SDL_SetRenderDrawColor. This function takes in a pointer to the renderer, as well as the four RGBA components (from 0 to 255). For example, to set the color as blue with 100% opacity, use the following:

SDL_SetRenderDrawColor(
   mRenderer,
   0,   // R
   0,   // G
   255, // B
   255  // A
);

Next, call SDL_RenderClear to clear the back buffer to the current draw color:

SDL_RenderClear(mRenderer);

The next step—skipped for now—is to draw the entire game scene.

Finally, to swap the front and back buffers, you call SDL_RenderPresent:

SDL_RenderPresent(mRenderer);

With this code in place, if you now run the game, you’ll see a filled-in blue window, as shown in Figure 1.5.

Figure 1.5

Figure 1.5 Game drawing a blue background

Drawing Walls, a Ball, and a Paddle

This chapter’s game project is a version of the classic video game Pong, where a ball moves around the screen, and the player controls a paddle that can hit the ball. Making a version of Pong is a rite of passage for any aspiring game developer—analogous to making a “Hello World” program when first learning how to program. This section explores drawing rectangles to represent the objects in Pong. Because these are objects in the game world, you draw them in GenerateOuput—after clearing the back buffer but before swapping the front and back buffers.

For drawing filled rectangles, SDL has a SDL_RenderFillRect function. This function takes in an SDL_Rect that represents the bounds of the rectangle and draws a filled-in rectangle using the current draw color. Of course, if you keep the draw color the same as the background, you won’t see any rectangles. You therefore need to change the draw color to white:

SDL_SetRenderDrawColor(mRenderer, 255, 255, 255, 255);

Next, to draw the rectangle, you need to specify dimensions via an SDL_Rect struct. The rectangle has four parameters: the x/y coordinates of the top-left corner of the rectangle onscreen, and the width/height of the rectangle. Keep in mind that in SDL rendering, as in many other 2D graphics libraries, the top-left corner of the screen is (0, 0), positive x is to the right, and positive y is down.

For example, if you want to draw a rectangle at the top of the screen, you can use the following declaration of an SDL_Rect:

SDL_Rect wall{
   0,        // Top left x
   0,        // Top left y
   1024,     // Width
   thickness // Height
};

Here, the x/y coordinates of the top-left corner are (0, 0), meaning the rectangle will be at the top left of the screen. You hard-code the width of the rectangle to 1024, corresponding to the width of the window. (It’s generally frowned upon to assume a fixed window size, as is done here, and you’ll remove this assumption in later chapters.) The thickness variable is const int set to 15, which makes it easy to adjust the thickness of the wall.

Finally, you draw the rectangle with SDL_RenderFillRect, passing in SDL_Rect by pointer:

SDL_RenderFillRect(mRenderer, &wall);

The game then draws a wall in the top part of the screen. You can use similar code to draw the bottom wall and the right wall, only changing the parameters of the SDL_Rect. For example, the bottom wall could have the same rectangle as the top wall except that the top-left y coordinate could be 768 - thickness.

Unfortunately, hard-coding the rectangles for the ball and paddle does not work because both objects will ultimately move in the UpdateGame stage of the loop. Although it makes some sense to represent both the ball and paddle as classes, this discussion doesn’t happen until Chapter 2, “Game Objects and 2D Graphics.” In the meantime, you can just use member variables to store the center positions of both objects and draw their rectangles based on these positions.

First, declare a simple Vector2 struct that has both x and y components:

struct Vector2
{
   float x;
   float y;
};

For now, think of a vector (not a std::vector) as a simple container for coordinates. Chapter 3, “Vectors and Basic Physics,” explores the topic of vectors in much greater detail.

Next, add two Vector2s as member variables to Game—one for the paddle position (mPaddlePos) and one for the ball’s position (mBallPos). The game constructor then initializes these to sensible initial values: the ball position to the center of the screen and the paddle position to the center of the left side of the screen.

Armed with these member variables, you can then draw rectangles for the ball and paddle in GenerateOutput. However, keep in mind that the member variables represent the center points of the paddle and ball, while you define an SDL_Rect in terms of the top-left point. To convert from the center point to the top-left point, you simply subtract half the width/height from the x and y coordinates, respectively. For example, the following rectangle works for the ball:

SDL_Rect ball{
   static_cast<int>(mBallPos.x - thickness/2),
   static_cast<int>(mBallPos.y - thickness/2),
   thickness,
   thickness
};

The static casts here convert mBallPos.x and mBallPos.y from floats into integers (which SDL_Rect uses). In any event, you can make a similar calculation for drawing the paddle, except its width and height are different sizes.

With all these rectangles, the basic game drawing now works, as shown in Figure 1.6. The next step is to implement the UpdateGame phase of the loop, which moves the ball and paddle.

Figure 1.6

Figure 1.6 A game with walls, a paddle, and a ball drawing

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020