Viewports

Last updated on November 20th, 2021

SDL2 allows for viewports. Have a look at the following screenshot of a SDL2 game (Battle for Wesnoth).

Modified screenshot of Battle of Wesnoth. (Original source image, as found in the Wesnoth Wiki by Wesnoth developers. Image license: GFDL.)

This is a classical situation to use viewports. The game screen is clearly parted into three distinguished areas. The main screen is the large part left with the mountains and the castles. Then there is the minimap in the right-upper corner. And a statistics overview under the minimap. These areas and the corresponding viewports are highlightened in the following screenshot.

Modified screenshot of Battle of Wesnoth. (Original source image, as found in the Wesnoth Wiki by Wesnoth developers. Image license: GFDL.)

The advantage of viewports is that each of them behaves like an own window, so if you draw to the right outside of viewport 1 in the screenshot above, the texture will just be clipped and there is no overlap into viewport 2 oder 3.

SDL2 window and viewport coordinates diagram
The relation of your screen, a SDL2 window and a viewport within this window are outlined here.

Let’s have a look at the code.

program SDL2_Viewport;

uses SDL2;

const
  Viewport1: TSDL_Rect = (x: 0;   y:   0; w: 400; h: 500);
  Viewport2: TSDL_Rect = (x: 400; y:   0; w: 100; h: 300);
  Viewport3: TSDL_Rect = (x: 400; y: 300; w: 100; h: 200);

  BlackDot:  TSDL_Rect = (x: 10;  y:  10; w:   3; h:   3);

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;

begin

  //initilization of video subsystem
  if SDL_Init(SDL_INIT_VIDEO) < 0 then Halt;

  SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN, @sdlWindow1, @sdlRenderer);
  if (sdlWindow1 = nil) or (sdlRenderer = nil) then Halt;

  // fill every viewport with background color and draw a black dot into it
  SDL_RenderSetViewport(sdlRenderer, @Viewport1);
  SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderFillRect(sdlRenderer, nil);
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderFillRect(sdlRenderer, @BlackDot);

  SDL_RenderSetViewport(sdlRenderer, @Viewport2);
  SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderFillRect(sdlRenderer, nil);
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderFillRect(sdlRenderer, @BlackDot);

  SDL_RenderSetViewport(sdlRenderer, @Viewport3);
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderFillRect(sdlRenderer, nil);
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderFillRect(sdlRenderer, @BlackDot);

  // render to window for 2 seconds
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(2000);

  // clear memory
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //shutting down video subsystem
  SDL_Quit;

end.  

The result will look like this:

Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
There are three viewports with different background colors and a black dot in the left upper corner.

First we set up some SDL2 rectangles by

const
  Viewport1: TSDL_Rect = (x: 0;   y:   0; w: 400; h: 500);
  Viewport2: TSDL_Rect = (x: 400; y:   0; w: 100; h: 300);
  Viewport3: TSDL_Rect = (x: 400; y: 300; w: 100; h: 200);

  BlackDot:  TSDL_Rect = (x: 10;  y:  10; w:   3; h:   3);

“Viewport1” represents the red viewport (left), “Viewport2” the yellow (upper-right) and “Viewport3” the green (lower-right) viewport in the result image.

Notice how we just prepare one “BlackDot” rectangle for a black dot of 3×3 px dimension at location (10/10).

After setting up SDL2, a renderer and a window as known, we start to set up the first (red, left) viewport.

  // fill every viewport with background color and draw a black dot into it
  SDL_RenderSetViewport(sdlRenderer, @Viewport1);
  SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderFillRect(sdlRenderer, nil);
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderFillRect(sdlRenderer, @BlackDot);

It is simple as that. Use the function SDL_RenderSetViewport(renderer, rectangle pointer) to set up a viewport. This function returns 0 on success or -1 on failure.

SDL_RenderSetViewport(renderer: PSDL_Renderer; const rect: PSDL_Rect)

After we set up the viewport, we set the draw color by SDL_SetRenderDrawColor(renderer, red, green, blue, alpha) to red (255/0/0/no transparency). Then we use SDL_RenderFillRect(renderer, rectangle pointer) to fill the whole viewport by not specifying a rectangle (nil). Both functions are known from a previous chapter.

Then the color is set to black and a tiny 3×3 rectangle is drawn at location (10/10).

This procedure is repeated for the other two viewports. Notice again, how we use the same rectangle for the black dot though and where it is shown in the result image. The black dot is always drawn at location (10/10) relative to the respective viewport’s location!

As general rule it applies:

The coordinates are always relative to the currently set viewport.

Well, the remaining parts of the code provides nothing new, just the rendering for 2 seconds and some clean up.

Let’s close with some helpful remarks.

No SDL_RenderClear for Viewports!

Do not use SDL_RenderClear(renderer). It will ignore the viewports and clear the whole window with the set drawing color.

Resetting the Viewport

The resetting is done simple by SDL_RenderSetViewport(renderer, nil) as one would expect.

← previous Chapter | next Chapter →

One thought on “Viewports

Leave a Reply

Your email address will not be published. Required fields are marked *