[m-users.] Calling pass-by-value with C FFI with raylib

Sean Charles (emacstheviking) objitsu at gmail.com
Fri Jul 14 16:29:10 AEST 2023


Hello,

Regarding FFI with C, I have some questions regarding the method I am using to
manage a particular data value. I am writing a graphics program using a
library called raylib, it's a break from my usual party with SDL2, and I am
finding this library to be much more effective in terms of less code to get
things running.

However, I am now finding myself running into the same issues again and again
and it revolves around managing colours. The structure that defines a Color is
this:

    // Color, 4 components, R8G8B8A8 (32bit)
    typedef struct Color {
        unsigned char r;        // Color red value
        unsigned char g;        // Color green value
        unsigned char b;        // Color blue value
        unsigned char a;        // Color alpha value
    } Color;

The author says that he uses 'pass by value' in most of the function calls, as
he has deliberately designed them to be no more than 64 bits, 32 bits for the
colours though, a typical C library signature to demonstrate:

    DrawRectangle(int posX, int posY, int width, int height, Color color);

He also has, conveniently, predefined a whole bunch of colours as a default
palette like this:

    #define LIGHTGRAY  CLITERAL(Color){ 200, 200, 200, 255 }   // Light Gray
    #define GRAY       CLITERAL(Color){ 130, 130, 130, 255 }   // Gray
    #define DARKGRAY   CLITERAL(Color){ 80, 80, 80, 255 }      // Dark Gray
    :

The only real way I could find to model that in Mercury has been to literally
re-invent the weel and create this union type and then some helpers:

    :- type p_color
        --->    lightgray
        ;       gray
        ;       yellow
        :
        ;       raywhite. % FInal colour from header file palette

    :- type color
        --->    rgb(uint8, uint8, uint8)
        ;       rgba(uint8, uint8, uint8, uint8)
        ;       c(p_color).

This means that so far, for a mainl loop that looks like this:


:- pred run_loop(io::di, io::uo) is det.

run_loop(!IO) :-
    window_should_close(X, !IO),
    (
        X = yes,
        trace_log("Stopped by user", [], !IO)
    ;
        X = no,

        dropped_files(Files, !IO),

        begin_drawing(!IO),

            clear_background(c(beige), !IO),
            draw_fps(0, 0, !IO),

            ui_draw(new_cursor(100,100), !IO),

        end_drawing(!IO),

        ( if list.is_not_empty(Files) then
            io.write_list(Files, "\n", io.print_line, !IO)
        else
            true
        ),
        run_loop(!IO)
    ).

I have to write a predicate that takes in the 'color', breaks it into RGBA
uint8 values and then calls the C wrapper:


draw_rectangle(X, Y, W, H, COLOR, !IO) :-
    color_parts(COLOR, R, G, B, A),
    draw_rectangle_(X, Y, W, H, R, G, B, A, !IO).

:- pred draw_rectangle_(
    int::in, int::in, int::in, int::in,
    uint8::in, uint8::in, uint8::in, uint8::in,
    io::di, io::uo
) is det.

:- pragma foreign_proc(
    "C", draw_rectangle_(
        X::in, Y::in, W::in, H::in,
        R::in, G::in, B::in, A::in,
        _IO0::di, _IO::uo
    ),
    [ promise_pure, will_not_call_mercury, will_not_throw_exception
    , will_not_modify_trail, thread_safe, does_not_affect_liveness
    , tabled_for_io],
    "
        Color C = {R, G, B, A};
        DrawRectangle(X, Y, W, H, C);
    ").

You can see that the first thing I have to do before calling the
DrawRectangle() API call is to recompose the uint8-s back into a 32 bit value!


Predicate color_parts/5 is defined as:

:- pred color_parts(
    color::in, uint8::out, uint8::out, uint8::out, uint8::out
) is det.

color_parts(rgb(R,G,B), R, G, B, 255u8).
color_parts(rgba(R, G, B, A), R, G, B, A).
color_parts(c(C), R, G, B, A) :- color_rgba(C, R, G, B, A).


The preceding proredicate is supported by color_rgba/5:

:- pred color_rgba(p_color::in, uint8::out, uint8::out, uint8::out,
    uint8::out) is det.

color_rgba(lightgray , 200u8, 200u8, 200u8, 255u8).
color_rgba(gray      , 130u8, 130u8, 130u8, 255u8).
color_rgba(darkgray  , 80u8, 80u8, 80u8, 255u8).
:


My question then is: Is there a more compact, clean and probably more
efficient way to handle the passing by value so that I don't have to keep
breaking something into 4 uint8s, only to then have to recompose them inside
the C handler?

That to me seems totally inefficient and somewhat silly at this moment in
time.

I am currently experimenting with passing a uint32 and casting it as a
Color but I am always very interested to see others suggestions!

Thank you,
Sean.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurylang.org/archives/users/attachments/20230714/49c85cb2/attachment.html>


More information about the users mailing list