summaryrefslogtreecommitdiff
path: root/project-base/src/assets.c
diff options
context:
space:
mode:
Diffstat (limited to 'project-base/src/assets.c')
-rw-r--r--project-base/src/assets.c710
1 files changed, 710 insertions, 0 deletions
diff --git a/project-base/src/assets.c b/project-base/src/assets.c
new file mode 100644
index 0000000..1cb4a2c
--- /dev/null
+++ b/project-base/src/assets.c
@@ -0,0 +1,710 @@
+/*
+* BSD 2-Clause “Simplified” License
+* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com
+* All rights reserved.
+*/
+
+#if defined(MODE_DEBUG)
+u64 load_stamp_start = 0;
+#endif
+
+void assets_create()
+{
+ assets asset_collection;
+ asset_collection.load_threads_busy = 0;
+ asset_collection.images = array_create(sizeof(image));
+ asset_collection.fonts = array_create(sizeof(font));
+ asset_collection.sounds = array_create(sizeof(sound));
+
+ array_reserve(&asset_collection.images, ASSET_IMAGE_COUNT);
+ array_reserve(&asset_collection.fonts, ASSET_FONT_COUNT);
+ array_reserve(&asset_collection.sounds, ASSET_SOUND_COUNT);
+
+ asset_collection.queue.queue = array_create(sizeof(asset_task));
+ asset_collection.post_process_queue = array_create(sizeof(asset_task));
+
+ array_reserve(&asset_collection.queue.queue, ASSET_QUEUE_COUNT);
+ array_reserve(&asset_collection.post_process_queue, ASSET_QUEUE_COUNT);
+
+ asset_mutex = mutex_create();
+ asset_collection.valid = true;
+ asset_collection.done_loading_assets = false;
+
+ global_asset_collection = asset_collection;
+
+ #if defined(MODE_DEBUG)
+ load_stamp_start = platform_get_time(TIME_FULL, TIME_MILI_S);
+ #endif
+}
+
+inline static bool is_big_endian()
+{
+ volatile uint32_t i=0x01234567;
+ // return 1 for big endian, 0 for little endian.
+ return !((*((uint8_t*)(&i))) == 0x67);
+}
+
+void assets_stop_if_done()
+{
+ if (!global_asset_collection.valid ||
+ (global_asset_collection.post_process_queue.length == 0 &&
+ global_asset_collection.queue.queue.length == 0 &&
+ !global_asset_collection.done_loading_assets &&
+ global_asset_collection.load_threads_busy == 0))
+ {
+ #if defined(MODE_DEBUG)
+ char msg[100];
+ sprintf(msg, "Loaded assets in %.3fs", (platform_get_time(TIME_FULL, TIME_MILI_S) - load_stamp_start)/1000.0f);
+ log_info(msg);
+ #endif
+ global_asset_collection.done_loading_assets = true;
+ array_destroy(&global_asset_collection.queue.queue);
+ // array_destroy(&global_asset_collection.post_process_queue);
+ }
+}
+
+bool assets_do_post_process()
+{
+ if (!global_asset_collection.valid) return false;
+
+ bool result = false;
+
+ mutex_lock(&asset_mutex);
+
+ for (int i = 0; i < global_asset_collection.post_process_queue.length; i++)
+ {
+ asset_task *task = array_at(&global_asset_collection.post_process_queue, i);
+
+ if (task->type == ASSET_WAV || task->type == ASSET_MUSIC) {
+ task->sound->loaded = true;
+
+ #if 0
+ if (task->sound->is_music) {
+ mem_free(task->sound->start_addr);
+ }
+ #endif
+ }
+ if (task->type == ASSET_PNG || task->type == ASSET_BITMAP)
+ {
+ if (task->image->data && task->valid)
+ {
+ if (current_render_driver() == DRIVER_CPU) { task->image->loaded = true; goto done; }
+
+ IMP_glGenTextures(1, &task->image->textureID);
+ IMP_glBindTexture(GL_TEXTURE_2D, task->image->textureID);
+
+ s32 flag = is_big_endian() ? GL_UNSIGNED_INT_8_8_8_8 :
+ GL_UNSIGNED_INT_8_8_8_8_REV;
+
+ if (task->type == ASSET_PNG) {
+ IMP_glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA8, task->image->width,
+ task->image->height, 0, GL_RGBA, flag, task->image->data);
+ }
+ else {
+ IMP_glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA8, task->image->width,
+ task->image->height, 0, GL_BGRA, flag, task->image->data);
+ }
+
+ IMP_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ IMP_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ IMP_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ IMP_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ task->image->loaded = true;
+ IMP_glBindTexture(GL_TEXTURE_2D, 0);
+ }
+ }
+ else if (task->type == ASSET_TTF)
+ {
+ if (task->valid)
+ {
+ if (current_render_driver() == DRIVER_CPU) { task->font->loaded = true; goto done; }
+
+ for (s32 i = TEXT_CHARSET_START; i < TEXT_CHARSET_END; i++)
+ {
+ glyph *g = &task->font->glyphs[i];
+
+ IMP_glGenTextures(1, &g->textureID);
+ IMP_glBindTexture(GL_TEXTURE_2D, g->textureID);
+
+ IMP_glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+ IMP_glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ IMP_glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ IMP_glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ IMP_glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ IMP_glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA, g->width,g->height,
+ 0, GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap );
+ }
+
+ task->font->loaded = true;
+ }
+ }
+
+ done:
+ result = true;
+ }
+ array_clear(&global_asset_collection.post_process_queue);
+ mutex_unlock(&asset_mutex);
+
+ assets_stop_if_done();
+
+ return result;
+}
+
+bool assets_queue_worker_load_bitmap(image *image)
+{
+#pragma pack(push, 1)
+ typedef struct {
+ unsigned short type; /* Magic identifier */
+ unsigned int size; /* File size in bytes */
+ unsigned int reserved;
+ unsigned int offset; /* Offset to image data, bytes */
+ } HEADER;
+ typedef struct {
+ unsigned int size; /* Header size in bytes */
+ int width,height; /* Width and height of image */
+ unsigned short planes; /* Number of colour planes */
+ unsigned short bits; /* Bits per pixel */
+ unsigned int compression; /* Compression type */
+ unsigned int imagesize; /* Image size in bytes */
+ int xresolution,yresolution; /* Pixels per meter */
+ unsigned int ncolours; /* Number of colours */
+ unsigned int importantcolours; /* Important colours */
+ } INFOHEADER;
+#pragma pack(pop)
+
+ HEADER* header = (HEADER*)image->start_addr;
+ INFOHEADER* info = (INFOHEADER*)(image->start_addr+sizeof(HEADER));
+
+ image->data = image->start_addr+header->offset;
+ image->width = info->width;
+ image->height = info->height;
+ image->channels = info->bits/8;
+
+ return image->data != 0;
+}
+
+bool assets_queue_worker_load_image(image *image)
+{
+ //stbi_convert_iphone_png_to_rgb(0);
+ image->data = stbi_load_from_memory(image->start_addr,
+ image->end_addr - image->start_addr,
+ &image->width,
+ &image->height,
+ &image->channels,
+ STBI_rgb_alpha);
+
+ return image->data != 0;
+}
+
+bool assets_queue_worker_load_font(font *font)
+{
+ unsigned char *ttf_buffer = (unsigned char*)font->start_addr;
+
+ stbtt_fontinfo info;
+ if (!stbtt_InitFont(&info, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)))
+ {
+ return false;
+ }
+ float scale = stbtt_ScaleForPixelHeight(&info, font->size);
+
+ for (s32 i = TEXT_CHARSET_START; i < TEXT_CHARSET_END; i++)
+ {
+ s32 w = 0, h = 0, xoff = 0, yoff = 0;
+
+ glyph new_glyph;
+ new_glyph.bitmap = stbtt_GetCodepointBitmap(&info, 0, scale, i, &w, &h, &xoff, &yoff);
+ new_glyph.width = w;
+ new_glyph.height = h;
+ new_glyph.xoff = xoff;
+ new_glyph.yoff = yoff;
+
+ stbtt_GetCodepointHMetrics(&info, i, &new_glyph.advance, &new_glyph.lsb);
+ new_glyph.advance *= scale;
+ new_glyph.lsb *= scale;
+
+ if (i == 'M') font->px_h = -yoff;
+
+ font->glyphs[i-TEXT_CHARSET_START] = new_glyph;
+ }
+
+ font->info = info;
+ font->scale = scale;
+
+ return true;
+}
+
+void *_assets_queue_worker()
+{
+ while (global_asset_collection.valid && !global_asset_collection.done_loading_assets)
+ {
+ thread_sleep(1000);
+
+ if (mutex_trylock(&asset_mutex))
+ {
+ int queue_length = global_asset_collection.queue.queue.length;
+ if (!queue_length)
+ {
+ mutex_unlock(&asset_mutex);
+ continue;
+ }
+ global_asset_collection.load_threads_busy++;
+
+ asset_task *task = array_at(&global_asset_collection.queue.queue, 0);
+ asset_task buf = *task;
+ array_remove_at(&global_asset_collection.queue.queue, 0);
+
+ mutex_unlock(&asset_mutex);
+
+ if (buf.type == ASSET_PNG)
+ {
+ bool result = assets_queue_worker_load_image(buf.image);
+ buf.valid = result;
+ }
+ else if (buf.type == ASSET_BITMAP)
+ {
+ bool result = assets_queue_worker_load_bitmap(buf.image);
+ buf.valid = result;
+ }
+ else if (buf.type == ASSET_TTF)
+ {
+ bool result = assets_queue_worker_load_font(buf.font);
+ buf.valid = result;
+ }
+
+ mutex_lock(&asset_mutex);
+
+ // SDL mixer cant handle multiple threads.
+ if (buf.type == ASSET_WAV)
+ {
+ buf.sound->chunk = Mix_LoadWAV_RW((SDL_RWops*)buf.sound->start_addr, 1);
+ buf.valid = (buf.sound->chunk!=0);
+ }
+ else if (buf.type == ASSET_MUSIC)
+ {
+ buf.sound->music = Mix_LoadMUS((char*)buf.sound->start_addr);
+ //printf("loaded!: %p %s\n", buf.sound->music, (char*)buf.sound->start_addr);
+ buf.valid = (buf.sound->music!=0);
+ }
+
+ log_assert(global_asset_collection.post_process_queue.reserved_length >
+ global_asset_collection.post_process_queue.length, "Attempted to process more assets than specified with constant ASSET_QUEUE_COUNT");
+
+ array_push(&global_asset_collection.post_process_queue, (uint8_t *)&buf);
+
+ global_asset_collection.load_threads_busy--; // Very important to do this last or assets may be flagged as done loading early.
+ mutex_unlock(&asset_mutex);
+ }
+ }
+
+ thread_exit();
+
+ return 0;
+}
+
+image* assets_find_image_ref(u8 *start_addr, s32 hash)
+{
+ for (int i = 0; i < global_asset_collection.images.length; i++)
+ {
+ image *img_at = array_at(&global_asset_collection.images, i);
+
+ if ((start_addr == img_at->start_addr || hash == img_at->path_hash) && img_at->references > 0)
+ {
+ img_at->references++;
+ return img_at;
+ }
+ }
+ return 0;
+}
+
+static sound* find_sound_ref(s32 hash)
+{
+ for (int i = 0; i < global_asset_collection.sounds.length; i++)
+ {
+ sound *sound_at = array_at(&global_asset_collection.sounds, i);
+
+ if (hash == sound_at->path_hash && sound_at->references > 0)
+ {
+ sound_at->references++;
+ return sound_at;
+ }
+ }
+ return 0;
+}
+
+static font* find_font_ref(u8 *start_addr, s32 hash, s16 size)
+{
+ for (int i = 0; i < global_asset_collection.fonts.length; i++)
+ {
+ font *font_at = array_at(&global_asset_collection.fonts, i);
+
+ if ((start_addr == font_at->start_addr || hash == font_at->path_hash) && font_at->size == size && font_at->references > 0)
+ {
+ font_at->references++;
+ return font_at;
+ }
+ }
+ return 0;
+}
+
+u32 assets_hash_path(char* str)
+{
+ unsigned long hash = 5381;
+ int c;
+ while ((c = *str++) && c) hash = ((hash << 5) + hash) + c;
+ return hash;
+}
+
+static image empty_image()
+{
+ image new_image;
+ new_image.loaded = false;
+ new_image.start_addr = 0;
+ new_image.end_addr = 0;
+ new_image.references = 1;
+ new_image.path_hash = UNDEFINED_PATH_HASH;
+ return new_image;
+}
+
+static font empty_font()
+{
+ font new_font;
+ new_font.size = 0;
+ new_font.loaded = false;
+ new_font.start_addr = 0;
+ new_font.end_addr = 0;
+ new_font.references = 1;
+ new_font.path_hash = UNDEFINED_PATH_HASH;
+ return new_font;
+}
+
+static sound empty_sound()
+{
+ sound new_sound;
+ new_sound.chunk = 0;
+ new_sound.is_music = false;
+ new_sound.loaded = false;
+ new_sound.references = 1;
+ new_sound.path_hash = UNDEFINED_PATH_HASH;
+ return new_sound;
+}
+
+static asset_task add_font_to_queue(font font)
+{
+ // NOTE(Aldrik): we should never realloc the image array because pointers will be invalidated.
+ log_assert(CAN_ADD_NEW_FONT(), "Attempted to process more fonts than specified with constant ASSET_FONT_COUNT");
+
+ int index = array_push(&global_asset_collection.fonts, (uint8_t *)&font);
+
+ asset_task task;
+ task.type = ASSET_TTF;
+ task.font = array_at(&global_asset_collection.fonts, index);
+
+ mutex_lock(&asset_mutex);
+ array_push(&global_asset_collection.queue.queue, (uint8_t *)&task);
+ mutex_unlock(&asset_mutex);
+
+ return task;
+}
+
+static asset_task add_image_to_queue(image img, bool is_bitmap)
+{
+ // NOTE(Aldrik): we should never realloc the image array because pointers will be invalidated.
+ log_assert(CAN_ADD_NEW_IMAGE(), "Attempted to process more images than specified with constant ASSET_IMAGE_COUNT");
+
+ int index = array_push(&global_asset_collection.images, (uint8_t *)&img);
+
+ asset_task task;
+ task.type = is_bitmap ? ASSET_BITMAP : ASSET_PNG;
+ task.image = array_at(&global_asset_collection.images, index);
+
+ mutex_lock(&asset_mutex);
+ array_push(&global_asset_collection.queue.queue, (uint8_t *)&task);
+ mutex_unlock(&asset_mutex);
+
+ return task;
+}
+
+static asset_task add_sound_to_queue(sound sound)
+{
+ // NOTE(Aldrik): we should never realloc the image array because pointers will be invalidated.
+ log_assert(CAN_ADD_NEW_SOUND(), "Attempted to process more sounds than specified with constant ASSET_SOUND_COUNT");
+
+ int index = array_push(&global_asset_collection.sounds, (uint8_t *)&sound);
+
+ asset_task task;
+ task.type = sound.is_music ? ASSET_MUSIC : ASSET_WAV;
+ task.sound = array_at(&global_asset_collection.sounds, index);
+
+ mutex_lock(&asset_mutex);
+ array_push(&global_asset_collection.queue.queue, (uint8_t *)&task);
+ mutex_unlock(&asset_mutex);
+
+ return task;
+}
+
+////////////////////////////////////////////////////
+// Loading
+////////////////////////////////////////////////////
+sound* assets_load_music_from_file(char* path)
+{
+ u32 hash = assets_hash_path(path);
+ sound* ref = find_sound_ref(hash);
+ if (ref) return ref;
+
+ sound new_sound = empty_sound();
+ new_sound.path_hash = hash;
+ new_sound.start_addr = (u8*)path;
+ new_sound.is_music = true;
+
+ return add_sound_to_queue(new_sound).sound;
+}
+
+sound* assets_load_wav_from_file(char* path)
+{
+ u32 hash = assets_hash_path(path);
+ sound* ref = find_sound_ref(hash);
+ if (ref) return ref;
+
+ sound new_sound = empty_sound();
+ new_sound.path_hash = hash;
+ new_sound.start_addr = (u8*)SDL_RWFromFile(path, "rb");
+
+ return add_sound_to_queue(new_sound).sound;
+}
+
+font* assets_load_font_from_file(char* path, s16 size)
+{
+ u32 hash = assets_hash_path(path);
+ font* ref = find_font_ref(UNDEFINED_START_ADDR, hash, size);
+ if (ref) return ref;
+
+ platform_set_active_directory(binary_path);
+ file_content content = platform_read_file_content(path, "rb");
+
+ font new_font = empty_font();
+ new_font.size = size;
+ new_font.path_hash = hash;
+ new_font.start_addr = content.content;
+ new_font.end_addr = content.content + content.content_length - 1;
+
+ return add_font_to_queue(new_font).font;
+}
+
+font* assets_load_font(u8 *start_addr, u8 *end_addr, s16 size)
+{
+ font* ref = find_font_ref(start_addr, UNDEFINED_PATH_HASH, size);
+ if (ref) return ref;
+
+ font new_font = empty_font();
+ new_font.start_addr = start_addr;
+ new_font.end_addr = end_addr;
+ new_font.size = size;
+
+ return add_font_to_queue(new_font).font;
+}
+
+image* assets_load_image_from_file(char* path)
+{
+ u32 hash = assets_hash_path(path);
+ image* ref = assets_find_image_ref(UNDEFINED_START_ADDR, hash);
+ if (ref) return ref;
+
+ platform_set_active_directory(binary_path);
+ file_content content = platform_read_file_content(path, "rb");
+
+ image new_image = empty_image();
+ new_image.path_hash = hash;
+ new_image.start_addr = content.content;
+ new_image.end_addr = content.content + content.content_length - 1;
+
+ return add_image_to_queue(new_image, false).image;
+}
+
+image* assets_load_image(u8 *start_addr, u8 *end_addr)
+{
+ image* ref = assets_find_image_ref(start_addr, UNDEFINED_PATH_HASH);
+ if (ref) return ref;
+
+ image new_image = empty_image();
+ new_image.start_addr = start_addr;
+ new_image.end_addr = end_addr;
+
+ return add_image_to_queue(new_image, false).image;
+}
+
+image* assets_load_bitmap_from_file(char* path)
+{
+ u32 hash = assets_hash_path(path);
+ image* ref = assets_find_image_ref(UNDEFINED_START_ADDR, hash);
+ if (ref) return ref;
+
+ platform_set_active_directory(binary_path);
+ file_content content = platform_read_file_content(path, "rb");
+
+ image new_image = empty_image();
+ new_image.path_hash = hash;
+ new_image.start_addr = content.content;
+ new_image.end_addr = content.content + content.content_length - 1;
+
+ return add_image_to_queue(new_image, true).image;
+}
+
+image* assets_load_bitmap(u8 *start_addr, u8 *end_addr)
+{
+ image* ref = assets_find_image_ref(start_addr, UNDEFINED_PATH_HASH);
+ if (ref) return ref;
+
+ image new_image = empty_image();
+ new_image.start_addr = start_addr;
+ new_image.end_addr = end_addr;
+
+ return add_image_to_queue(new_image, true).image;
+}
+
+////////////////////////////////////////////////////
+// Cleaning up
+////////////////////////////////////////////////////
+void assets_destroy_bitmap(image *image_to_destroy)
+{
+ if (image_to_destroy->references == 1)
+ {
+ if (current_render_driver() == DRIVER_GL)
+ {
+ IMP_glBindTexture(GL_TEXTURE_2D, 0);
+ IMP_glDeleteTextures(1, &image_to_destroy->textureID);
+ }
+
+ image_to_destroy->references = 0;
+ }
+ else
+ {
+ image_to_destroy->references--;
+ }
+}
+
+void assets_destroy_font(font *font_to_destroy)
+{
+ if (font_to_destroy->references == 1)
+ {
+ if (current_render_driver() == DRIVER_GL)
+ {
+ for (s32 i = TEXT_CHARSET_START; i < TEXT_CHARSET_END; i++)
+ {
+ glyph g = font_to_destroy->glyphs[i];
+ IMP_glBindTexture(GL_TEXTURE_2D, 0);
+ IMP_glDeleteTextures(1, &g.textureID);
+ }
+ }
+
+ font_to_destroy->references = 0;
+ }
+ else
+ {
+ font_to_destroy->references--;
+ }
+}
+
+void assets_destroy_image(image *image_to_destroy)
+{
+ if (image_to_destroy->references == 1)
+ {
+ if (current_render_driver() == DRIVER_CPU)
+ {
+ IMP_glBindTexture(GL_TEXTURE_2D, 0);
+ IMP_glDeleteTextures(1, &image_to_destroy->textureID);
+ }
+
+ image_to_destroy->references = 0;
+ }
+ else
+ {
+ image_to_destroy->references--;
+ }
+}
+
+
+////////////////////////////////////////////////////
+// Extra
+////////////////////////////////////////////////////
+void _assets_switch_render_method()
+{
+ for (int i = 0; i < global_asset_collection.images.length; i++)
+ {
+ image *img_at = array_at(&global_asset_collection.images, i);
+
+ if (current_render_driver() == DRIVER_GL)
+ {
+ asset_task task;
+ task.type = ASSET_PNG;
+ task.image = img_at;
+ task.valid = true;
+ array_push(&global_asset_collection.post_process_queue, (uint8_t *)&task);
+ }
+ else
+ {
+ IMP_glBindTexture(GL_TEXTURE_2D, 0);
+ IMP_glDeleteTextures(1, &img_at->textureID);
+ }
+ }
+
+ for (int i = 0; i < global_asset_collection.fonts.length; i++)
+ {
+ font *font_at = array_at(&global_asset_collection.fonts, i);
+
+ if (current_render_driver() == DRIVER_GL)
+ {
+ asset_task task;
+ task.type = ASSET_TTF;
+ task.font = font_at;
+ task.valid = true;
+
+ array_push(&global_asset_collection.post_process_queue, (uint8_t *)&task);
+ }
+ else
+ {
+ for (s32 i = TEXT_CHARSET_START; i < TEXT_CHARSET_END; i++)
+ {
+ glyph g = font_at->glyphs[i];
+ IMP_glBindTexture(GL_TEXTURE_2D, 0);
+ IMP_glDeleteTextures(1, &g.textureID);
+ }
+ }
+ }
+}
+
+void assets_destroy()
+{
+ global_asset_collection.valid = false;
+ global_asset_collection.done_loading_assets = true;
+ thread_sleep(30000); // wait 30ms for all asset threads to finish.
+ mutex_lock(&asset_mutex);
+ {
+ if (array_exists(&global_asset_collection.images)) array_destroy(&global_asset_collection.images);
+ if (array_exists(&global_asset_collection.fonts)) array_destroy(&global_asset_collection.fonts);
+
+ if (array_exists(&global_asset_collection.queue.queue)) array_destroy(&global_asset_collection.queue.queue);
+ if (array_exists(&global_asset_collection.post_process_queue)) array_destroy(&global_asset_collection.post_process_queue);
+
+ mem_free(binary_path);
+ }
+ mutex_unlock(&asset_mutex);
+ mutex_destroy(&asset_mutex);
+}
+
+vec2f scale_image_to_width(image* img, s32 width)
+{
+ vec2f v = {(float)img->width, (float)img->height};
+ float scale = v.x / width;
+ v.x = width;
+ v.y /= scale;
+ return v;
+}
+
+vec2f scale_image_to_height(image* img, s32 height)
+{
+ vec2f v = {(float)img->width, (float)img->height};
+ float scale = v.y / height;
+ v.y = height;
+ v.x /= scale;
+ return v;
+} \ No newline at end of file