How to upload a texture to the GPU ?

In order to use textures and render targets, haiku provides hk_image_t and associated utility functions in haiku/graphics.h.

In this tutorial, we will define a texture as a CPU ressource that you’ll want to use/sample later inside a shader on the GPU. We’ll imagine that you had previously loaded a .jpg or .png file from disc to CPU using any image library (like stb_image.h).

Here’s the full course of actions:

  • Create the CPU buffer containing the texture pixels

  • Create the GPU image that will be used inside as your texture

  • Transfer pixels from the CPU buffer to the GPU image.

Create the buffer

// You previously loaded a rgba8 texture
// with the following variables:
// - texture_width  : int;
// - texture_height : int;
// - pixels: unsigned char* ;

hk_buffer_t my_pixelbuf = hkgfx_buffer_create(device, &(hk_gfx_buffer_desc){
    .usage_flags = HK_BUFFER_USAGE_TRANSFER_SRC_BIT,
    .memory_type = HK_MEMORY_TYPE_CPU_ONLY,
	.bytesize    = texture_width*texture_height*4*sizeof(uint8_t),
    .dataptr     = texdata
});

// You can now free(pixels);

Create the image

hk_image_t my_texture = hkgfx_image_create(device, &(hk_gfx_image_desc){
    .type = HK_IMAGE_TYPE_2D,
    .extent = {.width = texture_width, .height = texture_height},
    .levels = 1,
    .usage_flags  = HK_IMAGE_USAGE_TRANSFER_DST_BIT | HK_IMAGE_USAGE_SAMPLED_BIT,
	.memory_type  = HK_MEMORY_TYPE_GPU_ONLY
});

Upload pixels to the GPU

hk_context_t ctx = hkgfx_context_create(device);

hkgfx_context_begin(ctx);
{
	// First we transfer the texture content to the image
    hkgfx_context_image_barrier(ctx, &(hk_gfx_barrier_image_params){
    	.image       = my_texture,
        .prev_state  = HK_IMAGE_STATE_UNDEFINED,
        .next_state  = HK_IMAGE_STATE_TRANSFER_DST
    });

    hkgfx_context_copy_buffer_to_image(ctx, my_pixelbuf, my_texture, &(hk_gfx_cmd_image_buffer_copy_params){
    	.aspect = HK_IMAGE_ASPECT_COLOR_BIT,
        .region = {
        	.extent = {.width = texture_width, .height = texture_height, .depth = 1},
        	.layer_count = 1,
    	}
    });
}
hkgfx_context_end(ctx);
hkgfx_device_submit(device, &(hk_gfx_submit_params){.context = ctx});
hkgfx_device_wait(device);

// after that, your texture is uploaded the GPU.
// You can cleanup my_pixelbuf using hkgfx_buffer_destroy(device, my_pixelbuf);

How to set a view from a texture ?

In order to use textures within a shader or as a render target, haiku provides hk_view_t and associated utility functions in haiku/graphics.h.

hk_view_t is used for bindgroups and render targets while hk_image_t is used in barriers, copy, resolve and blit commands.

Create the image ans its view

hk_image_t my_texture = hkgfx_image_create(device, &(hk_gfx_image_desc){
    .type = HK_IMAGE_TYPE_2D,
    .extent = {.width = texture_width, .height = texture_height},
    .levels = 1,
    .usage_flags  = HK_IMAGE_USAGE_TRANSFER_DST_BIT | HK_IMAGE_USAGE_SAMPLED_BIT,
	.memory_type  = HK_MEMORY_TYPE_GPU_ONLY
});
/* [...] */
hk_view_t my_texture_view = hkgfx_view_create(device, &(hk_gfx_view_desc){
    .src_image      = my_texture_view,
    .type           = HK_IMAGE_TYPE_2D,
    .aspect_flags   = HK_IMAGE_ASPECT_COLOR_BIT, 
});

Usecase: bindgroup

    hk_bindgroup_t mybindgroup = hkgfx_bindgroup_create(device,&(hk_gfx_bindgroup_desc){
        .layout = my_pipeline_layout,
        .images = {
            {.image_view = my_texture_view, .sampler = my_texture_sampler }
        }
    });

Usecase: render target

hkgfx_context_render_begin(rendering_context, &(hk_gfx_render_targets_params){
    .layer_count = 1,
    .color_count = 1,
    .render_area = { .extent = {app_frame_width, app_frame_height} },
    .color_attachments = { 
        {.target_render = my_texture_view, .clear_color = {.value_f32 = {0.2f,0.2f,0.2f,1.f}}} 
    },
});

hkgfx_context_set_pipeline(rendering_context, mypipeline);
hkgfx_context_set_bindings(rendering_context, mypipeline, 0, mybindgroup);
hkgfx_context_draw(rendering_context, 3, 1, 0, 0);
hkgfx_context_render_end(rendering_context);