WDTUTORIALS
menu
Django - The Easy Way Django - The Easy Way
Samuli Natri 2018.10.08
Samuli Natri is a software developer who enjoys programming games and web applications. He attended Helsinki University Of Technology (Computer Science) and Helsinki University (Social Sciences).

.NET Core + SDL2 : How To Render Images (C# Game Development Tutorial)

How to render images using SDL2_image library, renderer and textures.

Prerequisites

Error Checking

I won't be including any extensive error checking for the sake of brevity. Please do add error checking by following the link above and checking the official SDL2 documentation.

SDL_image library

Install the SDL_image image file loading library. In macOS you can do it with brew:

brew install sdl2_image

Install the C# wrapper:

cd sdl
wget https://github.com/flibitijibibo/SDL2-CS/raw/master/src/SDL2_image.cs

Now you should have this kind of folder structure:

.
├── Hello.csproj
├── Program.cs
├── bin
├── obj
└── sdl
    ├── SDL2.cs
    └── SDL2_image.cs

Renderer and Textures

This is how the rendering works with SDL's 2D rendering API:

  • First we create a Renderer with SDL_CreateRenderer() function. You can think this renderer as a canvas on which painter adds paint.
  • Then we add "paint" to it with SDL_RenderCopy() function. In this tutorial we will only paint the player icon but you could call the function multiple times to add more paint, like an enemy icon.
  • You prepare the "painting" in the backbuffer. When the painting is ready, we display it with SDL_RenderPresent(). You call this function once per frame.
  • You should initialize the backbuffer with SDL_RenderClear() function before you start composing a new painting for the next frame with SDL_RenderCopy(). Do not assume that content will carry on from frame to frame.

Add the following lines after the window creation code:

var renderer = SDL.SDL_CreateRenderer(window, -1, SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED);

SDL_CreateRenderer() creates the rendering context (the "canvas").

  • window is the target window.
  • 2 argument is the index of the rendering driver to initialize. -1 initalizes the first rendering driver that supports the requested flags.
  • If you don't provide any flags, the priority is given to available SDL_RENDERER_ACCELERATED renderers (this means the renderer uses hardware acceleration). You can also define the flags manually like this: SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED.

Load the player texture:

var playerTexture = SDL_image.IMG_LoadTexture(renderer, "Player.png");
SDL.SDL_RenderClear(renderer);

IMG_LoadTexture loads the image directly into a render texture.

SDL_RenderClear() clears the rendering target with the drawing color. We will change the color later in this tutorial.

Paint the player on the canvas:

SDL.SDL_RenderCopy(renderer, playerTexture, IntPtr.Zero, IntPtr.Zero);

SDL_RenderCopy() copies part of a texture to the rendering target.

  • renderer is the rendering context.
  • playerTexture is the source texture.
  • Source rectangle (SDL_Rect). What part of the texture we want to copy? IntPtr.Zero means the entire texture.
  • Target rectangle (SDL_Rect). The position and size of the target rectangle. We leave it IntPtr.Zero for now so the player image will stretch to fill the entire rendering target.

When the "painting" is ready, we display it with SDL_RenderPresent(). This updates the window with the "painting" from the backbuffer:

SDL.SDL_RenderPresent(renderer);

Add SDL_DestroyTexture and SDL_DestroyRenderer functions to the clean up section:

SDL.SDL_DestroyTexture(playerTexture);
SDL.SDL_DestroyRenderer(renderer);
SDL.SDL_DestroyWindow(window);
SDL.SDL_Quit();

Now you should see the player image stretched to fill the whole window surface:

You can add <NoWarn>CS0618</NoWarn> element to the project file if you are getting "..warning CS0618: 'UnmanagedType.Struct' is obsolete.." warning and want to suppress it:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <NoWarn>CS0618</NoWarn> // here
  </PropertyGroup>

</Project>

SDL_Rect

SDL_Rect describes a rectangle. Let's initialize the source (sRect) and target (tRect) structures, and use them with the SDL_RenderCopy function.

SDL.SDL_Rect sRect;
sRect.x = 0;
sRect.y = 0;
sRect.w = 64;
sRect.h = 64;

SDL.SDL_Rect tRect;
tRect.x = 100;
tRect.y = 100;
tRect.w = 64;
tRect.h = 64;

SDL.SDL_RenderCopy(renderer, playerTexture, ref sRect, ref tRect);
//SDL.SDL_RenderCopy(renderer, playerTexture, IntPtr.Zero, IntPtr.Zero);
  • nRect.x is x location of the upper left corner of the rectangle.
  • nRect.y is y location of the upper left corner of the rectangle.
  • nRect.w is the width of the rectangle.
  • nRect.h is the height of the rectangle.

With sRect we determine which part of the player texture we want to copy. We get a 64x64 rectangle that starts from the position (0,0).

With tRect we determine the target position and rectangle area. We position the player icon in the position (100, 100) in a 64x64 rectangle. If you would use different width and height, the player icon would stretch accordingly.

SDL_SetRenderDrawColor

Use SDL_SetRenderDrawColor to set the background color to white:

SDL.SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL.SDL_RenderClear(renderer);

Surfaces and Blitting

The older way to accomplish this is to do software rendering using surfaces and blitting. You can think of the renderer as a surface, SDL_RenderCopy() as the blit function and SDL_RenderPresent() as the SDL_Flip() function.

Source Code

using System;
using SDL2;

namespace Hello
{
    class Program
    {
        static void Main(string[] args)
        {
            // Initialize the library.
            SDL.SDL_Init(SDL.SDL_INIT_VIDEO);

            // Create a window.
            var window = SDL.SDL_CreateWindow("Hello", SDL.SDL_WINDOWPOS_CENTERED,
            SDL.SDL_WINDOWPOS_CENTERED, 1000, 800, 0);

            // Create the renderer. This is the "canvas".
            var renderer = SDL.SDL_CreateRenderer(window, -1, 0);

            // Load the image texture.
            var playerTexture = SDL_image.IMG_LoadTexture(renderer, "Player.png");

            // Set renderer drawing color.
            SDL.SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);

            // Clear the screen.
            SDL.SDL_RenderClear(renderer);

            // Set source rectangle.
            SDL.SDL_Rect sRect;
            sRect.x = 0;
            sRect.y = 0;
            sRect.w = 64;
            sRect.h = 64;
            
            // Set target rectangle.
            SDL.SDL_Rect tRect;
            tRect.x = 100;
            tRect.y = 100;
            tRect.w = 64;
            tRect.h = 64;

            // "Paint" the player to the canvas.
            SDL.SDL_RenderCopy(renderer, playerTexture, ref sRect, ref tRect);
            // SDL.SDL_RenderCopy(renderer, playerTexture, IntPtr.Zero, IntPtr.Zero);

            // Present the "Painting" (backbuffer) to the screen. Call this once per frame.
            SDL.SDL_RenderPresent(renderer);

            // Wait for user to close the window.
            var quit = false;
            while(!quit)
            {
                while(SDL.SDL_PollEvent(out SDL.SDL_Event e) != 0) {
                    switch (e.type)
                    {
                        case SDL.SDL_EventType.SDL_QUIT:
                            quit = true;
                            break;
                
                        case SDL.SDL_EventType.SDL_KEYDOWN:
                            if (e.key.keysym.sym == SDL.SDL_Keycode.SDLK_q) {
                                quit = true;
                                break;
                            }
                            break;
                    }
                }
            }

            // Clean up.
            SDL.SDL_DestroyTexture(playerTexture);
            SDL.SDL_DestroyRenderer(renderer);
            SDL.SDL_DestroyWindow(window);
            SDL.SDL_Quit();
        }
    }
}