/fortran-sdl2

Fortran 2008 interface bindings to SDL 2.0

Primary LanguageFortranISC LicenseISC

fortran-sdl2

A collection of ISO C binding interfaces to Simple DirectMedia Layer 2.0 (SDL 2.0), for 2D and 3D game programming in Fortran. SDL versions tested against:

Library Version
SDL2 2.28.2
SDL2_image 2.6.3
SDL2_mixer 2.6.3
SDL2_ttf 2.20.2

For bindings to SDL2_gfx, see fortran-sdl2_gfx.

Build Instructions

If not present already, install SDL 2.0 with development headers (and optionally: SDL_image 2.0, SDL_mixer 2.0, and/or SDL_ttf 2.0). On FreeBSD, run:

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

Either use GNU/BSD make, xmake to build fortran-sdl2, or the Fortran Package Manager (fpm).

Make

Run make sdl2 to compile the static library libsdl2.a:

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

On Microsoft Windows, you have to set LIBGL and LIBGLU:

$ make all LIBGL=-lopengl32 LIBGLU=-lglu32

On macOS, replace -lGL -lGLU with -framework OpenGL. You can override the default compiler (gfortran) by passing the FC argument, for example:

$ make all FC=/opt/intel/bin/ifort

Link your Fortran project with libsdl2.a and (optionally) libsdl2_*.a, or simply with libfortran-sdl2.a:

Library Compilation Linking
SDL2 make sdl2 libsdl2.a -lSDL2
SDL2_image make sdl2_image libsdl2.a libsdl2_image.a -lSDL2 -lSDL2_image
SDL2_mixer make sdl2_mixer libsdl2.a libsdl2_mixer.a -lSDL2 -lSDL2_mixer
SDL2_ttf make sdl2_ttf libsdl2.a libsdl2_ttf.a -lSDL2 -lSDL2_ttf
all make all libfortran-sdl2.a -lSDL2 -lSDL2_image -lSDL2_mixer -lSDL2_ttf

xmake

Build all static libraries with:

$ xmake

Build a particular library with:

$ xmake build <name>

The default output directory is build/.

Example

The following example shows how to fill a rectangle, using the hardware renderer.

! example.f90
program main
    use, intrinsic :: iso_c_binding, only: c_associated, c_null_char, c_ptr
    use, intrinsic :: iso_fortran_env, only: stdout => output_unit, stderr => error_unit
    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)  :: rect
    integer         :: rc
    logical         :: is_running

    ! Initialise SDL.
    if (sdl_init(SDL_INIT_VIDEO) < 0) then
        write (stderr, '("SDL Error: ", a)') 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: ", a)') sdl_get_error()
        stop
    end if

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

    ! Set position and size of the rectangle.
    rect = sdl_rect(50, 50, 250, 250)

    ! Event loop.
    is_running = .true.

    do while (is_running)
        ! Catch events.
        do while (sdl_poll_event(event) > 0)
            select case (event%type)
                case (SDL_QUITEVENT)
                    is_running = .false.
            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(127), &
                                       uint8(255), &
                                       uint8(0), &
                                       uint8(SDL_ALPHA_OPAQUE))
        rc = sdl_render_fill_rect(renderer, rect)

        ! 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

To compile the source code with GNU Fortran, run:

$ gfortran `sdl2-config --cflags` -o example example.f90 libsdl2.a `sdl2-config --libs`

Further Examples

Some demo applications can be found in examples/:

  • alpha makes one color of an image transparent (software renderer).
  • cyclic implements a 2D cyclic cellular automaton (hardware renderer).
  • draw draws some shapes (hardware renderer).
  • dvd loads a PNG file with SDL_image and lets it bounce on the screen (hardware renderer).
  • events polls SDL events (software renderer).
  • fire renders the DOOM fire effect (hardware renderer).
  • forest implements a cellular automaton, based on the forest fire model (hardware renderer).
  • gl renders a triangle with OpenGL 1.3.
  • gl3d rotates textured cubes with OpenGL 1.3.
  • glsphere rotates the camera around GLU spheres.
  • image loads and displays an image (software renderer).
  • info prints debug information to console (software renderer).
  • log prints log messages with SDL_Log() (software renderer).
  • logo lets you fly through a field of Fortran logos (hardware renderer).
  • msgbox shows a simple message box (software renderer).
  • opera plays an OGG file with SDL_mixer (software renderer).
  • pixel copies an SDL_Surface to an SDL_Texture pixelwise (hardware renderer).
  • scaling displays a scaled image (software renderer).
  • text outputs text with SDL_ttf (hardware renderer).
  • vertex shows geometry renderer of SDL 2.0.18.
  • voxel renders a voxel space with direct pixel manipulation. Use arrow keys and Q, A for camera movement (hardware renderer).
  • window opens a window and fills rectangles (software renderer).

Compile all examples with:

$ make examples

If you prefer xmake, build and run an example with:

$ xmake build <name>
$ xmake run <name>

To compile all examples, simply run:

$ xmake build examples

Compatibility

All Fortran interface names are written in snake case. For instance, SDL_CreateWindow() can be accessed by calling Fortran interface sdl_create_window(). The same is valid for derived types and their components. Enums and constants have kept their original names.

Null-Termination of Strings

A c_null_char must be appended to all strings passed to the interfaces, except sdl_set_hint() and sdl_log*(), which are wrappers that terminate the arguments for convenience.

SDL_Color

SDL 2.0 stores RGB colour values as Uint8. As Fortran does not feature unsigned types, the intrinsic procedure transfer() has to be used to transfer bit patterns directly. For example:

type(sdl_color) :: color

color = sdl_color(r = transfer([255, 1], 1_c_int8_t)
                  g = transfer([127, 1], 1_c_int8_t)
                  b = transfer([  0, 1], 1_c_int8_t)
                  a = transfer([SDL_ALPHA_OPAQUE, 1], 1_c_int8_t))

The Fortran bindings provide a utility function uint8() that simplifies the conversion:

color = sdl_color(r = uint8(255), &
                  g = uint8(127), &
                  b = uint8(0), &
                  a = uint8(SDL_ALPHA_OPAQUE))

SDL_Surface

C pointers in derived types like sdl_surface must be converted to Fortran types manually by calling the intrinsic procedure c_f_pointer(). For instance, to assign the sdl_pixel_format pointer in sdl_surface:

type(sdl_pixel_format), pointer :: pixel_format
type(sdl_surface),      pointer :: surface

! Convert C pointer to Fortran pointer.
call c_f_pointer(surface%format, pixel_format)

The C struct SDL_Surface stores RGB pixel values as Uint8. Use transfer() and ichar() to convert Uint8 to Fortran signed integer. For example:

integer, parameter              :: X = 10
integer, parameter              :: Y = 20
integer(kind=int8)              :: r, g, b
integer(kind=c_int32_t)         :: pixel
integer(kind=c_int8_t), pointer :: pixels(:)
type(sdl_pixel_format), pointer :: pixel_format
type(sdl_surface),      pointer :: surface

! Load BMP file into SDL_Surface.
surface => sdl_load_bmp('image.bmp' // c_null_char)

! Get SDL_PixelFormat.
call c_f_pointer(surface%format, pixel_format)

! Get Fortran array of pixel pointers.
call c_f_pointer(surface%pixels, pixels, shape=[surface%pitch * surface%h])

! Get single pixel of coordinates X and Y. Convert to Fortran integer.
pixel = ichar(transfer(pixels((Y - 1) * surface%pitch + X), 'a'), kind=c_int32_t)

! Get RGB values of pixel.
call sdl_get_rgb(pixel, pixel_format, r, g, b)

SDL_Log

Only a single string message can be passed to sdl_log() and sdl_log_*() routines, as Fortran does not support ellipsis arguments. For example:

call sdl_log_debug(SDL_LOG_CATEGORY_TEST, 'debug message')

You may have to write your log message to a string first:

character(len=32) :: msg
integer           :: rc

write (msg, '(a, i0)') 'Error: ', rc
call sdl_log(msg)

Log messages will be trimmed and null-terminated by the wrapper routines.

Events

The SDL event SDL_QUIT has been renamed to SDL_QUITEVENT in Fortran to avoid conflict with interface sdl_quit().

Documentation

Generate the source code documentation with FORD. Add FORD with pip, for example:

$ python3 -m venv virtual-environment/
$ source virtual-environment/bin/activate
$ python3 -m pip install ford

Or, instead, just install the package in your user directory:

$ python3 -m pip install --user ford

Then, run:

$ ford project.md -d ./src

Open doc/index.html in a web browser.

Coverage

See coverage for the current status.

Credits

Thanks go to angelog0.

Licence

ISC