SDL
Simple DirectMedia Layer (SDL) is an API for cross-platform low-level access to graphics, audio, and input hardware. The library is written in C, but bindings for various programming languages are available. Three versions of SDL exist:
- SDL 1.2
- The original SDL library, created by Sam Lantinga in 1998, that provides video, audio, joystick, and CD-ROM access, as well as a threading sub-system. The 2D renderer is software-based only. An OpenGL context is available.
- SDL 2.0
- This version adds a hardware-accelerated renderer for 2D graphics to the library, among other improvements. The API is not backwards-compatible to SDL 1.2.
- SDL 3.0
- The successor to SDL 2.0, currently in development.
For both, SDL 1.2 and 2.0, additional libraries are available that extend the core functionality: SDL_image adds support for multiple image formats, SDL_mixer for sound mixing, or SDL_ttf for TrueType font rendering.
The SDL 77 library is a simple layer around SDL 1.2 that allows 2D graphics rendering, image loading, and audio output from FORTRAN 77. The fortran-sdl2 library includes modern ISO C binding interfaces to SDL 2.0 and OpenGL 1.3 for 2D and 3D game programming in Fortran 2008.
SDL 1.2
The SDL 77 library is compatible to all platforms that have at least a FORTRAN 77 compiler available and are supported by SDL 1.2. Common Fortran/C calling conventions are used for mixed-language programming, which are followed by most Fortran compilers. The packages SDL 1.2, SDL_image 1.2, and SDL_mixer 1.2 have to be present. On FreeBSD, install them with:
# pkg install audio/sdl_mixer devel/sdl12 graphics/sdl_image
On Linux, additional development headers are required as well. Just clone the Git repository and execute the included Makefile:
$ git clone https://github.com/interkosmos/sdl77
$ cd sdl77/
$ make
This outputs the static library libSDL77.a
. Link your Fortran
application against libSDL77.a -lSDL -lSDL_image -lSDL_mixer
. If
preferred, the SDL 77 library may be built without dependencies to SDL_image
and/or SDL_mixer.
- Fig. 1: SDL 1.2 software rendering from Fortran
Software Renderer
We can call the SDL 77 library procedures either from FORTRAN 77 or
Fortran ≥ 90. The external subroutines should be imported with the
external
statement first, functions with their return type.
Character strings passed to SDL 77 procedures must be null-terminated, using
achar(0)
or c_null_char
from the intrinsic module
iso_c_binding
.
The following example in Fortran 2018 fills rectangles at random position and in random colour on screen (fig. 1). Press the escape key to quit the program.
! rect.f90
program rect
implicit none (type, external)
! Import SDL 77 procedures.
external :: gclose, gcur, gdelay, gevent, gflush, gopen
integer :: gkey
! SDL 1.2 events and key codes.
integer, parameter :: EVENT_QUIT = 12
integer, parameter :: KEY_ESC = 27
! Minimum and maximum rectangle sizes.
integer, parameter :: MAX_W = 200
integer, parameter :: MAX_H = 200
integer, parameter :: MIN_W = 10
integer, parameter :: MIN_H = 10
! Frame delay in msec. and number of rectangles per frame.
integer, parameter :: DELAY = 20
integer, parameter :: NRECTS = 5
! Window size.
integer, parameter :: WIN_WIDTH = 640
integer, parameter :: WIN_HEIGHT = 480
integer :: event, i, stat
logical :: done = .false.
! Seed PRNG, open SDL 1.2 window and disable cursor.
call random_seed()
call gopen(WIN_WIDTH, WIN_HEIGHT, 'SDL 77' // achar(0), stat)
if (stat /= 0) stop 'Error: failed to open SDL 1.2 window'
call gcur(0)
! Main loop.
do while (.not. done)
! Event loop.
do
call gevent(event, stat)
if (event == EVENT_QUIT) done = .true.
if (stat == 0) exit
end do
! Check for keyboard input.
if (gkey(KEY_ESC) == 1) done = .true.
! Draw random rectangles.
do i = 1, NRECTS
call draw_rect()
end do
! Copy screen layer to screen.
call gflush()
call gdelay(delay)
end do
! Clean up and quit.
call gclose()
contains
subroutine draw_rect()
external :: gcolor, gfillr
integer :: x, y, w, h
integer :: r, g, b
real :: p(7)
call random_number(p)
w = MIN_W + nint(p(1) * (MAX_W - MIN_W))
h = MIN_H + nint(p(2) * (MAX_H - MIN_H))
x = nint(p(3) * (WIN_WIDTH - w))
y = nint(p(4) * (WIN_HEIGHT - h))
r = nint(p(5) * 255)
g = nint(p(6) * 255)
b = nint(p(7) * 255)
call gcolor(r, g, b)
call gfillr(x, y, w, h)
end subroutine draw_rect
end program rect
Compile and link the example program with GNU Fortran by running:
$ gfortran13 -o rect rect.f90 libSDL77.a -lSDL -lSDL_image -lSDL_mixer
If SDL 77 has been built without dependencies to SDL_image and
SDL_mixer, it is sufficient to link with -lSDL
only. The
command-line tool sdl-config(1) returns the platform-specific compiler
flags and linker arguments:
$ gfortran13 `sdl-config --cflags` -o rect rect.f90 libSDL77.a `sdl-config --libs` -lSDL_image -lSDL_mixer
$ ./rect
SDL 2.0
The SDL 2.0 library requires different packages than SDL 1.2. Both can be installed at the same time. Packages of SDL 2.0 are available for all modern Unix-like operating systems. On FreeBSD, run:
# pkg install audio/sdl2_mixer devel/sdl20 graphics/sdl2_image graphics/sdl2_ttf
On Linux, additional development headers have to be installed again. 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 may want to
run xmake:
$ xmake
There is also rudimentary support for the Fortran Package Manager:
$ fpm build --profile release
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 known from SDL 1.2.
- Fig. 2: Software-based voxel space engine in Fortran, using SDL 2.0
The following program draws a coloured rectangle to screen. Additional demo
applications are part of the fortran-sdl2 library (fig. 2). In the
main loop of the example program, we first poll for I/O events which may have
occured, followed by the scene rendering. Before terminating the application, we
have to free all allocated memory. As SDL 2.0 uses unsigned integers to store
colours internally, all RGB values have to be converted from signed 4-byte
integer to unsigned 1-byte integer using the utility function
uint8()
.
! demo.f90
program main
use, intrinsic :: iso_c_binding
use, intrinsic :: iso_fortran_env, only: stderr => error_unit, stdout => output_unit
use :: sdl2
implicit none
integer, parameter :: SCREEN_WIDTH = 640
integer, parameter :: SCREEN_HEIGHT = 480
integer :: rc
type(c_ptr) :: renderer
type(c_ptr) :: window
type(sdl_event) :: event
type(sdl_rect) :: r
! 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(title = 'Fortran SDL 2.0' // c_null_char, &
x = SDL_WINDOWPOS_UNDEFINED, &
y = SDL_WINDOWPOS_UNDEFINED, &
w = SCREEN_WIDTH, &
h = SCREEN_HEIGHT, &
flags = SDL_WINDOW_SHOWN)
if (.not. c_associated(window)) then
write (stderr, '("SDL Error: ", a)') 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
To compile, link, and run the example program, execute:
$ gfortran13 `sdl2-config --cflags` -o demo demo.f90 libsdl2.a `sdl2-config --libs`
$ ./demo
The utility program sdl2-config(1) will return the correct compiler and linker flags with respect to the currently used platform.
- Fig. 3: Basic 3D rendering in Fortran with SDL 2.0 and OpenGL
OpenGL
The SDL 2.0 library offers an OpenGL rendering context that can be accessed through the Fortran interface bindings (fig. 3), as an alternative to the slightly dated GLUT. At the current stage, the bindings support only OpenGL immediate mode and a few selected GLU routines. The following example program renders a coloured triangle in orthographic projection (fig. 4):
! gl.f90
program main
use, intrinsic :: iso_c_binding
use, intrinsic :: iso_fortran_env, only: r8 => real64, stderr => error_unit, stdout => output_unit
use :: sdl2
implicit none
integer, parameter :: SCREEN_WIDTH = 300
integer, parameter :: SCREEN_HEIGHT = 300
integer :: flags, rc
type(c_ptr) :: context
type(c_ptr) :: window
type(sdl_event) :: event
! Initialise SDL.
if (sdl_init(SDL_INIT_EVERYTHING) < 0) then
write (stderr, '("SDL Error: ", a)') 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.
flags = ior(SDL_WINDOW_OPENGL, SDL_WINDOW_SHOWN)
window = sdl_create_window(title = 'Fortran SDL 2.0' // c_null_char, &
x = SDL_WINDOWPOS_UNDEFINED, &
y = SDL_WINDOWPOS_UNDEFINED, &
w = SCREEN_WIDTH, &
h = SCREEN_HEIGHT, &
flags = flags)
if (.not. c_associated(window)) then
write (stderr, '("SDL Error: ", a)') sdl_get_error()
stop
end if
! Create OpenGL context.
context = sdl_gl_create_context(window)
call opengl_init(SCREEN_WIDTH, SCREEN_HEIGHT)
! Main loop.
main_loop: do
! Event handling.
if (sdl_poll_event(event) > 0) then
select case (event%type)
case (SDL_QUITEVENT)
exit main_loop
end select
end if
! Render scene.
call opengl_display()
end do main_loop
! Quit gracefully.
call sdl_gl_delete_context(context)
call sdl_destroy_window(window)
call sdl_quit()
contains
subroutine opengl_display()
!! The rendering 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
integer, intent(in) :: screen_height
real(kind=r8) :: aspect
! Set projection matrix.
call glMatrixMode(GL_PROJECTION)
! Set aspect ratio of 2D orthographic projection.
aspect = real(screen_width, kind=r8) / real(screen_height, kind=r8)
call glOrtho(-aspect, aspect, -1.0_r8, 1.0_r8, -1.0_r8, 1.0_r8)
! 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
Additionally to SDL 2.0, the program has to be linked against
-lGL -lGLU
on Linux and Unix, -framework OpenGL
on
macOS, or -lopengl32 -lglu32
on Microsoft Windows:
$ gfortran13 `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.
- Fig. 4: The triangle rendered with SDL 2.0 and OpenGL in Fortran
Fortran Libraries
- SDL 77: C library for access to SDL 1.2 from FORTRAN 77
- fortran-sdl2: Fortran 2008 interface bindings to SDL 2.0
References
- Simple DirectMedia Layer: Official website
- SDL 1.2 documentation
- SDL 2.0 documentation
< OpenGL | [Index] | raylib > |