!> Fortran interfaces to the C++ VSG layer (src/vsg/vkf_layer.cpp, with
!> src/vsg/vkf_rocky.cpp for Rocky maps and src/vsg/vkf_gui.cpp for GUI panels),
!> and a few helpers that fill the layer's buffers in place.
!>
!> Objects are opaque `type(c_ptr)` handles owned by the layer; release each
!> one with its `*_delete` routine. Strings cross as (pointer, length).
!> Functions returning `integer(c_int)` return 0 on success and -1 on failure
!> unless documented otherwise; those returning a handle return null on failure.
module vkf_layer

    use, intrinsic :: iso_c_binding

    implicit none

    private

    public :: vkf_vulkan_device_name, vkf_driver_preload
    public :: vkf_version, vkf_viewer_device_info, vkf_viewer_stats, vkf_viewer_scene_counts
    public :: vkf_cache_directory, vkf_cache_clear
    public :: vkf_viewer_create, vkf_viewer_delete, vkf_viewer_look_at, vkf_viewer_perspective
    public :: vkf_viewer_set_pixel_ratio
    public :: vkf_viewer_get_look_at, vkf_viewer_set_view_transform, vkf_viewer_orthographic
    public :: vkf_viewer_set_background
    public :: vkf_viewer_add_node, vkf_viewer_frame, vkf_viewer_capture
    public :: vkf_viewer_record_start, vkf_viewer_record_stop, vkf_viewer_record_frames
    public :: vkf_viewer_set_camera_controls
    public :: vkf_viewer_mouse, vkf_viewer_scroll, vkf_viewer_key, vkf_viewer_resize, vkf_viewer_close
    public :: vkf_viewer_set_grab
    public :: vkf_viewer_events, vkf_viewer_size, vkf_viewer_pixel_ratio
    public :: vkf_viewer_frame_begin, vkf_viewer_frame_draw
    public :: check_status
    public :: vkf_viewer_gui, vkf_gui_begin, vkf_gui_end, vkf_gui_text, vkf_gui_button, vkf_gui_checkbox, vkf_gui_slider, &
              vkf_gui_separator, vkf_gui_same_line, vkf_gui_combo
    public :: vkf_node_delete, vkf_node_add_child, vkf_node_remove_child, vkf_node_bound, vkf_compile_node, vkf_viewer_any
    public :: vkf_switch_create, vkf_switch_set
    public :: vkf_transform_create, vkf_transform_set_matrix
    public :: vkf_image_read, vkf_image_delete, vkf_model_read
    public :: vkf_mesh_set_bound
    public :: vkf_mesh_create, vkf_mesh_array, vkf_mesh_material, vkf_mesh_material_changed, vkf_mesh_vertices_changed, &
              vkf_mesh_set_twilight
    public :: vkf_light_create, vkf_light_set, vkf_viewer_light, vkf_viewer_light_enable
    public :: vkf_font_read, vkf_font_delete, vkf_label_create, vkf_label_set_text, vkf_label_set_color
    public :: vkf_label_set_layout, vkf_label_set_on_top, vkf_label_set_at_infinity
    public :: vkf_viewer_hud_add, vkf_viewer_hud_remove, vkf_viewer_remove_node
    public :: vkf_line_create, vkf_line_points, vkf_line_style, vkf_line_points_changed, vkf_line_style_changed
    public :: vkf_line_set_range, vkf_line_set_lod, vkf_marker_create, vkf_line_create_colored, vkf_line_colors
    public :: vkf_ribbon_create, vkf_line_set_highlight, vkf_line_set_trail, vkf_line_set_dash_mode
    public :: vkf_stars_create, vkf_sky_lines_create, vkf_sky_texture_create, vkf_sky_stars, vkf_sky_stars_changed
    public :: vkf_sky_style, vkf_sky_style_changed
    public :: vulkan_device_name, line_set_points, line_set_style, line_set_pattern, line_set_segments, marker_set_style
    public :: mesh_arrays
    public :: vkf_rocky_available, vkf_rocky_map_create, vkf_rocky_map_settings, vkf_rocky_map_radius, vkf_rocky_map_alive
    public :: vkf_rocky_add_image, vkf_rocky_add_elevation, vkf_rocky_elevation
    public :: mesh_set_material
    public :: vkf_sphere_lod_create, vkf_sphere_lod_add, vkf_sphere_lod_requested

    interface

        !> A version string into `buf` (blank-padded): which = 1 VSG, 2 vsgXchange, 3 Dear ImGui, 4 the Vulkan
        !> headers vkframes was built with, 5 the Vulkan loader's instance version. Returns its length, or -1.
        integer(c_int) function vkf_version(which, buf, buflen) bind(C, name="vkf_version")
            import :: c_int, c_char
            implicit none
            integer(c_int), value :: which                  !! 1 to 5 (see above)
            character(kind=c_char), intent(out) :: buf(*)
            integer(c_int), value :: buflen                 !! the length of `buf`
        end function vkf_version

        !> The viewer's Vulkan device into `buf` (blank-padded): which = 1 its name, 2 its type, 3 the Vulkan
        !> version it supports, 4 its driver's name, 5 its driver's information. Returns the length, or -1.
        integer(c_int) function vkf_viewer_device_info(viewer, which, buf, buflen) bind(C, name="vkf_viewer_device_info")
            import :: c_ptr, c_int, c_char
            implicit none
            type(c_ptr), value :: viewer                    !! viewer handle (vkf_viewer_create)
            integer(c_int), value :: which                  !! 1 to 5 (see above)
            character(kind=c_char), intent(out) :: buf(*)
            integer(c_int), value :: buflen                 !! the length of `buf`
        end function vkf_viewer_device_info

        !> The viewer's last frame drawn, into stats(1:n) (n at most 9): frames drawn, milliseconds waiting for a
        !> swapchain image, recording and submitting, presenting, the GPU's time (-1: not measured), the
        !> framebuffer's width and height, the device memory in use and its budget (bytes; -1: unknown).
        !> Returns 0, or -1.
        integer(c_int) function vkf_viewer_stats(viewer, stats, n) bind(C, name="vkf_viewer_stats")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: viewer                    !! viewer handle (vkf_viewer_create)
            real(c_double), intent(out) :: stats(*)
            integer(c_int), value :: n                      !! the size of `stats`
        end function vkf_viewer_stats

        !> The size of the viewer's scene graph into counts: nodes, draw commands, vertices (traverses it all).
        !> Returns 0, or -1.
        integer(c_int) function vkf_viewer_scene_counts(viewer, counts) bind(C, name="vkf_viewer_scene_counts")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: viewer                    !! viewer handle (vkf_viewer_create)
            real(c_double), intent(out) :: counts(3)
        end function vkf_viewer_scene_counts

        !> Name of the first Vulkan physical device, into `buf`; returns its length, or -1 on failure.
        !> Needs no window, so it checks that the Vulkan loader finds a driver (MoltenVK on macOS).
        integer(c_int) function vkf_vulkan_device_name(buf, buflen) bind(C, name="vkf_vulkan_device_name")
            import :: c_int, c_char
            implicit none
            character(kind=c_char), dimension(*), intent(out) :: buf  !! the name (not null-terminated)
            integer(c_int), value :: buflen                           !! the length of `buf`
        end function vkf_vulkan_device_name

        !> The cache directory (src/vsg/vkf_cache.h: VKFRAMES_CACHE_DIR, or the user's cache directory;
        !> created if it doesn't exist) into `buf` (not null-terminated); returns its length (0: none, or
        !> it can't be created), or -1 if `buf` is too short.
        integer(c_int) function vkf_cache_directory(buf, buflen) bind(C, name="vkf_cache_directory")
            import :: c_int, c_char
            implicit none
            character(kind=c_char), dimension(*), intent(out) :: buf  !! the directory (not null-terminated)
            integer(c_int), value :: buflen                           !! the length of `buf`
        end function vkf_cache_directory

        !> Delete the cache's files (converted fonts, compiled shaders; nothing else), and its fonts and
        !> shaders directories if left empty (not the cache directory). Returns the number of files
        !> removed, or -1 if some couldn't be.
        integer(c_int) function vkf_cache_clear() bind(C, name="vkf_cache_clear")
            import :: c_int
            implicit none
        end function vkf_cache_clear

        !> Start loading the Vulkan driver, on a thread of its own, for the first window (see
        !> DriverPreload in vkf_layer.cpp): called when a scene is set up, before its window opens.
        subroutine vkf_driver_preload() bind(C, name="vkf_driver_preload")
            implicit none
        end subroutine vkf_driver_preload

        !> A host's window now has `ratio` framebuffer pixels per window unit.
        !> (Qt's devicePixelRatio changed: moved to a screen of another scale, or the display's scaling
        !> changed.) Sizes in window units (line widths, markers, labels) are scaled by it; the host also
        !> resizes (vkf_viewer_resize) to the new framebuffer size. Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_set_pixel_ratio(viewer, ratio) bind(C, name="vkf_viewer_set_pixel_ratio")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: viewer     !! viewer handle (vkf_viewer_create)
            real(c_double), value :: ratio   !! framebuffer pixels per window unit (> 0)
        end function vkf_viewer_set_pixel_ratio

        !> A viewer with one window, an empty scene and a trackball camera; null on failure.
        !> With an existing window (`native_window` not 0) the host owns that window's events and
        !> forwards them (vkf_viewer_mouse, ..., vkf_viewer_resize).
        type(c_ptr) function vkf_viewer_create(width, height, title, title_len, native_window, pixel_ratio, vsync) &
                bind(C, name="vkf_viewer_create")
            import :: c_ptr, c_int, c_char, c_long_long, c_double
            implicit none
            integer(c_int), value :: width, height                   !! window size (window units: logical pixels)
            character(kind=c_char), dimension(*), intent(in) :: title !! window title
            integer(c_int), value :: title_len                        !! the length of `title`
            integer(c_long_long), value :: native_window  !! 0 for a window of its own, or the native handle of
                                                          !! an existing window to render into (e.g. a Qt
                                                          !! QWindow's winId(): an NSView* on macOS, an HWND on
                                                          !! Windows, an xcb window on Linux)
            real(c_double), value :: pixel_ratio  !! framebuffer pixels per window unit of an existing window
                                                  !! (Qt's devicePixelRatio); ignored for a window of its own
            integer(c_int), value :: vsync  !! 1 to present at the display's refresh (FIFO), 0 as soon as a frame is
                                            !! ready (IMMEDIATE where the driver has it, else FIFO), e.g. for benchmarks
        end function vkf_viewer_create

        !> A mouse event from the host's window. Returns 0, or -1 on failure (or an unknown `kind`).
        integer(c_int) function vkf_viewer_mouse(viewer, kind, x, y, mask, button) bind(C, name="vkf_viewer_mouse")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer   !! viewer handle (vkf_viewer_create)
            integer(c_int), value :: kind  !! 1 = move, 2 = button press, 3 = button release
            integer(c_int), value :: x     !! pointer position (framebuffer pixels from the left)
            integer(c_int), value :: y     !! pointer position (framebuffer pixels from the top)
            integer(c_int), value :: mask  !! the buttons held (VSG's BUTTON_MASK_1/2/3 = 256/512/1024)
            integer(c_int), value :: button  !! the button pressed or released (1 left, 2 middle, 3 right)
        end function vkf_viewer_mouse

        !> A scroll wheel event from the host's window. Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_scroll(viewer, dy) bind(C, name="vkf_viewer_scroll")
            import :: c_ptr, c_int, c_float
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
            real(c_float), value :: dy    !! the scroll amount: > 0 away from the user
        end function vkf_viewer_scroll

        !> A key press or release from the host's window, as VSG key symbols and modifiers
        !> (vsg/ui/KeyEvent.h: ASCII for printable keys, X11-style codes otherwise). Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_key(viewer, pressed, symbol, modified_symbol, modifier) &
                bind(C, name="vkf_viewer_key")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer              !! viewer handle (vkf_viewer_create)
            integer(c_int), value :: pressed          !! 1 for a press, 0 for a release
            integer(c_int), value :: symbol           !! the key's base symbol (unmodified)
            integer(c_int), value :: modified_symbol  !! the key's symbol with the modifiers applied ('V' with shift)
            integer(c_int), value :: modifier         !! the modifier keys held (vsg::KeyModifier mask)
        end function vkf_viewer_key

        !> The keyboard and mouse events since the last call, oldest first, up to `capacity` of them.
        !> Returns how many were copied (the rest are kept for the next call), or -1 on failure.
        !> Each event (a column of `events`): kind (1 key press, 2 key release, 3 pointer move, 4 button press,
        !> 5 button release), key (modified: 'V' with shift), key base, modifiers, button, buttons held (mask),
        !> x, y (framebuffer pixels from the top left), and the render area (offset x, y, width, height).
        !> Pointer events have key 1 while the mouse is grabbed (vkf_viewer_set_grab). Events taken by a
        !> GUI panel are not included.
        integer(c_int) function vkf_viewer_events(viewer, events, capacity) bind(C, name="vkf_viewer_events")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer                !! viewer handle (vkf_viewer_create)
            integer(c_int), intent(out) :: events(12, *) !! the events, 12 integers each
            integer(c_int), value :: capacity           !! the most events `events` holds
        end function vkf_viewer_events

        !> Whether a left button press takes the mouse from the trackball (armed 1: a handle is under the
        !> cursor), until the button is released: those events are given with key 1 (vkf_viewer_events).
        !> Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_set_grab(viewer, armed) bind(C, name="vkf_viewer_set_grab")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer                !! viewer handle (vkf_viewer_create)
            integer(c_int), value :: armed              !! 1: armed; 0: not
        end function vkf_viewer_set_grab

        !> Framebuffer pixels per window unit (2 on Retina; a host's window's as given).
        real(c_double) function vkf_viewer_pixel_ratio(viewer) bind(C, name="vkf_viewer_pixel_ratio")
            import :: c_ptr, c_double
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
        end function vkf_viewer_pixel_ratio

        !> The swapchain's size (framebuffer pixels): a VSG window's size as it is resized (a host's window
        !> has the size it gives, vkf_viewer_resize). Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_size(viewer, width, height) bind(C, name="vkf_viewer_size")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer                 !! viewer handle (vkf_viewer_create)
            integer(c_int), intent(out) :: width, height !! the size (framebuffer pixels)
        end function vkf_viewer_size

        !> The host resized its window: rebuild the swapchain. A window of no width or height (e.g. a
        !> collapsed splitter) is not drawn until it has a size again. Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_resize(viewer, width, height) bind(C, name="vkf_viewer_resize")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer            !! viewer handle (vkf_viewer_create)
            integer(c_int), value :: width, height  !! the new size (framebuffer pixels)
        end function vkf_viewer_resize

        !> The host is closing the window: the next vkf_viewer_frame returns 0. Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_close(viewer) bind(C, name="vkf_viewer_close")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
        end function vkf_viewer_close

        !> Delete a viewer (its window, GUI and default light; waits for the device to be idle first).
        subroutine vkf_viewer_delete(viewer) bind(C, name="vkf_viewer_delete")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
        end subroutine vkf_viewer_delete

        !> Set the camera's eye, center and up vectors (view-frame coordinates); stops the trackball's throw (the
        !> camera doesn't keep turning after it). Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_look_at(viewer, eye, center, up) bind(C, name="vkf_viewer_look_at")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: viewer                               !! viewer handle (vkf_viewer_create)
            real(c_double), dimension(3), intent(in) :: eye, center, up !! view-frame coordinates
        end function vkf_viewer_look_at

        !> Set the background (clear) color. Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_set_background(viewer, rgba) bind(C, name="vkf_viewer_set_background")
            import :: c_ptr, c_int, c_float
            implicit none
            type(c_ptr), value :: viewer                    !! viewer handle (vkf_viewer_create)
            real(c_float), dimension(4), intent(in) :: rgba !! color (rgba, 0 to 1)
        end function vkf_viewer_set_background

        !> The camera's eye, center and up vectors (view-frame coordinates), as the trackball left them.
        !> Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_get_look_at(viewer, eye, center, up) bind(C, name="vkf_viewer_get_look_at")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: viewer                                !! viewer handle (vkf_viewer_create)
            real(c_double), dimension(3), intent(out) :: eye, center, up !! view-frame coordinates
        end function vkf_viewer_get_look_at

        !> Set the transform from world (scene root) to view-frame coordinates, applied after the trackball's
        !> eye/center/up (see ViewLookAt). Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_set_view_transform(viewer, m) bind(C, name="vkf_viewer_set_view_transform")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: viewer                       !! viewer handle (vkf_viewer_create)
            real(c_double), dimension(4,4), intent(in) :: m    !! world to view-frame matrix
        end function vkf_viewer_set_view_transform

        !> Use an orthographic projection with the given view-volume bounds. Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_orthographic(viewer, left, right, bottom, top, near_distance, far_distance) &
                bind(C, name="vkf_viewer_orthographic")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
            real(c_double), value :: left, right, bottom, top, near_distance, far_distance
        end function vkf_viewer_orthographic

        !> Use a perspective projection. Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_perspective(viewer, fovy, near_distance, far_distance) &
                bind(C, name="vkf_viewer_perspective")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
            real(c_double), value :: fovy  !! vertical field of view (degrees)
            real(c_double), value :: near_distance, far_distance  !! near and far plane distances (scene units)
        end function vkf_viewer_perspective

        !> Add a node to the viewer's scene, also while running (it is compiled first). The scene holds a
        !> reference: the node handle can be deleted afterwards. Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_add_node(viewer, node) bind(C, name="vkf_viewer_add_node")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer, node  !! viewer handle (vkf_viewer_create) and node handle
        end function vkf_viewer_add_node

        ! Rocky maps (src/vsg/vkf_rocky.cpp; optional: vkf_rocky_available() is 0 without it)

        !> 1 if vkframes was built with Rocky, else 0.
        integer(c_int) function vkf_rocky_available() bind(C, name="vkf_rocky_available")
            import :: c_int
            implicit none
        end function vkf_rocky_available

        !> A map for the viewer (a node handle: vkf_node_*); null without Rocky or on failure.
        type(c_ptr) function vkf_rocky_map_create(viewer, profile, profile_len, scale) bind(C, name="vkf_rocky_map_create")
            import :: c_ptr, c_int, c_char, c_double
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
            character(kind=c_char), dimension(*), intent(in) :: profile  !! Rocky's profile (e.g.
                                                                         !! "global-geodetic", "global-qsc", "moon";
                                                                         !! empty: Rocky's default)
            integer(c_int), value :: profile_len  !! the length of `profile`
            real(c_double), value :: scale        !! scene units per meter
        end function vkf_rocky_map_create

        !> A map's settings. Returns 0, or -1 on failure.
        integer(c_int) function vkf_rocky_map_settings(map, lighting, background) bind(C, name="vkf_rocky_map_settings")
            import :: c_ptr, c_int, c_float
            implicit none
            type(c_ptr), value :: map        !! map handle (vkf_rocky_map_create)
            integer(c_int), value :: lighting  !! (1: lit by the scene's lights, as vkframes' spheres;
                                               !! 0: imagery as is (Rocky's default))
            real(c_float), dimension(4), intent(in) :: background  !! the color where there is no imagery (rgba, 0 to 1)
        end function vkf_rocky_map_settings

        !> 1 while a map can be drawn (the viewer it was made for exists), else 0.
        integer(c_int) function vkf_rocky_map_alive(map) bind(C, name="vkf_rocky_map_alive")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: map  !! map handle (vkf_rocky_map_create)
        end function vkf_rocky_map_alive

        !> The equatorial radius of a map's body (meters); 0 without Rocky or on failure.
        real(c_double) function vkf_rocky_map_radius(map) bind(C, name="vkf_rocky_map_radius")
            import :: c_ptr, c_double
            implicit none
            type(c_ptr), value :: map  !! map handle (vkf_rocky_map_create)
        end function vkf_rocky_map_radius

        !> The height (meters) of the elevation data in a file (GDAL) at a latitude and longitude on the body
        !> of a Rocky profile, sampled directly (no map or window needed; files are kept open for later samples).
        real(c_double) function vkf_rocky_elevation(uri, uri_len, profile, profile_len, lat, lon, ok) &
                bind(C, name="vkf_rocky_elevation")
            import :: c_int, c_char, c_double
            implicit none
            character(kind=c_char), dimension(*), intent(in) :: uri, profile  !! the file, and the profile
                                                                              !! (as vkf_rocky_map_create)
            integer(c_int), value :: uri_len, profile_len  !! the lengths of `uri` and `profile`
            real(c_double), value :: lat, lon              !! latitude and longitude (degrees)
            integer(c_int), intent(out) :: ok              !! 1 if there was data there
        end function vkf_rocky_elevation

        !> Add an image layer to a map. Returns 0, or -1 on failure.
        integer(c_int) function vkf_rocky_add_image(map, kind, uri, uri_len) bind(C, name="vkf_rocky_add_image")
            import :: c_ptr, c_int, c_char
            implicit none
            type(c_ptr), value :: map  !! map handle (vkf_rocky_map_create)
            integer(c_int), value :: kind  !! 0 a TMS service (`uri` its root), 1 a file or service GDAL reads,
                                           !! 2 a template ({z}, {x}, {y}) of geodetic tiles on the map's body,
                                           !! rows from the top (WMTS-style, e.g. NASA's Moon Trek),
                                           !! 3 a template of web Mercator tiles (OpenStreetMap-style)
            integer(c_int), value :: uri_len  !! the length of `uri`
            character(kind=c_char), dimension(*), intent(in) :: uri
        end function vkf_rocky_add_image

        !> Add an elevation layer to a map. Returns 0, or -1 on failure.
        integer(c_int) function vkf_rocky_add_elevation(map, kind, uri, uri_len, encoding) &
                bind(C, name="vkf_rocky_add_elevation")
            import :: c_ptr, c_int, c_char
            implicit none
            type(c_ptr), value :: map  !! map handle (vkf_rocky_map_create)
            integer(c_int), value :: kind      !! as vkf_rocky_add_image's
            integer(c_int), value :: uri_len   !! the length of `uri`
            integer(c_int), value :: encoding  !! 0 single-channel heights (meters), 1 Mapbox RGB, 2 Terrarium RGB
            character(kind=c_char), dimension(*), intent(in) :: uri
        end function vkf_rocky_add_elevation

        !> The viewer's GUI panels (vkf_gui.cpp), made on first use: a handle for the vkf_gui_* functions,
        !> valid while the viewer exists; null on failure.
        type(c_ptr) function vkf_viewer_gui(viewer, font, font_len, size) bind(C, name="vkf_viewer_gui")
            import :: c_ptr, c_int, c_char, c_double
            implicit none
            type(c_ptr), value :: viewer                    !! viewer handle (vkf_viewer_create)
            character(kind=c_char), intent(in) :: font(*)   !! font file (empty: ImGui's)
            integer(c_int), value :: font_len               !! the length of `font`
            real(c_double), value :: size                   !! font size (window units)
        end function vkf_viewer_gui

        !> Begin a panel: the widgets up to vkf_gui_end are in it.
        subroutine vkf_gui_begin(gui, title, title_len, x, y, pivot_x, pivot_y, follow) bind(C, name="vkf_gui_begin")
            import :: c_ptr, c_int, c_char, c_double
            implicit none
            type(c_ptr), value :: gui                     !! GUI handle (vkf_viewer_gui)
            character(kind=c_char), intent(in) :: title(*) !! panel title ("##name": no title bar, e.g. a callout)
            integer(c_int), value :: title_len            !! the length of `title`
            integer(c_int), value :: follow  !! not 0: placed at (x, y) every frame (and not movable),
                                             !! else only at first
            real(c_double), value :: x, y    !! position (0 to 1 across the window, y up; x < 0: where ImGui puts it)
            real(c_double), value :: pivot_x, pivot_y  !! the panel's point placed there
                                                       !! (0 to 1 across it, from the top left)
        end subroutine vkf_gui_begin

        !> End the panel begun by vkf_gui_begin.
        subroutine vkf_gui_end(gui) bind(C, name="vkf_gui_end")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: gui  !! GUI handle (vkf_viewer_gui)
        end subroutine vkf_gui_end

        !> A line of text in the current panel.
        subroutine vkf_gui_text(gui, text, text_len) bind(C, name="vkf_gui_text")
            import :: c_ptr, c_int, c_char
            implicit none
            type(c_ptr), value :: gui                     !! GUI handle (vkf_viewer_gui)
            character(kind=c_char), intent(in) :: text(*)
            integer(c_int), value :: text_len             !! the length of `text`
        end subroutine vkf_gui_text

        !> A button: 1 if it was clicked since the last call for it (in this panel), else 0.
        integer(c_int) function vkf_gui_button(gui, label, label_len) bind(C, name="vkf_gui_button")
            import :: c_ptr, c_int, c_char
            implicit none
            type(c_ptr), value :: gui                      !! GUI handle (vkf_viewer_gui)
            character(kind=c_char), intent(in) :: label(*)
            integer(c_int), value :: label_len             !! the length of `label`
        end function vkf_gui_button

        !> A checkbox showing `value`: 1 (and `value` set) if the user changed it since the last call for it, else 0.
        integer(c_int) function vkf_gui_checkbox(gui, label, label_len, value) bind(C, name="vkf_gui_checkbox")
            import :: c_ptr, c_int, c_char
            implicit none
            type(c_ptr), value :: gui                      !! GUI handle (vkf_viewer_gui)
            character(kind=c_char), intent(in) :: label(*)
            integer(c_int), value :: label_len             !! the length of `label`
            integer(c_int), intent(inout) :: value         !! 1 checked, 0 not
        end function vkf_gui_checkbox

        !> A slider showing `value` from `low` to `high` (as `format`, printf's; empty: "%.4g"): 1 (and `value`
        !> set) if the user changed it since the last call for it, else 0.
        integer(c_int) function vkf_gui_slider(gui, label, label_len, value, low, high, format, format_len) &
                bind(C, name="vkf_gui_slider")
            import :: c_ptr, c_int, c_char, c_double
            implicit none
            type(c_ptr), value :: gui                      !! GUI handle (vkf_viewer_gui)
            character(kind=c_char), intent(in) :: label(*)
            integer(c_int), value :: label_len             !! the length of `label`
            real(c_double), intent(inout) :: value
            real(c_double), value :: low, high             !! the slider's range
            character(kind=c_char), intent(in) :: format(*)
            integer(c_int), value :: format_len            !! the length of `format` (0: the default)
        end function vkf_gui_slider

        !> A combo box (drop-down menu) of `items` (newline-separated), showing item `index` (0-based): 1 (and
        !> `index` set) if the user chose another since the last call for it, else 0.
        integer(c_int) function vkf_gui_combo(gui, label, label_len, index, items, items_len) &
                bind(C, name="vkf_gui_combo")
            import :: c_ptr, c_int, c_char
            implicit none
            type(c_ptr), value :: gui                      !! GUI handle (vkf_viewer_gui)
            character(kind=c_char), intent(in) :: label(*)
            integer(c_int), value :: label_len             !! the length of `label`
            integer(c_int), intent(inout) :: index
            character(kind=c_char), intent(in) :: items(*)
            integer(c_int), value :: items_len             !! the length of `items`
        end function vkf_gui_combo

        !> A horizontal separator in the current panel.
        subroutine vkf_gui_separator(gui) bind(C, name="vkf_gui_separator")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: gui  !! GUI handle (vkf_viewer_gui)
        end subroutine vkf_gui_separator

        !> Put the next widget on the same line as the last one.
        subroutine vkf_gui_same_line(gui) bind(C, name="vkf_gui_same_line")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: gui  !! GUI handle (vkf_viewer_gui)
        end subroutine vkf_gui_same_line

        !> The first part of a frame (compiling the scene on the first call): up to the window's events handled
        !> (the trackball has moved the camera), before the scene is drawn (vkf_viewer_frame_draw): a program can
        !> still change the scene for this frame in between. Returns 1 while the viewer is running, 0 once its
        !> window has been closed, -1 on failure.
        integer(c_int) function vkf_viewer_frame_begin(viewer) bind(C, name="vkf_viewer_frame_begin")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
        end function vkf_viewer_frame_begin

        !> The rest of a frame begun by vkf_viewer_frame_begin: the scene's updates, drawn and presented.
        !> Returns 1, or -1 on failure.
        integer(c_int) function vkf_viewer_frame_draw(viewer) bind(C, name="vkf_viewer_frame_draw")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
        end function vkf_viewer_frame_draw

        !> Render one frame (vkf_viewer_frame_begin, then vkf_viewer_frame_draw). Returns 1 while the viewer is
        !> running, 0 once its window has been closed, -1 on failure.
        integer(c_int) function vkf_viewer_frame(viewer) bind(C, name="vkf_viewer_frame")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
        end function vkf_viewer_frame

        !> Write the last presented frame to an image file (format from the extension, e.g. .png).
        !> Call after vkf_viewer_frame. Returns 0 on success.
        integer(c_int) function vkf_viewer_capture(viewer, filename, filename_len) bind(C, name="vkf_viewer_capture")
            import :: c_ptr, c_int, c_char
            implicit none
            type(c_ptr), value :: viewer                                  !! viewer handle (vkf_viewer_create)
            character(kind=c_char), dimension(*), intent(in) :: filename
            integer(c_int), value :: filename_len                         !! the length of `filename`
        end function vkf_viewer_capture

        !> Start recording the frames drawn to a video of `fps` frames per second: piped as raw video to
        !> `ffmpeg` (the program), encoded with `output` (ffmpeg's output options, then the file, quoted).
        !> Returns 0, or -1 if already recording.
        integer(c_int) function vkf_viewer_record_start(viewer, ffmpeg, ffmpeg_len, output, output_len, fps) &
                bind(C, name="vkf_viewer_record_start")
            import :: c_ptr, c_int, c_char, c_double
            implicit none
            type(c_ptr), value :: viewer                                  !! viewer handle (vkf_viewer_create)
            character(kind=c_char), dimension(*), intent(in) :: ffmpeg    !! the ffmpeg command (quoted if need be)
            integer(c_int), value :: ffmpeg_len                           !! the length of `ffmpeg`
            character(kind=c_char), dimension(*), intent(in) :: output    !! ffmpeg's output options and file
            integer(c_int), value :: output_len                           !! the length of `output`
            real(c_double), value :: fps                                  !! frames per second of the video
        end function vkf_viewer_record_start

        !> Stop recording: the last frames written and ffmpeg's input closed (it finishes the file).
        !> Returns the frames written, or -1 if not recording or on failure.
        integer(c_long_long) function vkf_viewer_record_stop(viewer) bind(C, name="vkf_viewer_record_stop")
            import :: c_ptr, c_long_long
            implicit none
            type(c_ptr), value :: viewer                                  !! viewer handle (vkf_viewer_create)
        end function vkf_viewer_record_stop

        !> The trackball's sensitivity: `zoom_speed` scales each scroll step's zoom and a right drag's;
        !> `throw` (1) keeps the camera moving after a drag released while moving. Returns 0, or -1.
        integer(c_int) function vkf_viewer_set_camera_controls(viewer, zoom_speed, throw) &
                bind(C, name="vkf_viewer_set_camera_controls")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: viewer                                  !! viewer handle (vkf_viewer_create)
            real(c_double), value :: zoom_speed                           !! (> 0; 1: VSG's)
            integer(c_int), value :: throw                                !! 1: keep moving after a released drag
        end function vkf_viewer_set_camera_controls

        !> Frames recorded so far, or -1 if not recording or recording failed.
        integer(c_long_long) function vkf_viewer_record_frames(viewer) bind(C, name="vkf_viewer_record_frames")
            import :: c_ptr, c_long_long
            implicit none
            type(c_ptr), value :: viewer                                  !! viewer handle (vkf_viewer_create)
        end function vkf_viewer_record_frames

        !> Add a child to a group node (a transform or a switch); while running, compile the child first
        !> (vkf_compile_node). Returns 0, or -1 on failure.
        integer(c_int) function vkf_node_add_child(parent, child) bind(C, name="vkf_node_add_child")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: parent, child  !! node handles
        end function vkf_node_add_child

        !> Remove a child from a group node (a transform or a switch), also while running: its GPU data are
        !> released a few frames later. Returns 0, or -1 if it is not a child.
        integer(c_int) function vkf_node_remove_child(parent, child) bind(C, name="vkf_node_remove_child")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: parent, child  !! node handles
        end function vkf_node_remove_child

        !> Compile a node for the running viewers (to add it to a shown scene).
        !> Viewers not yet running compile it with their scene at their first frame. Returns 0 on success.
        integer(c_int) function vkf_compile_node(node) bind(C, name="vkf_compile_node")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: node  !! node handle
        end function vkf_compile_node

        !> A viewer that exists (the oldest running one, else the oldest), or null.
        type(c_ptr) function vkf_viewer_any() bind(C, name="vkf_viewer_any")
            import :: c_ptr
            implicit none
        end function vkf_viewer_any

        !> A sphere's level of detail for a body of `radius` (its largest semi-axis), centered on the node's
        !> origin: a node handle; meshes are added with vkf_sphere_lod_add.
        type(c_ptr) function vkf_sphere_lod_create(radius) bind(C, name="vkf_sphere_lod_create")
            import :: c_ptr, c_double
            implicit none
            real(c_double), value :: radius  !! scene units
        end function vkf_sphere_lod_create

        !> Add a mesh of `segments` segments around the equator. Returns 0, or -1 on failure.
        integer(c_int) function vkf_sphere_lod_add(lod, mesh, segments) bind(C, name="vkf_sphere_lod_add")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: lod, mesh  !! handles (vkf_sphere_lod_create, vkf_mesh_create)
            integer(c_int), value :: segments
        end function vkf_sphere_lod_add

        !> The most segments any view wanted since the last call (0 if not drawn).
        integer(c_int) function vkf_sphere_lod_requested(lod) bind(C, name="vkf_sphere_lod_requested")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: lod  !! handle (vkf_sphere_lod_create)
        end function vkf_sphere_lod_requested

        !> Draw only points `first` .. `first`+`n`-1 (no data is uploaded): e.g. for trace mode.
        !> (Separate segments: `first` odd, `n` even.) Returns 0, or -1 on failure (out of range).
        integer(c_int) function vkf_line_set_range(line, first, n) bind(C, name="vkf_line_set_range")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: line        !! line, marker or ribbon handle
            integer(c_int), value :: first, n  !! the first point (1-based) and the number of points
        end function vkf_line_set_range

        !> A line strip's level of detail: the bounding sphere of its points and
        !> error(k): how far the points skipped can be from the segments drawn at
        !> stride 2^k (k = 1 .. levels).
        !> Returns 0, or -1 on failure.
        integer(c_int) function vkf_line_set_lod(line, center, radius, error, levels) bind(C, name="vkf_line_set_lod")
            import :: c_ptr, c_int, c_double, c_float
            implicit none
            type(c_ptr), value :: line                          !! line handle
            real(c_double), dimension(3), intent(in) :: center  !! bounding sphere center
            real(c_double), value :: radius                     !! bounding sphere radius
            real(c_float), dimension(*), intent(in) :: error    !! (levels), in the points' units
            integer(c_int), value :: levels                     !! (0: all points drawn)
        end function vkf_line_set_lod

        !> Bounding sphere of a node in its own coordinates. Returns 0, or -1 if the node is empty.
        integer(c_int) function vkf_node_bound(node, center, radius) bind(C, name="vkf_node_bound")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: node                           !! node handle
            real(c_double), dimension(3), intent(out) :: center
            real(c_double), intent(out) :: radius
        end function vkf_node_bound

        !> A switch node: a group whose children can be hidden and shown together; null on failure.
        type(c_ptr) function vkf_switch_create() bind(C, name="vkf_switch_create")
            import :: c_ptr
            implicit none
        end function vkf_switch_create

        !> Show or hide all children of a switch. Returns 0, or -1 on failure.
        integer(c_int) function vkf_switch_set(node, visible) bind(C, name="vkf_switch_set")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: node        !! switch handle (vkf_switch_create)
            integer(c_int), value :: visible  !! 1 to show, 0 to hide
        end function vkf_switch_set

        !> A transform node (a group whose children are placed by a 4x4 matrix), initially identity;
        !> null on failure.
        type(c_ptr) function vkf_transform_create() bind(C, name="vkf_transform_create")
            import :: c_ptr
            implicit none
        end function vkf_transform_create

        !> Set a transform node's matrix. Returns 0, or -1 on failure (not a transform).
        integer(c_int) function vkf_transform_set_matrix(transform, m) bind(C, name="vkf_transform_set_matrix")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: transform                  !! transform handle (vkf_transform_create)
            real(c_double), dimension(4,4), intent(in) :: m
        end function vkf_transform_set_matrix

        !> Delete a node handle (a scene or parent that holds the node keeps it).
        subroutine vkf_node_delete(node) bind(C, name="vkf_node_delete")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: node  !! node handle
        end subroutine vkf_node_delete

        !> Read an image file (png, jpg, ...): an image handle, or null on failure.
        type(c_ptr) function vkf_image_read(filename, filename_len) bind(C, name="vkf_image_read")
            import :: c_ptr, c_int, c_char
            implicit none
            character(kind=c_char), dimension(*), intent(in) :: filename
            integer(c_int), value :: filename_len  !! the length of `filename`
        end function vkf_image_read

        !> Delete an image handle.
        subroutine vkf_image_delete(image) bind(C, name="vkf_image_delete")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: image  !! image handle (vkf_image_read)
        end subroutine vkf_image_delete

        !> Read a model file (3ds, obj, gltf, ...): a node handle, or null on failure.
        type(c_ptr) function vkf_model_read(filename, filename_len) bind(C, name="vkf_model_read")
            import :: c_ptr, c_int, c_char
            implicit none
            character(kind=c_char), dimension(*), intent(in) :: filename
            integer(c_int), value :: filename_len  !! the length of `filename`
        end function vkf_model_read

        !> A triangle mesh (a node handle), filled from Fortran (vkf_mesh_array, before the first frame);
        !> null on failure.
        !> A translucent mesh's bounding sphere (its coordinates), for depth sorting and culling.
        !> Returns 0, or -1 if it isn't a translucent mesh (vkf_mesh_create's flag 8).
        integer(c_int) function vkf_mesh_set_bound(mesh, x, y, z, radius) bind(C, name="vkf_mesh_set_bound")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: mesh                    !! mesh handle (vkf_mesh_create)
            real(c_double), value :: x, y, z, radius      !! the bound's center and radius
        end function vkf_mesh_set_bound

        type(c_ptr) function vkf_mesh_create(n_vertices, n_indices, rgba, texture, night_texture, flags) &
                bind(C, name="vkf_mesh_create")
            import :: c_ptr, c_int, c_float
            implicit none
            integer(c_int), value :: n_vertices  !! number of vertices
            integer(c_int), value :: n_indices   !! number of triangle indices
            integer(c_int), value :: flags  !! 1 for an unlit transparent surface (no textures), 2 for view lighting
                                            !! (frames' axes), else lit by the scene's lights with a material
                                            !! (vkf_mesh_material); plus 4 for vertices that change after the mesh is
                                            !! compiled (rewritten in place, then vkf_mesh_vertices_changed); plus 8
                                            !! for a lit translucent surface (depth sorted: vkf_mesh_set_bound); plus
                                            !! 16 two-sided (lit: no faces culled); plus 32 a color per vertex
                                            !! (vkf_mesh_array 5; rgba each one's initial color)
            real(c_float), dimension(4), intent(in) :: rgba  !! color (rgba, 0 to 1; multiplies the texture)
            type(c_ptr), value :: texture, night_texture     !! image handles (vkf_image_read), or null
        end function vkf_mesh_create

        !> The material of a lit mesh: 19 floats (vsg::PhongMaterial: ambient, diffuse, specular, emissive rgba,
        !> shininess, alpha mask, alpha mask cutoff); call vkf_mesh_material_changed after writing them.
        !> Null for a mesh without one.
        type(c_ptr) function vkf_mesh_material(mesh) bind(C, name="vkf_mesh_material")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: mesh  !! mesh handle (vkf_mesh_create)
        end function vkf_mesh_material

        !> Upload a mesh's material again after writing it (vkf_mesh_material). Returns 0, or -1 on failure.
        integer(c_int) function vkf_mesh_material_changed(mesh) bind(C, name="vkf_mesh_material_changed")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: mesh  !! mesh handle (vkf_mesh_create)
        end function vkf_mesh_material_changed

        !> Upload a mesh's vertices again after rewriting them in place (a mesh
        !> created with flag 4).
        !> Returns 0, or -1 on failure.
        integer(c_int) function vkf_mesh_vertices_changed(mesh) bind(C, name="vkf_mesh_vertices_changed")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: mesh  !! mesh handle (vkf_mesh_create)
        end function vkf_mesh_vertices_changed

        !> A lit mesh's twilight: its diffuse light extended to `angle` (radians)
        !> past the terminator, fading out (0: none).
        !> Returns 0, or -1 on failure.
        integer(c_int) function vkf_mesh_set_twilight(mesh, angle) bind(C, name="vkf_mesh_set_twilight")
            import :: c_ptr, c_int, c_float
            implicit none
            type(c_ptr), value :: mesh   !! mesh handle (vkf_mesh_create)
            real(c_float), value :: angle !! radians
        end function vkf_mesh_set_twilight

        !> Remove a node from the viewer's scene (its GPU data are released a few frames later).
        !> Returns 0, or -1 if it is not in the scene.
        integer(c_int) function vkf_viewer_remove_node(viewer, node) bind(C, name="vkf_viewer_remove_node")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer, node  !! viewer handle (vkf_viewer_create) and node handle
        end function vkf_viewer_remove_node

        !> Remove a label from the viewer's HUD (its GPU data are released a few frames later).
        !> Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_hud_remove(viewer, label) bind(C, name="vkf_viewer_hud_remove")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer, label  !! viewer handle (vkf_viewer_create) and label handle
        end function vkf_viewer_hud_remove

        !> A light (a node handle), initially a directional light along -z (in its node's coordinates) of white
        !> diffuse color and no ambient or specular part; null on failure.
        type(c_ptr) function vkf_light_create() bind(C, name="vkf_light_create")
            import :: c_ptr
            implicit none
        end function vkf_light_create

        !> Set a light's colors and position, in its node's coordinates. Returns 0 (-1 for a bad handle).
        integer(c_int) function vkf_light_set(light, ambient, diffuse, specular, position) bind(C, name="vkf_light_set")
            import :: c_ptr, c_int, c_float, c_double
            implicit none
            type(c_ptr), value :: light  !! light handle (vkf_light_create, vkf_viewer_light)
            real(c_float), dimension(3), intent(in) :: ambient, diffuse, specular  !! colors (rgb, 0 to 1)
            real(c_double), dimension(4), intent(in) :: position  !! OpenGL's (x, y, z, w): w = 0 for a directional
                                                                  !! light from direction (x, y, z), else a point
                                                                  !! light at (x/w, y/w, z/w)
        end function vkf_light_set

        !> The viewer's default light (a vkf_light_create light in eye coordinates, owned by the viewer);
        !> null on failure.
        type(c_ptr) function vkf_viewer_light(viewer) bind(C, name="vkf_viewer_light")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: viewer  !! viewer handle (vkf_viewer_create)
        end function vkf_viewer_light

        !> Turn the viewer's default light on or off. Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_light_enable(viewer, enabled) bind(C, name="vkf_viewer_light_enable")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer      !! viewer handle (vkf_viewer_create)
            integer(c_int), value :: enabled  !! 1 on, 0 off
        end function vkf_viewer_light_enable

        !> One of the mesh's arrays, to fill in place; valid while the mesh handle exists.
        type(c_ptr) function vkf_mesh_array(mesh, which) bind(C, name="vkf_mesh_array")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: mesh     !! mesh handle (vkf_mesh_create)
            integer(c_int), value :: which !! 1 = vertices (3 floats each), 2 = normals (3 floats), 3 = texture
                                           !! coordinates (2 floats), 4 = triangle indices (uint32, 0-based)
        end function vkf_mesh_array

        !> Read a font file (TrueType, ... through vsgXchange's freetype): a font handle, or null on failure.
        !> Converting a font takes seconds, so the result is cached and read from there next time.
        type(c_ptr) function vkf_font_read(filename, filename_len) bind(C, name="vkf_font_read")
            import :: c_ptr, c_int, c_char
            implicit none
            character(kind=c_char), dimension(*), intent(in) :: filename
            integer(c_int), value :: filename_len  !! the length of `filename`
        end function vkf_font_read

        !> Delete a font handle.
        subroutine vkf_font_delete(font) bind(C, name="vkf_font_delete")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: font  !! font handle (vkf_font_read)
        end subroutine vkf_font_delete

        !> A label (a node handle): `text` at `position`, facing the screen; null on failure.
        type(c_ptr) function vkf_label_create(font, text, text_len, rgba, position, size, max_size, halign, valign) &
                bind(C, name="vkf_label_create")
            import :: c_ptr, c_int, c_char, c_float, c_double
            implicit none
            type(c_ptr), value :: font                              !! font handle (vkf_font_read)
            character(kind=c_char), dimension(*), intent(in) :: text
            integer(c_int), value :: text_len                       !! the length of `text`
            real(c_float), dimension(4), intent(in) :: rgba         !! color (rgba, 0 to 1)
            real(c_double), dimension(3), intent(in) :: position    !! in its parent's coordinates
            real(c_float), value :: size      !! > 0: `size` window units tall (a fixed screen size);
                                              !! < 0: -`size` scene units tall, but at most `max_size` on screen
            real(c_float), value :: max_size  !! the most window units tall on screen (`size` < 0)
            integer(c_int), value :: halign, valign  !! (vsg::StandardLayout's alignments (0 baseline,
                                                     !! 1 left/top, 2 center, 3 right/bottom))
        end function vkf_label_create

        !> Change a label's text, in place: a text longer than the label's capacity (glyphs allocated when it
        !> was made) is cut to it, with a warning. Returns 0, or -1 on failure.
        integer(c_int) function vkf_label_set_text(label, text, text_len) bind(C, name="vkf_label_set_text")
            import :: c_ptr, c_int, c_char
            implicit none
            type(c_ptr), value :: label                             !! label handle (vkf_label_create)
            character(kind=c_char), dimension(*), intent(in) :: text
            integer(c_int), value :: text_len                       !! the length of `text`
        end function vkf_label_set_text

        !> Draw a label on top of the scene (at the near plane, as HUD labels: not hidden by what is in front of
        !> it) or depth-tested. Returns 0, or -1 on failure.
        integer(c_int) function vkf_label_set_on_top(label, on) bind(C, name="vkf_label_set_on_top")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: label  !! label handle (vkf_label_create)
            integer(c_int), value :: on  !! not 0: on top; 0: depth-tested
        end function vkf_label_set_on_top

        !> Draw a label at infinity (on /= 0: its position a direction, in its parent's axes; at the far plane,
        !> behind the scene, as the sky's stars) or at its position (0). Returns 0, or -1 if not a label.
        integer(c_int) function vkf_label_set_at_infinity(label, on) bind(C, name="vkf_label_set_at_infinity")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: label  !! label handle (vkf_label_create)
            integer(c_int), value :: on  !! 1: at infinity; 0: at its position
        end function vkf_label_set_at_infinity

        !> Change a label's layout, in place. Returns 0, or -1 on failure.
        integer(c_int) function vkf_label_set_layout(label, position, size, max_size, halign, valign) &
                bind(C, name="vkf_label_set_layout")
            import :: c_ptr, c_int, c_double, c_float
            implicit none
            type(c_ptr), value :: label                  !! label handle (vkf_label_create)
            real(c_double), intent(in) :: position(3)    !! as vkf_label_create's
            real(c_float), value :: size, max_size       !! as vkf_label_create's
            integer(c_int), value :: halign, valign      !! as vkf_label_create's
        end function vkf_label_set_layout

        !> Change a label's color. Returns 0, or -1 on failure.
        integer(c_int) function vkf_label_set_color(label, rgba) bind(C, name="vkf_label_set_color")
            import :: c_ptr, c_int, c_float
            implicit none
            type(c_ptr), value :: label                      !! label handle (vkf_label_create)
            real(c_float), dimension(4), intent(in) :: rgba  !! color (rgba, 0 to 1)
        end function vkf_label_set_color

        !> Show a label in the viewer's HUD, on top of the scene, also while running: the label's position is
        !> (x, y) from 0 to 1 across the viewport, bottom to top. Returns 0, or -1 on failure.
        integer(c_int) function vkf_viewer_hud_add(viewer, label) bind(C, name="vkf_viewer_hud_add")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: viewer, label  !! viewer handle (vkf_viewer_create) and label handle
        end function vkf_viewer_hud_add

        !> A line strip holding up to `capacity` points (initially none): a node handle, or null on failure.
        !> Fortran fills its point and style buffers in place (vkf_line_points, vkf_line_style), then calls
        !> vkf_line_points_changed / vkf_line_style_changed. It can be shown in any number of windows.
        type(c_ptr) function vkf_line_create(capacity) bind(C, name="vkf_line_create")
            import :: c_ptr, c_int
            implicit none
            integer(c_int), value :: capacity  !! the most points
        end function vkf_line_create

        !> Stars (up to `capacity`; initially none): a node handle, or null on failure. Fortran fills the
        !> buffer in place (vkf_sky_stars) and calls vkf_sky_stars_changed.
        type(c_ptr) function vkf_stars_create(capacity) bind(C, name="vkf_stars_create")
            import :: c_ptr, c_int
            implicit none
            integer(c_int), value :: capacity  !! the most stars
        end function vkf_stars_create

        !> Lines on the sky (up to `capacity`; initially none; e.g. constellations): segments between
        !> directions, of the color and width of the style's parameters (vkf_sky_style). A node handle, or null on
        !> failure. Fortran fills the buffer as the stars' (vkf_sky_stars: 8 floats per line, its first direction
        !> x, y, z, unused, then its second) and calls vkf_sky_stars_changed.
        type(c_ptr) function vkf_sky_lines_create(capacity) bind(C, name="vkf_sky_lines_create")
            import :: c_ptr, c_int
            implicit none
            integer(c_int), value :: capacity  !! the most lines
        end function vkf_sky_lines_create

        !> The sky texture: an image (equirectangular, as a Sphere's texture) on the sky seen from inside;
        !> a node handle, or null on failure.
        type(c_ptr) function vkf_sky_texture_create(image) bind(C, name="vkf_sky_texture_create")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: image  !! image handle (vkf_image_read)
        end function vkf_sky_texture_create

        !> The stars' buffer, 8 floats per star (direction x, y, z, size in window units, then r, g, b, unused),
        !> to fill in place; null for a sky texture.
        type(c_ptr) function vkf_sky_stars(node, capacity) bind(C, name="vkf_sky_stars")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: node              !! stars or lines handle (vkf_stars_create, vkf_sky_lines_create)
            integer(c_int), intent(out) :: capacity !! the buffer's capacity (stars or lines)
        end function vkf_sky_stars

        !> The first `n` stars were written: draw them, and upload the buffer. Returns 0, or -1 on failure.
        integer(c_int) function vkf_sky_stars_changed(node, n) bind(C, name="vkf_sky_stars_changed")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: node  !! stars handle (vkf_stars_create)
            integer(c_int), value :: n  !! the number of stars (at most the capacity)
        end function vkf_sky_stars_changed

        !> The sky's style buffer: 24 floats, the attitude (a column-major 4x4 matrix, sky frame to root frame),
        !> then 8 parameters (lines: width in window units, 3 unused, then the color r, g, b, a; unused by stars
        !> and the texture); call vkf_sky_style_changed after writing them. Null on failure.
        type(c_ptr) function vkf_sky_style(node) bind(C, name="vkf_sky_style")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: node  !! sky handle (vkf_stars_create, vkf_sky_texture_create)
        end function vkf_sky_style

        !> Upload a sky's style again after writing it (vkf_sky_style). Returns 0, or -1 on failure.
        integer(c_int) function vkf_sky_style_changed(node) bind(C, name="vkf_sky_style_changed")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: node  !! sky handle (vkf_stars_create, vkf_sky_texture_create)
        end function vkf_sky_style_changed

        !> Markers at up to `capacity` points (initially none): a screen-space quad per point, of the shape and
        !> size in the style buffer (shaders/marker.vert); a node handle, or null on failure. The point and style
        !> buffers are those of a line (vkf_line_points, vkf_line_style, vkf_line_points_changed,
        !> vkf_line_set_range, vkf_line_style_changed); with `colored` (1), a color per marker
        !> (vkf_line_colors), times the style's.
        type(c_ptr) function vkf_marker_create(capacity, image, colored) bind(C, name="vkf_marker_create")
            import :: c_ptr, c_int
            implicit none
            integer(c_int), value :: capacity  !! the most points
            type(c_ptr), value :: image  !! image handle (vkf_image_read) for the image shape, or null
            integer(c_int), value :: colored  !! 1: a color per marker, 0: the style's only
        end function vkf_marker_create

        !> A line with a color per point (vkf_line_colors), blended along each segment.
        !> A node handle, or null on failure.
        type(c_ptr) function vkf_line_create_colored(capacity) bind(C, name="vkf_line_create_colored")
            import :: c_ptr, c_int
            implicit none
            integer(c_int), value :: capacity  !! the most points
        end function vkf_line_create_colored

        !> A ribbon of up to `capacity` points (even): a translucent surface through
        !> pairs of points, a quad between each pair and the next; a color per
        !> point (vkf_line_colors) if `colored` is not 0. The buffers are a line's.
        !> A node handle, or null on failure.
        type(c_ptr) function vkf_ribbon_create(capacity, colored) bind(C, name="vkf_ribbon_create")
            import :: c_ptr, c_int
            implicit none
            integer(c_int), value :: capacity  !! the most points (even)
            integer(c_int), value :: colored   !! not 0: a color per point
        end function vkf_ribbon_create

        !> The colors (4 floats per point) of a line made by vkf_line_create_colored (null for others).
        type(c_ptr) function vkf_line_colors(line, capacity) bind(C, name="vkf_line_colors")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: line              !! line or ribbon handle
            integer(c_int), intent(out) :: capacity !! the buffer's capacity (points)
        end function vkf_line_colors

        !> A line's point buffer, 8 floats per point (x, y, z and the distance along the strip from its start,
        !> high parts, then their low parts; see shaders/line.vert; markers ignore the distance), to fill in
        !> place; valid while the line handle exists. Null on failure.
        type(c_ptr) function vkf_line_points(line, capacity) bind(C, name="vkf_line_points")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: line              !! line, marker or ribbon handle
            integer(c_int), intent(out) :: capacity !! the buffer's capacity (points)
        end function vkf_line_points

        !> A line's style buffer: 8 floats (r, g, b, a, width in window units, 1 for separate segments (pairs of
        !> points) or 0 for a strip, the dash pattern (16 bits; 65535 or 0: solid), window units per bit of it).
        !> Set the segment flag before drawing points. Null on failure.
        type(c_ptr) function vkf_line_style(line) bind(C, name="vkf_line_style")
            import :: c_ptr
            implicit none
            type(c_ptr), value :: line  !! line, marker or ribbon handle
        end function vkf_line_style

        !> The first `n` points of the buffer have been written: draw `n` points and upload the buffer.
        !> Returns 0, or -1 if `n` exceeds the capacity.
        integer(c_int) function vkf_line_points_changed(line, n) bind(C, name="vkf_line_points_changed")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: line  !! line, marker or ribbon handle
            integer(c_int), value :: n  !! the number of points
        end function vkf_line_points_changed

        !> Upload a line's style again after writing it (vkf_line_style). Returns 0, or -1 on failure.
        integer(c_int) function vkf_line_style_changed(line) bind(C, name="vkf_line_style_changed")
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value :: line  !! line, marker or ribbon handle
        end function vkf_line_style_changed

        !> Set a line's highlight (shaders/line.vert) and upload it: `values`
        !> are its color (rgba), (flags: 1 pulse, 2 arrows; arrow spacing
        !> (window units), arrow speed (window units per second), band width
        !> (window units)), (pulse period (s), 0, 0, 0). Returns 0, or -1 if
        !> `line` isn't a line.
        integer(c_int) function vkf_line_set_highlight(line, values) bind(C, name="vkf_line_set_highlight")
            import :: c_ptr, c_int, c_float
            implicit none
            type(c_ptr), value :: line                      !! line handle
            real(c_float), dimension(12), intent(in) :: values
        end function vkf_line_set_highlight

        !> Set a line strip's trail (shaders/line.vert) and upload it: `values`
        !> are (the distance at the current time as a high/low pair, the
        !> direction (1, -1; 0: no trail), the color flags), (the past's cutoff
        !> and fade start, the future's fade start and cutoff: distances from the
        !> current time's), the past's color (rgba), the future's, (the past's
        !> dash pattern and factor, the future's). Returns 0, or -1 if `line`
        !> isn't a line strip.
        integer(c_int) function vkf_line_set_trail(line, values) bind(C, name="vkf_line_set_trail")
            import :: c_ptr, c_int, c_float
            implicit none
            type(c_ptr), value :: line                      !! line handle
            real(c_float), dimension(20), intent(in) :: values
        end function vkf_line_set_trail

        !> A line's dashes: `screen` 1 screen-locked (a constant length in pixels, following the line on
        !> screen), 0 anchored to the line (the default). Returns 0, or -1 if `line` isn't a line.
        integer(c_int) function vkf_line_set_dash_mode(line, mode, length) bind(C, name="vkf_line_set_dash_mode")
            import :: c_ptr, c_int, c_double
            implicit none
            type(c_ptr), value :: line                      !! line handle
            integer(c_int), value :: mode                   !! 0 anchored, 1 screen-locked, 2 a fixed length
            real(c_double), value :: length                 !! (2) the pattern's length, scene units (0: from the first view)
        end function vkf_line_set_dash_mode

    end interface

contains

    !> Name of the first Vulkan physical device, or an empty string if there is none.
    function vulkan_device_name() result(name)
        character(len=:), allocatable :: name
        character(kind=c_char, len=256) :: buf
        integer(c_int) :: n
        n = vkf_vulkan_device_name(buf, len(buf, kind=c_int))
        if (n < 0) then
            name = ''
        else
            name = buf(1:min(n, len(buf)))
        end if
    end function vulkan_device_name

    !> Set the points of a line strip (`xyz(3, n)`, double precision), splitting
    !> each coordinate, and the distance along the strip, into the (high, low)
    !> float pair the line shader reads, in place in the strip's buffer.
    !> Returns 0, or -1 if `n` exceeds the capacity.
    integer(c_int) function line_set_points(line, xyz) result(status)
        type(c_ptr), intent(in) :: line                   !! line, marker or ribbon handle
        real(c_double), dimension(:,:), intent(in) :: xyz !! (3, n) points
        real(c_float), dimension(:,:,:), pointer :: p  !! (xyz and distance, high/low, point)
        type(c_ptr) :: buffer
        integer(c_int) :: capacity, n
        real(c_double) :: s                            !! distance along the strip
        integer :: i
        n = int(size(xyz, 2), c_int)
        buffer = vkf_line_points(line, capacity)
        if (.not. c_associated(buffer) .or. n > capacity) then
            status = -1
            return
        end if
        call c_f_pointer(buffer, p, [4, 2, int(capacity)])
        s = 0.0_c_double
        do i = 1, n
            if (i > 1) s = s + norm2(xyz(:, i) - xyz(:, i - 1))
            p(1:3, 1, i) = real(xyz(:, i), c_float)
            p(1:3, 2, i) = real(xyz(:, i) - real(p(1:3, 1, i), c_double), c_float)
            p(4, 1, i) = real(s, c_float)
            p(4, 2, i) = real(s - real(p(4, 1, i), c_double), c_float)
        end do
        status = vkf_line_points_changed(line, n)
    end function line_set_points

    !> Set the color (`rgba`) and width (window units: logical pixels) of a line strip.
    !> Returns 0, or -1 on failure.
    integer(c_int) function line_set_style(line, rgba, width) result(status)
        type(c_ptr), intent(in) :: line                  !! line or ribbon handle
        real(c_float), dimension(4), intent(in) :: rgba  !! color (rgba, 0 to 1)
        real(c_float), intent(in) :: width               !! width (window units)
        real(c_float), dimension(:), pointer :: style
        type(c_ptr) :: buffer
        buffer = vkf_line_style(line)
        if (.not. c_associated(buffer)) then
            status = -1
            return
        end if
        call c_f_pointer(buffer, style, [8])
        style(1:4) = rgba
        style(5) = width
        status = vkf_line_style_changed(line)
    end function line_set_style

    !> Set a line's dash pattern, as OpenGL's line stipple: each bit of the
    !> 16-bit `pattern` (bit 0 first; z'FFFF' solid) covers `factor` window
    !> units (see shaders/line.vert).
    !> Returns 0, or -1 on failure.
    integer(c_int) function line_set_pattern(line, factor, pattern) result(status)
        type(c_ptr), intent(in) :: line  !! line handle
        integer, intent(in) :: factor, pattern
        real(c_float), dimension(:), pointer :: style
        type(c_ptr) :: buffer
        buffer = vkf_line_style(line)
        if (.not. c_associated(buffer)) then
            status = -1
            return
        end if
        call c_f_pointer(buffer, style, [8])
        style(7) = real(iand(pattern, int(z'FFFF')), c_float)
        style(8) = real(max(factor, 1), c_float)
        status = vkf_line_style_changed(line)
    end function line_set_pattern

    !> Draw a line's points as separate segments (pairs of points: 1-2, 3-4,
    !> ...) instead of a strip. Set before its points are drawn.
    !> Returns 0, or -1 on failure.
    integer(c_int) function line_set_segments(line, segments) result(status)
        type(c_ptr), intent(in) :: line      !! line handle
        logical, intent(in) :: segments      !! true for separate segments, false for a strip
        real(c_float), dimension(:), pointer :: style
        type(c_ptr) :: buffer
        buffer = vkf_line_style(line)
        if (.not. c_associated(buffer)) then
            status = -1
            return
        end if
        call c_f_pointer(buffer, style, [8])
        style(6) = merge(1.0_c_float, 0.0_c_float, segments)
        status = vkf_line_style_changed(line)
    end function line_set_segments

    !> Set the color (`rgba`), size (window units), shape (see shaders/
    !> marker.frag) and attenuation radius (scene units; 0 for none) of markers.
    !> Returns 0, or -1 on failure.
    integer(c_int) function marker_set_style(marker, rgba, size, shape, radius) result(status)
        type(c_ptr), intent(in) :: marker                !! marker handle (vkf_marker_create)
        real(c_float), dimension(4), intent(in) :: rgba  !! color (rgba, 0 to 1)
        real(c_float), intent(in) :: size, radius
        integer, intent(in) :: shape
        real(c_float), dimension(:), pointer :: style
        type(c_ptr) :: buffer
        buffer = vkf_line_style(marker)
        if (.not. c_associated(buffer)) then
            status = -1
            return
        end if
        call c_f_pointer(buffer, style, [8])
        style(1:4) = rgba
        style(5:7) = [size, real(shape, c_float), radius]
        status = vkf_line_style_changed(marker)
    end function marker_set_style

    !> Set a lit mesh's material: ambient, diffuse, specular and emissive
    !> colors (rgba) and shininess (VSG's Phong: the ambient color scales the
    !> diffuse color reflected from ambient light).
    !> Returns 0, or -1 on failure (e.g. a mesh without a material).
    integer(c_int) function mesh_set_material(mesh, ambient, diffuse, specular, emissive, shininess) result(status)
        type(c_ptr), intent(in) :: mesh  !! mesh handle (vkf_mesh_create)
        real(c_float), dimension(4), intent(in) :: ambient, diffuse, specular, emissive  !! colors (rgba, 0 to 1)
        real(c_float), intent(in) :: shininess  !! Phong exponent
        real(c_float), dimension(:), pointer :: m
        type(c_ptr) :: buffer
        buffer = vkf_mesh_material(mesh)
        if (.not. c_associated(buffer)) then
            status = -1
            return
        end if
        call c_f_pointer(buffer, m, [19])
        m(1:4) = ambient
        m(5:8) = diffuse
        m(9:12) = specular
        m(13:16) = emissive
        m(17) = shininess
        status = vkf_mesh_material_changed(mesh)
    end function mesh_set_material

    !> Pointers to the arrays of a mesh of `n_vertices` vertices and `n_indices`
    !> indices (as created by `vkf_mesh_create`), to fill in place before the
    !> first frame. Indices are 0-based.
    subroutine mesh_arrays(mesh, n_vertices, n_indices, xyz, normals, uv, indices)
        type(c_ptr), intent(in) :: mesh  !! mesh handle (vkf_mesh_create)
        integer, intent(in) :: n_vertices, n_indices
        real(c_float), dimension(:,:), pointer, intent(out) :: xyz      !! (3, n_vertices)
        real(c_float), dimension(:,:), pointer, intent(out) :: normals  !! (3, n_vertices)
        real(c_float), dimension(:,:), pointer, intent(out) :: uv       !! (2, n_vertices)
        integer(c_int32_t), dimension(:), pointer, intent(out) :: indices  !! (n_indices) triangle indices, 0-based
        call c_f_pointer(vkf_mesh_array(mesh, 1_c_int), xyz, [3, n_vertices])
        call c_f_pointer(vkf_mesh_array(mesh, 2_c_int), normals, [3, n_vertices])
        call c_f_pointer(vkf_mesh_array(mesh, 3_c_int), uv, [2, n_vertices])
        call c_f_pointer(vkf_mesh_array(mesh, 4_c_int), indices, [n_indices])
    end subroutine mesh_arrays

    !> Report a failed rendering-layer call (a status not 0), saying where
    !> (`context`, e.g. 'shapes').
    subroutine check_status(status, context)
        integer(c_int), intent(in) :: status                !! a rendering-layer call's result (0: success)
        character(len=*), intent(in), optional :: context  !! where the call was made
        if (status == 0) return
        if (present(context)) then
            write(*, '(a)') 'vkframes: a rendering-layer call failed ('//context//')'
        else
            write(*, '(a)') 'vkframes: a rendering-layer call failed'
        end if
    end subroutine check_status

end module vkf_layer
