Float pixel format

I’m trying to render a texture in SDL using SDL_PIXELFORMAT_RGB96_FLOAT. I use streaming texture and update its data using SDL_LockTexture. I get data from 2d array of vec3 which is an alias for three floats. Here are some snippets from my code:

...
typedef float vec3[3];
static SDL_Texture *texture = NULL;
...
struct
{
    ...
    vec3 cbuffer[480][640];
}
data;
...
int init(void)
{
    ...
    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB96_FLOAT, SDL_TEXTUREACCESS_STREAMING, 640, 480);
    ...
}
...
int render(void)
{
    ...
    int pitch;
    void *pixels = NULL;
    SDL_LockTexture(texture, NULL, &pixels, &pitch);
    memcpy(pixels, data.cbuffer, 640 * 480 * sizeof(vec3));
    pitch = 640 * sizeof(vec3);
    SDL_UnlockTexture(texture);
    SDL_RenderTexture(renderer, texture, NULL, NULL);
    ...
}
...

However, for some reason, the colors for this texture are not displayed correctly. More specifically, only the white color is displayed.

Here is what the texture looks like:

The texture is supposed to look like this:

This texture was obtained by converting pixel format to SDL_PIXELFORMAT_RGB32. Since RGB96_FLOAT format is supported, however, this conversion should not be required.

Here is the source code:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <glm.h>
#include <SDL3/SDL.h>

static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_Texture *texture = NULL;

struct {
    int nposition;
    int nnormal;
    int ntexcoord;
    int nface;
    int ncolor;
    vec3 positions[1 << 14];
    vec2 normals[1 << 14];
    vec2 texcoords[1 << 14];
    vec3 faces[1 << 14];
    vec3 colors[1 << 14];
    vec3 buffer[1 << 14];
    vec3 cbuffer[480][640];
    float dbuffer[480][640];
    uint32_t mcbuffer[480][640];
}
data;

int init(void)
{
    SDL_SetAppMetadata("Graphics", "1.0", "com.graphics");

    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        SDL_Log("Failed to init SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!SDL_CreateWindowAndRenderer("graphics", 640, 480, 0, &window, &renderer))
    {
        SDL_Log("Failed to init window / renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB96_FLOAT, SDL_TEXTUREACCESS_STREAMING, 640, 480);
    if (!texture)
    {
        SDL_Log("Failed to create texture: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    return SDL_APP_CONTINUE;
}

int app_event(SDL_Event *event)
{
    if (event->type == SDL_EVENT_QUIT)
        return SDL_APP_SUCCESS;
    return SDL_APP_CONTINUE;
}

int app_iterate(void)
{
    void triangle_scanline(vec2 *, uint8_t *);
    void triangle_edge(vec2 *, uint8_t *);
    void render_obj(void);

    SDL_RenderClear(renderer);
    render_obj();
    SDL_RenderPresent(renderer);
    SDL_Delay(10);
    return SDL_APP_CONTINUE;
}

void quit(void) {}

void triangle_scanline(vec2 t[3], uint8_t col[3])
{
    SDL_SetRenderDrawColor(renderer, col[0], col[1], col[2], 255);
    if (t[0][1] > t[1][1]) glm_vec2_swap(t[0], t[1]);
    if (t[0][1] > t[2][1]) glm_vec2_swap(t[0], t[2]);
    if (t[1][1] > t[2][1]) glm_vec2_swap(t[1], t[2]);
    float s1 = (t[1][0] - t[0][0]) / (t[1][1] - t[0][1]);
    float s2 = (t[2][0] - t[0][0]) / (t[2][1] - t[0][1]);
    float s3 = (t[2][0] - t[1][0]) / (t[2][1] - t[1][1]);
    float x1 = t[0][0], x2 = t[0][0];
    for (int y = t[0][1]; y < t[1][1]; y++)
    {
        SDL_RenderLine(renderer, x1, y, x2, y);
        x1 += s1; x2 += s2;
    }
    for (int y = t[1][1]; y < t[2][1]; y++)
    {
        SDL_RenderLine(renderer, x1, y, x2, y);
        x1 += s3; x2 += s2;
    }
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
}

float edge(vec2 a, vec2 b, vec2 p)
{
    return -1 * ((p[0] - a[0]) * (b[1] - a[1]) - (p[1] - a[1]) * (b[0] - a[0])) / 2;
}

void triangle_edge(vec3 t[3], uint8_t col[3])
{
    int xmin = min(min(t[0][0], t[1][0]), t[2][0]);
    int xmax = max(max(t[0][0], t[1][0]), t[2][0]);
    int ymin = min(min(t[0][1], t[1][1]), t[2][1]);
    int ymax = max(max(t[0][1], t[1][1]), t[2][1]);
    for (int y = ymin; y < ymax; y++)
        for (int x = xmin; x < xmax; x++)
        {
            vec2 p = {x, y};
            float s1 = edge(t[0], t[1], p);
            float s2 = edge(t[1], t[2], p);
            float s3 = edge(t[2], t[0], p);
            if (s1 >= 0 && s2 >= 0 && s3 >= 0)
            {
                float s = s1 + s2 + s3;
                if (s == 0) continue;
                float invs = 1 / s;
                vec3 weights = {s1 * invs, s2 * invs, s3 * invs};
                vec3 depths = {t[0][2], t[1][2], t[2][2]};
                vec3 vcol = {col[0], col[1], col[2]};
                float depth = glm_vec3_dot(weights, depths);
                if (depth > data.dbuffer[y][x]) continue;
                glm_vec3_copy(vcol, data.cbuffer[y][x]);
                data.dbuffer[y][x] = depth;
            }
        }
}

int parse_obj(char *filename)
{
    FILE *f = fopen(filename, "r");
    if (!f)
    {
        printf("failed to parse obj file: file does not exist\n");
        return 0;
    }
    char s[16];
    while (fscanf(f, "%s", &s) > 0)
    {
        if (!strcmp(s, "v"))
        {
            int fields = fscanf(f, "%f %f %f\n", 
                &data.positions[data.nposition][0],
                &data.positions[data.nposition][1],
                &data.positions[data.nposition][2]
            );
            if (fields < 3)
            {
                printf("failed to parse obj file: invalid position format\n");
                return 0;
            }
            data.nposition++;
        }
        else if (!strcmp(s, "vn"))
        {
            int fields = fscanf(f, "%f %f\n",
                &data.normals[data.nnormal][0],
                &data.normals[data.nnormal][1]
            );
            if (fields < 2)
            {
                printf("failed to parse obj file: invalid normal format\n");
                return 0;
            }
            data.nnormal++;
        }
        else if (!strcmp(s, "vt"))
        {
            int fields = fscanf(f, "%f %f\n",
                &data.texcoords[data.ntexcoord][0],
                &data.texcoords[data.ntexcoord][1]
            );
            if (fields < 2)
            {
                printf("failed to parse obj file: invalid texcoord format\n");
                return 0;
            }
            data.ntexcoord++;
        }
        else if (!strcmp(s, "f"))
        {
            int fields = fscanf(f, "%f/%f/%f %f/%f/%f %f/%f/%f\n",
                &data.faces[data.nface][0],
                &data.faces[data.nface][1],
                &data.faces[data.nface][2],
                &data.faces[data.nface+1][0],
                &data.faces[data.nface+1][1],
                &data.faces[data.nface+1][2],
                &data.faces[data.nface+2][0],
                &data.faces[data.nface+2][1],
                &data.faces[data.nface+2][2]
            );
            if (fields < 3)
            {
                printf("failed to parse obj file: invalid face format\n");
                return 0;
            }
            data.nface += 3;
        }
        else
        {
            char str[1 << 10];
            fgets(str, (1 << 10), f);
        }
    }
    for (int i = 0; i < data.nface; i++)
        glm_vec3_copy(
            (vec3){
                rand() % 255, rand() % 255, rand() % 255
            },
            data.colors[i]
        );
    return 1;
}

void print_obj(void)
{
    for (int i = 0; i < data.nposition; i++)
        printf("%f %f %f\n",
            data.positions[i][0],
            data.positions[i][1],
            data.positions[i][2]);
    for (int i = 0; i < data.nnormal; i++)
        printf("%f %f\n",
            data.normals[i][0],
            data.normals[i][1]);
    for (int i = 0; i < data.ntexcoord; i++)
        printf("%f %f\n",
            data.texcoords[i][0],
            data.texcoords[i][1]);
    for (int i = 0; i < data.nface; i++)
        printf("%4.0f %4.0f %4.0f\n",
            data.faces[i][0],
            data.faces[i][1],
            data.faces[i][2]);
}

float rad(float theta) { return theta * 3.1415 / 180; }

void render_obj(void)
{
    static float theta = 0.0f;
    // theta++;
    vec3 ihat_yaw = {cos(rad(theta)), 0, sin(rad(theta))};
    vec3 jhat_yaw = {0, 1, 0};
    vec3 khat_yaw = {-sin(rad(theta)), 0, cos(rad(theta))};
    vec3 ihat_pitch = {1, 0, 0};
    vec3 jhat_pitch = {0, cos(rad(theta)), -sin(rad(theta))};
    vec3 khat_pitch = {0, sin(rad(theta)), cos(rad(theta))};

    for (int i = 0; i < data.nposition; i++)
    {
        vec3 v;
        glm_vec3_copy(data.positions[i], v);
        glm_vec3_transform(ihat_yaw, jhat_yaw, khat_yaw, v);
        glm_vec3_add(v, (vec3){0, 0, 5}, v);
        glm_vec3_copy(v, data.buffer[i]);
    }

    for (int i = 0; i < data.nposition; i++)
    {
        data.buffer[i][0] = 
            (data.buffer[i][0] * 
                (480 / (2 * tan(rad(30 / 2))) / data.buffer[i][2])) 
                + (640 / 2);
        data.buffer[i][1] = 
            480 - ((data.buffer[i][1] * 
                (480 / (2 * tan(rad(30 / 2))) / data.buffer[i][2])) 
                + (480 / 2));
    }

    for (int i = 0; i < data.nface; i += 3)
    {
        vec3 t[3];
        glm_vec3_copy(data.buffer[(int) data.faces[i][0] - 1], t[0]);
        glm_vec3_copy(data.buffer[(int) data.faces[i+1][0] - 1], t[1]);
        glm_vec3_copy(data.buffer[(int) data.faces[i+2][0] - 1], t[2]);
        triangle_edge(
            t,
            (uint8_t [3]) {
                data.colors[i][0], data.colors[i][1], data.colors[i][2]
            }
        );
    }

    const SDL_PixelFormatDetails *format = SDL_GetPixelFormatDetails(SDL_PIXELFORMAT_RGBA32);
    for (int y = 0; y < 480; y++)
        for (int x = 0; x < 640; x++)
            data.mcbuffer[y][x] = SDL_MapRGB(
                format, NULL, 
                (uint8_t) data.cbuffer[y][x][0],
                (uint8_t) data.cbuffer[y][x][1],
                (uint8_t) data.cbuffer[y][x][2]
            );

    int pitch;
    void *pixels = NULL;
    SDL_LockTexture(texture, NULL, &pixels, &pitch);
    memcpy(pixels, data.cbuffer, 640 * 480 * sizeof(vec3));
    pitch = 640 * sizeof(vec3);
    SDL_UnlockTexture(texture);
    SDL_RenderTexture(renderer, texture, NULL, NULL);

    memset(data.mcbuffer, 0, 640 * 480 * sizeof(uint32_t));
    memset(data.cbuffer, 0, 640 * 480 * sizeof(vec3));
    for (int y = 0; y < 480; y++)
        for (int x = 0; x < 640; x++)
            data.dbuffer[y][x] = INFINITY;
}

int main(int argc, char **argv)
{
    for (int y = 0; y < 480; y++)
        for (int x = 0; x < 640; x++)
            data.dbuffer[y][x] = INFINITY;

    int status = parse_obj("../../monkey.obj");
    if (!status)
        return 1;

    status = init();
    if (status != SDL_APP_CONTINUE)
        return status;

    SDL_Event e;
    while ((status = app_iterate()) == SDL_APP_CONTINUE)
        while (SDL_PollEvent(&e) != 0)
            if ((status = app_event(&e)) != SDL_APP_CONTINUE)
                return (status == SDL_APP_SUCCESS) ? 0 : status;
}

byte-order, padding, strides and alignment are subjects that always terrify me
I’m not trying to answer. interesting problem, someone will certainly help you and I hope so

edit: the size of stuff always terrify me as well. I always _Static_assert(4 == sizeof (float)); etc. to appease my uncertainty

There is no renderer that supports SDL_PIXELFORMAT_RGB96_FLOAT natively. It’s always converted to another format when used in the renderer, and this is a slow conversion path.

That said, in theory this should work. Can you report a bug on GitHub with a complete minimal example that we can use to reproduce this? Also it would be helpful to know what platform and renderer you’re using.

The issue is resolved. The color data was not normalized. Floating-point color data must be in range [0, 1].

2 Likes