SDL

Simple DirectMedia Layer (SDL) is an API for cross-platform low-level access to graphics, audio, and input hardware. The API is written in C, but bindings for various programming languages are available. The fortran-sdl2 library provides ISO C binding interfaces to SDL 2.0 and OpenGL 1.3 for 2D and 3D game programming in Fortran 2008.

Fortran voxel space
Fig. 1: Voxel space engine in Fortran, using SDL 2.0

Installation

Packages of SDL 2.0 are available for all modern Unix-like operating systems. On FreeBSD, simply run:

# pkg install audio/sdl2_mixer devel/sdl20 graphics/sdl2_image graphics/sdl2_ttf

On Linux, you may have to install additional development headers. Clone the fortran-sdl2 repository, and then build the library libsdl2.a or libfortran-sdl2.a:

$ git clone https://github.com/interkosmos/fortran-sdl2
$ cd fortran-sdl2/
$ make

We can override the default compiler (GNU Fortran) by adding parameter FC=<compiler>. Instead of GNU or BSD make, you can instead run xmake:

$ xmake

Hardware Renderer

Calling SDL 2.0 from Fortran does not differ much from the way it is done in C. The Fortran interfaces stay close to the bound C routines. We first have to initialise SDL, create a new window, and, if hardware acceleration is desired, create a renderer. Alternatively, we still have access to the legacy software blitter from SDL 1.2.

The following program draws a coloured rectangle to screen. Additional demo applications are part of the fortran-sdl2 library (fig. 1). In the main loop of the example program, we poll an I/O event that may have occured and then do the actual rendering. Before terminating the application, we have to free all allocated memory. As SDL 2.0 uses unsigned integers to store colours internally, RGB values first have to be converted using the utility function uint8().

! demo.f90
program main
    use, intrinsic :: iso_fortran_env, only: stderr => error_unit, stdout => output_unit
    use, intrinsic :: iso_c_binding, only: c_associated, c_null_char, c_ptr
    use :: sdl2
    implicit none

    integer, parameter :: SCREEN_WIDTH  = 640
    integer, parameter :: SCREEN_HEIGHT = 480

    type(c_ptr)     :: window
    type(c_ptr)     :: renderer
    type(sdl_event) :: event
    type(sdl_rect)  :: r
    integer         :: rc

    ! Initialise SDL.
    if (sdl_init(SDL_INIT_VIDEO) < 0) then
        write (stderr, *) 'SDL Error: ', sdl_get_error()
        stop
    end if

    ! Create the SDL window.
    window = sdl_create_window('Fortran SDL 2.0' // c_null_char, &
                               SDL_WINDOWPOS_UNDEFINED, &
                               SDL_WINDOWPOS_UNDEFINED, &
                               SCREEN_WIDTH, &
                               SCREEN_HEIGHT, &
                               SDL_WINDOW_SHOWN)

    if (.not. c_associated(window)) then
        write (stderr, *) 'SDL Error: ', sdl_get_error()
        stop
    end if

    ! Position and size of the rectangle.
    r = sdl_rect(50, 50, 150, 150)

    ! Create the renderer.
    renderer = sdl_create_renderer(window, -1, 0)

    ! Event loop.
    do
        ! Poll events.
        do while (sdl_poll_event(event) > 0)
            select case (event%type)
                case (SDL_QUITEVENT)
                    exit
            end select
        end do

        ! Fill screen black.
        rc = sdl_set_render_draw_color(renderer, uint8(0), uint8(0), uint8(0), uint8(SDL_ALPHA_OPAQUE))
        rc = sdl_render_clear(renderer)

        ! Fill the rectangle.
        rc = sdl_set_render_draw_color(renderer, uint8(255), uint8(255), uint8(0), uint8(SDL_ALPHA_OPAQUE))
        rc = sdl_render_fill_rect(renderer, r)

        ! Render to screen and wait 20 ms.
        call sdl_render_present(renderer)
        call sdl_delay(20)
    end do

    ! Quit gracefully.
    call sdl_destroy_renderer(renderer)
    call sdl_destroy_window(window)
    call sdl_quit()
end program main

We need to compile the Fortran interface bindings to static library libsdl2.a first, by executing the provided Makefile. Afterwards, compile, link, and run the example program:

$ gfortran10 `sdl2-config --cflags` -o demo demo.f90 libsdl2.a `sdl2-config --libs`
$ ./demo

The tool sdl2-config will return the correct compiler and linker flags with respect to the currently used platform.

OpenGL

Fortran OpenGL triangle
Fig. 2: The triangle rendered with SDL 2.0 and OpenGL in Fortran

In Fortran, we have access to the OpenGL rendering context of SDL 2.0, as an alternative to the quite dated GLUT. Currently, the Fortran interface bindings support only OpenGL immediate mode and a few selected GLU routines. The sample program renders a coloured triangle in orthographic projection (fig. 2):

! gl.f90
program main
    use, intrinsic :: iso_c_binding,   only: c_associated, c_null_char, c_ptr
    use, intrinsic :: iso_fortran_env, only: stderr => error_unit, stdout => output_unit
    use :: sdl2
    implicit none

    integer, parameter :: SCREEN_WIDTH  = 300
    integer, parameter :: SCREEN_HEIGHT = 300

    type(c_ptr)     :: context
    type(c_ptr)     :: window
    type(sdl_event) :: event
    integer         :: rc

    ! Initialise SDL.
    if (sdl_init(SDL_INIT_EVERYTHING) < 0) then
        write (stderr, '(2a)') 'SDL Error: ', sdl_get_error()
        stop
    end if

    ! Enable multisampling for anti-aliasing.
    rc = sdl_gl_set_attribute(SDL_GL_MULTISAMPLEBUFFERS, 1)
    rc = sdl_gl_set_attribute(SDL_GL_MULTISAMPLESAMPLES, 8)

    ! Create the SDL window.
    window = sdl_create_window('Fortran SDL 2.0' // c_null_char, &
                               SDL_WINDOWPOS_UNDEFINED, &
                               SDL_WINDOWPOS_UNDEFINED, &
                               SCREEN_WIDTH, &
                               SCREEN_HEIGHT, &
                               ior(SDL_WINDOW_OPENGL, SDL_WINDOW_SHOWN))

    if (.not. c_associated(window)) then
        write (stderr, '(2a)') 'SDL Error: ', sdl_get_error()
        stop
    end if

    ! Create OpenGL context.
    context = sdl_gl_create_context(window)
    call opengl_init(SCREEN_WIDTH, SCREEN_HEIGHT)

    ! Main loop.
    loop: do
        ! Event handling.
        if (sdl_poll_event(event) > 0) then
            select case (event%type)
                case (SDL_QUITEVENT)
                    exit loop
            end select
        end if

        ! Render scene.
        call opengl_display()
    end do loop

    ! Quit gracefully.
    call sdl_gl_delete_context(context)
    call sdl_destroy_window(window)
    call sdl_quit()
contains
    subroutine opengl_display()
        !! Render routine, called every frame.

        call glclear(ior(GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT)) ! Clear the screen and depth buffer.
        call glloadidentity()                                       ! Reset current model view matrix.

        ! Render a coloured triangle.
        call glbegin(gl_triangles)
            call glcolor3f(1.0, 0.0, 0.0) ! red
            call glvertex2f(-0.8, -0.8)

            call glcolor3f(0.0, 1.0, 0.0) ! green
            call glvertex2f(0.8, -0.8)

            call glcolor3f(0.0, 0.0, 1.0) ! blue
            call glvertex2f(0.0, 0.9)
        call glend()

        ! Flush to screen.
        call sdl_gl_swap_window(window)
    end subroutine opengl_display

    subroutine opengl_init(screen_width, screen_height)
        !! Initialises OpenGL.
        integer, intent(in) :: screen_width, screen_height
        real(kind=8)        :: aspect

        ! Set projection matrix.
        call glmatrixmode(GL_PROJECTION)
        ! Set aspect ratio of 2D orthographic projection.
        aspect = real(screen_width, kind=8) / real(screen_height, kind=8)
        call glortho(-aspect, aspect, -1.0_8, 1.0_8, -1.0_8, 1.0_8)

        ! Set model view matrix.
        call glmatrixmode(GL_MODELVIEW)
        call glenable(GL_DEPTH_TEST)
        call glclearcolor(0.0, 0.0, 0.0, 1.0)
    end subroutine opengl_init
end program main

Addtionally to SDL 2.0, the program has to be linked against -lGL and -lGLU:

$ gfortran10 `sdl2-config --cflags` -o gl gl.f90 libfortran-sdl2.a `sdl2-config --libs` -lGL -lGLU
$ ./gl

See the fortran-sdl2 source code repository for more OpenGL examples.

References