Rendering Pixel Buffer with Sokol to Screen

Hello
I am currently trying to render a Pixel Buffer with an RGBA8 Pixel Format to the Screen using Sokol and Odin. I have come pretty far. I have a quad on the screen and the color is also displayed. I do have one weird problem, though. The color red 0xff0000ff works fine. When I try the color blue which is 0x0000ffff I get yellow. and when I try the color green 0x00ff00ff I get magenta. For clearing I use my clear_color_buffer method. I think it is either something with the pixel format or with my GLSL shading code. I am running this on MacOS using the Metal API. I have generated the Shader using the sokol-shdc tool. Any help would be greatly appreciated as I am currently pretty stuck. The code as well as the shader code can be found below.

//------------------------------------------------------------------------------
package main

import "core:fmt"

import "base:runtime"
import slog "../../sokol/log"
import sg "../../sokol/gfx"
import sapp "../../sokol/app"
import sglue "../../sokol/glue"

window_width: i32 : 800
window_height: i32 : 600


state: struct {
    pass_action: sg.Pass_Action,
    pip: sg.Pipeline,
    bind: sg.Bindings,
    pixel_buffer: [window_width*window_height]u32
}


init :: proc "c" () {
    context = runtime.default_context()

    sg.setup({
        environment = sglue.environment(),
        logger = { func = slog.func },
    })

    vertices := [?]f32 {
        // positions     uvs
        -1.0,  1.0, 0.0, 0.0, 0.0,
         1.0,  1.0, 0.0, 1.0, 0.0,
         1.0, -1.0, 0.0, 1.0, 1.0,
        -1.0, -1.0, 0.0, 0.0, 1.0
    }

    pixel_format_info := sg.query_pixelformat(.RGBA8)
    fmt.printfln("Got pixel format info ", pixel_format_info)


    // vertex buffer
    state.bind.vertex_buffers[0] = sg.make_buffer({
        data = { ptr = &vertices, size = size_of(vertices) },
    })

    // an index buffer
    indices := [?]u16 { 0, 1, 2,  0, 2, 3 }
    state.bind.index_buffer = sg.make_buffer({
        type = .INDEXBUFFER,
        data = { ptr = &indices, size = size_of(indices) },
    })

    // image which can be dynamically updated
    img := sg.make_image({
        width = window_width,
        height = window_height,
        pixel_format = .RGBA8,
	sample_count = 1,
        usage = .STREAM,
        label = "dynamic-texture"
    });
    state.bind.images[IMG_tex] = img

    // a sampler object
    sampler := sg.make_sampler({
        min_filter = .NEAREST,
        mag_filter = .NEAREST,
        wrap_u = .CLAMP_TO_EDGE,
        wrap_v = .CLAMP_TO_EDGE,
    });
    state.bind.samplers[SMP_smp] = sampler


    // a shader and pipeline object
    state.pip = sg.make_pipeline({
        shader = sg.make_shader(quad_shader_desc(sg.query_backend())),
        index_type = .UINT16,
        layout = {
            attrs = {
                ATTR_quad_position = { format = .FLOAT3 },
                ATTR_quad_texcoord0 = { format = .FLOAT2 }
            },
        },
    })

    // default pass action
    state.pass_action = {
        colors = {
            0 = { load_action = .CLEAR, clear_value = { 0, 0, 0, 1 }},
        },
    }
}

clear_color_buffer :: proc(color: u32) {
    for y in 0..<window_height {
        for x in 0..<window_width {
            state.pixel_buffer[(y * window_width) + x] = color 
        }
    }

}

frame :: proc "c" () {
    context = runtime.default_context()

    // clear_color_buffer(0xff0000ff)
    clear_color_buffer(0x0000ffff)

    // Update image
    size := u64(size_of(state.pixel_buffer))
    image_data: sg.Image_Data
    image_data.subimage[0][0] = {
        ptr = &state.pixel_buffer,
        size = size
    }
    sg.update_image(state.bind.images[IMG_tex], image_data);


    sg.begin_pass({ action = state.pass_action, swapchain = sglue.swapchain() })
    sg.apply_pipeline(state.pip)
    sg.apply_bindings(state.bind)
    sg.draw(0, 6, 1)
    sg.end_pass()
    sg.commit()
}

cleanup :: proc "c" () {
    context = runtime.default_context()
    sg.shutdown()
}

main :: proc () {
    sapp.run({
        init_cb = init,
        frame_cb = frame,
        cleanup_cb = cleanup,
        width = window_width,
        height = window_height,
        window_title = "quad",
        icon = { sokol_default = true },
        logger = { func = slog.func },
    })
}
@header package main
@header import sg "../../sokol/gfx"

@vs vs
in vec4 position;
in vec2 texcoord0;

layout(location=0) out vec2 uv;


void main() {
    gl_Position = position;
    uv = texcoord0;
}
@end

@fs fs
layout(binding=0) uniform texture2D tex;
layout(binding=0) uniform sampler smp;
layout(location=0) in vec2 uv;
out vec4 frag_color;

void main() {
    frag_color = texture(sampler2D(tex, smp), uv);
}
@end

@program quad vs fs

It would seem on the surface level that something, somewhere, somehow is interpreting your data as ABGR. Or at least I assume that’s the case.

If it is being used as ABGR it makes a lot more sense.
Red 0xff0000ff = A ff, B 00, G 00, R ff
Green 0x00ff00ff (is Magenta) = A 00, B ff, G 00, R ff
Blue 0x0000ffff (is Yellow) = A 00, B 00, G ff, R ff

I was gonna try to help more, but when I tried running your code, I got these undeclared names, so I’m at a roadblock:

main.odin(64:23) Error: Undeclared name: IMG_tex 
	state.bind.images[IMG_tex] = img 
	                  ^~~~~~^ 
main.odin(73:25) Error: Undeclared name: SMP_smp 
	state.bind.samplers[SMP_smp] = sampler 
	                    ^~~~~~^ 
main.odin(78:33) Error: Undeclared name: quad_shader_desc 
	shader = sg.make_shader(quad_shader_desc(sg.query_backend())), 
	                        ^~~~~~~~~~~~~~~^ 
main.odin(82:17) Error: Undeclared name: ATTR_quad_position 
	ATTR_quad_position = { format = .FLOAT3 }, 
	^~~~~~~~~~~~~~~~~^ 
main.odin(83:17) Error: Undeclared name: ATTR_quad_texcoord0 
	ATTR_quad_texcoord0 = { format = .FLOAT2 } 
	^~~~~~~~~~~~~~~~~~^ 
main.odin(118:39) Error: Undeclared name: IMG_tex 
	sg.update_image(state.bind.images[IMG_tex], image_data); 
	                                  ^~~~~~^ 

The only way I could help you with this would be if I were to run the program and diagnose it myself since I’ve never used Sokol before. I can’t exactly eyeball it here since nothing on the surface jumps out as wrong to me.

Actually, now that I think about it, is your system little-endian?
I can never tell what the case would be with Macs.

Since RGBA is just defining what each byte is in memory, then using a literal might be an issue since all hex literals are written as big-endian, so you would need to keep in mind that the that the bytes at the end of the literal would end up first in memory if your system is little-endian.

The more I think about it, the more confident I am that this is just a mistake in the understanding of how the literals work.

If you do something like u32be(0x0000ffff), I think that should enforce big-endian enocding. But you need to make sure you transmute() it and not cast() it whenever you need it as u32.

Otherwise you’re just going to have to think about your hex literals in reverse.

2 Likes

Awesome thank you so much. That totally was the issue (endianess). I have prepared a GitHub Repo which should serve as a good starting point for anyone trying to do the same thing.

Git Repo

4 Likes

I was looking for EXACTLY this. Thank you so much.