From cf3d6786998f0e59f5451cca983e0f58ebf4cc7e Mon Sep 17 00:00:00 2001 From: jacob Date: Wed, 1 May 2024 00:22:06 -0500 Subject: [PATCH] texture cache w/ eviction & reloading --- res/graphics/white.ase | 3 + src/app.c | 5 +- src/ase.c | 6 +- src/common.h | 30 +- src/config.h | 22 + src/draw.c | 45 +- src/draw.h | 3 +- src/entity.h | 4 +- src/font.c | 8 +- src/font.h | 3 +- src/game.c | 66 ++- src/renderer.h | 10 +- src/renderer_d3d11.c | 37 +- src/resource.c | 3 +- src/sheet.c | 190 +++++---- src/sheet.h | 12 +- src/sys.h | 1 + src/sys_win32.c | 79 ++-- src/texture.c | 907 +++++++++++++++++++++++++++++++++-------- src/texture.h | 57 +-- src/user.c | 174 ++++---- 21 files changed, 1149 insertions(+), 516 deletions(-) create mode 100644 res/graphics/white.ase diff --git a/res/graphics/white.ase b/res/graphics/white.ase new file mode 100644 index 00000000..182ac67a --- /dev/null +++ b/res/graphics/white.ase @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2424e08c9bf7bdcfa17b02d27cef0b62078d01d627b6b1ef0992201bb6977c6b +size 334 diff --git a/src/app.c b/src/app.c index 40bd63a9..b6369ec3 100644 --- a/src/app.c +++ b/src/app.c @@ -20,6 +20,7 @@ #include "settings.h" #include "draw.h" #include "math.h" +#include "renderer.h" struct exit_callback { app_exit_callback_func *func; @@ -209,8 +210,8 @@ void app_entry_point(void) struct asset_cache_startup_receipt asset_cache_sr = asset_cache_startup(&work_sr); struct ttf_startup_receipt ttf_sr = ttf_startup(); struct font_startup_receipt font_sr = font_startup(&work_sr, &renderer_sr, &asset_cache_sr, &ttf_sr, &resource_sr); - struct texture_startup_receipt texture_sr = texture_startup(&work_sr, &renderer_sr, &asset_cache_sr, &resource_sr); - struct sheet_startup_receipt sheet_sr = sheet_startup(&work_sr, &asset_cache_sr, &resource_sr); + struct texture_startup_receipt texture_sr = texture_startup(&renderer_sr, &resource_sr); + struct sheet_startup_receipt sheet_sr = sheet_startup(&resource_sr); struct mixer_startup_receipt mixer_sr = mixer_startup(); struct sound_startup_receipt sound_sr = sound_startup(&work_sr, &asset_cache_sr, &resource_sr); struct draw_startup_receipt draw_sr = draw_startup(&renderer_sr, &font_sr); diff --git a/src/ase.c b/src/ase.c index 302f33cf..df884d95 100644 --- a/src/ase.c +++ b/src/ase.c @@ -240,7 +240,7 @@ INTERNAL u16 huffman_decode(struct huffman *huffman, struct bitbuf *bb) return res; } -INTERNAL void inflate(struct arena *arena, u8 *dest, u8 *encoded) +INTERNAL void inflate(u8 *dest, u8 *encoded) { __prof; struct bitbuf bb = { encoded }; @@ -273,7 +273,7 @@ INTERNAL void inflate(struct arena *arena, u8 *dest, u8 *encoded) case BLOCK_TYPE_COMPRESSED_FIXED: case BLOCK_TYPE_COMPRESSED_DYNAMIC: { - struct temp_arena scratch = scratch_begin(arena); + struct temp_arena scratch = scratch_begin_no_conflict(); u32 lit_len_dist_table[512] = { 0 }; u32 hlit; u32 hdist; @@ -692,7 +692,7 @@ struct ase_decode_image_result ase_decode_image(struct arena *arena, struct buff cel->height = br_read_u16(&br); cel->pixels = arena_push_array(scratch.arena, u32, cel->width * cel->height); - inflate(scratch.arena, (u8 *)cel->pixels, br.at); + inflate((u8 *)cel->pixels, br.at); br_seek_to(&br, chunk_end_pos); } break; diff --git a/src/common.h b/src/common.h index 675ebe70..1981d84e 100644 --- a/src/common.h +++ b/src/common.h @@ -8,12 +8,6 @@ extern "C" { #endif -/* ========================== * - * Configurable constants - * ========================== */ - -#include "config.h" - /* ========================== * * Compiler headers * ========================== */ @@ -368,6 +362,24 @@ struct buffer { u8 *data; }; +struct renderer_handle { + u64 v[1]; +}; + +/* ========================== * + * Tag structs + * ========================== */ + +struct texture_tag { + u128 hash; + struct string path; +}; + +struct sheet_tag { + u128 hash; + struct string path; +}; + /* ========================== * * Buffer utils * ========================== */ @@ -566,6 +578,12 @@ INLINE void __prof_zone_cleanup_func(TracyCZoneCtx *__tracy_ctx) { TracyCZoneEnd #endif /* PROFILING */ +/* ========================== * + * Configurable constants + * ========================== */ + +#include "config.h" + #ifdef __cplusplus } #endif diff --git a/src/config.h b/src/config.h index a976eeab..26044b10 100644 --- a/src/config.h +++ b/src/config.h @@ -39,6 +39,28 @@ #define USER_INTERP_OFFSET_TICK_RATIO 1.1 #define USER_INTERP_ENABLED 1 +/* ========================== * + * Cache limits + * + * (Size of caches before evicting starts) + * ========================== */ + +/* NOTE: Total memory budget should be half of envisioned hard cache memory + * budget. This is because eviction can happen in parallel, so theoretically + * cache entries can load and exist in memory at the same time the old evicted + * record is still around. */ +#define TOTAL_CACHE_MEMORY_BUDGET (MEGABYTE(256)) + +#define TEXTURE_CACHE_MEMORY_BUDGET (MEGABYTE(128)) +#define SHEET_CACHE_MEMORY_BUDGET (MEGABYTE(128)) + +/* Sum of cache budgets must = total cache memory budget */ +CT_ASSERT( + ( + SHEET_CACHE_MEMORY_BUDGET + + TEXTURE_CACHE_MEMORY_BUDGET + ) == TOTAL_CACHE_MEMORY_BUDGET +); /* ========================== * * Settings diff --git a/src/draw.c b/src/draw.c index f9eb21d3..5c4d5e18 100644 --- a/src/draw.c +++ b/src/draw.c @@ -3,9 +3,10 @@ #include "math.h" #include "font.h" #include "scratch.h" +#include "texture.h" GLOBAL struct { - struct renderer_handle solid_white; + struct texture_tag solid_white; } G = { 0 }, DEBUG_ALIAS(G, G_draw); /* ========================== * @@ -17,20 +18,7 @@ struct draw_startup_receipt draw_startup(struct renderer_startup_receipt *render { (UNUSED)renderer_sr; (UNUSED)font_sr; - - /* Generate solid white texture */ - { - struct temp_arena scratch = scratch_begin_no_conflict(); - struct image_rgba image_data = { - .width = 1, - .height = 1, - .pixels = arena_push(scratch.arena, u32) - }; - image_data.pixels[0] = COLOR_WHITE; - G.solid_white = renderer_texture_alloc(image_data); - scratch_end(scratch); - } - + G.solid_white = texture_tag_from_path(STR("res/graphics/white.ase")); return (struct draw_startup_receipt) { 0 }; } @@ -82,8 +70,7 @@ INTERNAL void draw_texture_quad_internal(struct renderer_canvas *canvas, struct void draw_texture_quad(struct renderer_canvas *canvas, struct draw_texture_params params, struct quad quad) { - ASSERT(params.texture); - renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = params.texture->renderer_handle }); + renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_tag = params.texture_tag }); draw_texture_quad_internal(canvas, params.clip, params.tint, quad); } @@ -130,7 +117,7 @@ void draw_solid_poly(struct renderer_canvas *canvas, struct v2_array array, u32 return; } - renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = G.solid_white }); + renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_tag = G.solid_white }); draw_solid_poly_internal(canvas, array, color); } @@ -158,13 +145,13 @@ void draw_solid_circle(struct renderer_canvas *canvas, struct v2 pos, f32 radius void draw_solid_quad(struct renderer_canvas *canvas, struct quad quad, u32 color) { - renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = G.solid_white }); + renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_tag = G.solid_white }); draw_texture_quad_internal(canvas, CLIP_ALL, color, quad); } void draw_solid_rect(struct renderer_canvas *canvas, struct rect rect, u32 color) { - renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = G.solid_white }); + renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_tag = G.solid_white }); struct quad quad = quad_from_rect(rect); draw_texture_quad_internal(canvas, CLIP_ALL, color, quad); } @@ -175,14 +162,14 @@ void draw_solid_rect(struct renderer_canvas *canvas, struct rect rect, u32 color void draw_solid_line(struct renderer_canvas *canvas, struct v2 start, struct v2 end, f32 thickness, u32 color) { - renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = G.solid_white }); + renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_tag = G.solid_white }); struct quad quad = quad_from_line(start, end, thickness); draw_texture_quad_internal(canvas, CLIP_ALL, color, quad); } void draw_solid_ray(struct renderer_canvas *canvas, struct v2 pos, struct v2 rel, f32 thickness, u32 color) { - renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = G.solid_white }); + renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_tag = G.solid_white }); struct quad quad = quad_from_ray(pos, rel, thickness); draw_texture_quad_internal(canvas, CLIP_ALL, color, quad); } @@ -193,7 +180,7 @@ void draw_solid_poly_line(struct renderer_canvas *canvas, struct v2_array array, return; } - renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = G.solid_white }); + renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_tag = G.solid_white }); for (u64 i = 1; i < array.count; ++i) { struct v2 p1 = array.points[i - 1]; struct v2 p2 = array.points[i]; @@ -223,7 +210,7 @@ void draw_solid_rect_line(struct renderer_canvas *canvas, struct rect rect, f32 void draw_solid_arrow_line(struct renderer_canvas *canvas, struct v2 start, struct v2 end, f32 thickness, f32 arrowhead_height, u32 color) { - renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = G.solid_white }); + renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_tag = G.solid_white }); const f32 head_width_ratio = 0.5f; /* Width of arrowhead relative to its length */ @@ -270,7 +257,7 @@ void draw_text(struct renderer_canvas *canvas, struct font *font, struct v2 pos, void draw_text_ex(struct renderer_canvas *canvas, struct font *font, struct v2 pos, f32 scale, struct string str) { - renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = font->texture.renderer_handle }); + renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = font->image_renderer_handle }); struct v2 draw_pos = pos; draw_pos.y += font->point_size * scale; @@ -294,13 +281,13 @@ void draw_text_ex(struct renderer_canvas *canvas, struct font *font, struct v2 p struct clip_rect clip = { { - glyph->atlas_rect.x / font->texture.size.x, - glyph->atlas_rect.y / font->texture.size.y + glyph->atlas_rect.x / font->image_size.x, + glyph->atlas_rect.y / font->image_size.y }, { - (glyph->atlas_rect.x + glyph->atlas_rect.width) / font->texture.size.x, - (glyph->atlas_rect.y + glyph->atlas_rect.height) / font->texture.size.y + (glyph->atlas_rect.x + glyph->atlas_rect.width) / font->image_size.x, + (glyph->atlas_rect.y + glyph->atlas_rect.height) / font->image_size.y } }; diff --git a/src/draw.h b/src/draw.h index 30f8d49a..e15e91b6 100644 --- a/src/draw.h +++ b/src/draw.h @@ -11,8 +11,9 @@ struct font_startup_receipt; .clip = CLIP_ALL, \ __VA_ARGS__ \ }) + struct draw_texture_params { - struct texture *texture; + struct texture_tag texture_tag; struct clip_rect clip; u32 tint; }; diff --git a/src/entity.h b/src/entity.h index 9de8d830..3e88d397 100644 --- a/src/entity.h +++ b/src/entity.h @@ -1,7 +1,7 @@ #ifndef ENTITY_H #define ENTITY_H -#include "mixer.h" +#include "texture.h" #include "sheet.h" #include "mixer.h" @@ -66,7 +66,7 @@ struct entity { /* ====================================================================== */ /* Sprite */ - struct string sprite_name; + struct texture_tag sprite_texture_tag; struct sheet_tag sprite_sheet_tag; struct string sprite_span_name; struct xform sprite_quad_xform; diff --git a/src/font.c b/src/font.c index fe16dac4..336f0a6f 100644 --- a/src/font.c +++ b/src/font.c @@ -121,7 +121,7 @@ INTERNAL WORK_TASK_FUNC_DEF(font_load_asset_task, vparams) resource_close(res); /* Send texture to GPU */ - struct renderer_handle texture_renderer_handle = renderer_texture_alloc(result.image_data); + struct renderer_handle image_renderer_handle = renderer_texture_alloc(result.image_data); /* Allocate store memory */ struct font *font = NULL; @@ -134,10 +134,8 @@ INTERNAL WORK_TASK_FUNC_DEF(font_load_asset_task, vparams) } /* Set font data */ - font->texture = (struct texture) { - .renderer_handle = texture_renderer_handle, - .size = V2(result.image_data.width, result.image_data.height), - }; + font->image_renderer_handle = image_renderer_handle; + font->image_size = V2(result.image_data.width, result.image_data.height); font->glyphs_count = result.glyphs_count; font->point_size = point_size; diff --git a/src/font.h b/src/font.h index 89d2ea72..8cf1aefa 100644 --- a/src/font.h +++ b/src/font.h @@ -21,8 +21,9 @@ struct font_glyph { }; struct font { + struct renderer_handle image_renderer_handle; + struct v2 image_size; f32 point_size; - struct texture texture; u16 glyphs_count; struct font_glyph *glyphs; u16 *lookup; diff --git a/src/game.c b/src/game.c index 55ba0970..872c4a80 100644 --- a/src/game.c +++ b/src/game.c @@ -152,6 +152,21 @@ INTERNAL void recalculate_world_xform_recurse(struct entity *parent) scratch_end(scratch); } +#if 0 +INTERNAL struct v2 sheet_size_meters(struct sheet_tag s) +{ + struct v2 size = { 0 }; + struct sheet_scope *scope = sheet_scope_begin(); + { + struct sheet *sheet = sheet_from_tag_await(scope, s); + size.x = sheet->frame_size.x / (f32)PIXELS_PER_UNIT; + size.y = sheet->frame_size.y / (f32)PIXELS_PER_UNIT; + } + sheet_scope_end(scope); + return size; +} +#endif + INTERNAL void game_update(void) { __prof; @@ -183,22 +198,13 @@ INTERNAL void game_update(void) struct string sprite_name = STR("res/graphics/tim.ase"); struct string sprite_span_name = STR("UNARMED"); + struct texture_tag sprite_texture_tag = texture_tag_from_path(sprite_name); struct sheet_tag sprite_sheet_tag = sheet_tag_from_path(sprite_name); - f32 meters_width, meters_height; - { - struct sheet_scope *scope = sheet_scope_begin(); - { - struct sheet *sheet = sheet_from_tag_await(scope, sprite_sheet_tag); - meters_width = sheet->frame_size.x / (f32)PIXELS_PER_UNIT; - meters_height = sheet->frame_size.y / (f32)PIXELS_PER_UNIT; - } - sheet_scope_end(scope); - } - struct v2 sprite_pos = V2(0, 0); f32 sprite_rot = 0; - struct v2 sprite_size = V2(meters_width, meters_height); + struct v2 sprite_size = V2(0.5f, 0.5f); + // struct v2 sprite_size = sheet_size_meters(sprite_sheet_tag); struct v2 sprite_pivot; { @@ -213,7 +219,7 @@ INTERNAL void game_update(void) sprite_xf = xform_scale(sprite_xf, sprite_size); e->sprite_quad_xform = sprite_xf; - e->sprite_name = sprite_name; + e->sprite_texture_tag = sprite_texture_tag; e->sprite_sheet_tag = sprite_sheet_tag; e->sprite_span_name = sprite_span_name; e->sprite_tint = COLOR_WHITE; @@ -244,22 +250,13 @@ INTERNAL void game_update(void) struct string sprite_name = STR("res/graphics/tim.ase"); struct string sprite_span_name = STR("UNARMED"); + struct texture_tag sprite_texture_tag = texture_tag_from_path(sprite_name); struct sheet_tag sprite_sheet_tag = sheet_tag_from_path(sprite_name); - f32 meters_width, meters_height; - { - struct sheet_scope *scope = sheet_scope_begin(); - { - struct sheet *sheet = sheet_from_tag_await(scope, sprite_sheet_tag); - meters_width = sheet->frame_size.x / (f32)PIXELS_PER_UNIT; - meters_height = sheet->frame_size.y / (f32)PIXELS_PER_UNIT; - } - sheet_scope_end(scope); - } - struct v2 sprite_pos = V2(0, 0); f32 sprite_rot = 0; - struct v2 sprite_size = V2(meters_width, meters_height); + struct v2 sprite_size = V2(0.5f, 0.5f); + // struct v2 sprite_size = sheet_size_meters(sprite_sheet_tag); struct v2 sprite_pivot; { @@ -274,7 +271,7 @@ INTERNAL void game_update(void) sprite_xf = xform_scale(sprite_xf, sprite_size); e->sprite_quad_xform = sprite_xf; - e->sprite_name = sprite_name; + e->sprite_texture_tag = sprite_texture_tag; e->sprite_sheet_tag = sprite_sheet_tag; e->sprite_span_name = sprite_span_name; e->sprite_tint = RGBA_F(0.5, 0.5, 0, 1); @@ -322,23 +319,14 @@ INTERNAL void game_update(void) e->rel_xform = XFORM_POS(V2(-3, -3)); struct string sprite_name = STR("res/graphics/sound.ase"); + struct texture_tag sprite_texture_tag = texture_tag_from_path(sprite_name); struct sheet_tag sprite_sheet_tag = sheet_tag_from_path(sprite_name); - f32 meters_width, meters_height; - { - struct sheet_scope *scope = sheet_scope_begin(); - { - struct sheet *sheet = sheet_from_tag_await(scope, sprite_sheet_tag); - meters_width = sheet->frame_size.x / (f32)PIXELS_PER_UNIT; - meters_height = sheet->frame_size.y / (f32)PIXELS_PER_UNIT; - } - sheet_scope_end(scope); - } - - struct v2 sprite_size = V2(meters_width, meters_height); + struct v2 sprite_size = V2(0.25f, 0.25f); + // struct v2 sprite_size = sheet_size_meters(sprite_sheet_tag); e->sprite_quad_xform = xform_with_scale(XFORM_IDENT, sprite_size); - e->sprite_name = sprite_name; + e->sprite_texture_tag = sprite_texture_tag; e->sprite_sheet_tag = sprite_sheet_tag; e->sprite_tint = RGBA_F(1, 1, 0, 1); diff --git a/src/renderer.h b/src/renderer.h index 63fe5e58..ea89cabc 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -2,7 +2,6 @@ #define RENDERER_H struct sys_window; -struct texture; #define RENDERER_TEXTURE_MAX_WIDTH 16384 #define RENDERER_TEXTURE_MAX_HEIGHT 16384 @@ -11,10 +10,6 @@ typedef u32 vidx; struct renderer_canvas; -struct renderer_handle { - u64 v[1]; -}; - /* ========================== * * Shaders * ========================== */ @@ -27,7 +22,8 @@ enum shader_kind { }; struct texture_shader_parameters { - struct renderer_handle texture; + struct renderer_handle texture_handle; /* Overrides texture_tag */ + struct texture_tag texture_tag; }; struct texture_shader_vertex { @@ -80,7 +76,7 @@ void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_cou * ========================== */ struct renderer_handle renderer_texture_alloc(struct image_rgba data); - void renderer_texture_release(struct renderer_handle handle); +b32 renderer_texture_is_nil(struct renderer_handle handle); #endif diff --git a/src/renderer_d3d11.c b/src/renderer_d3d11.c index 46b4a86f..d5a57298 100644 --- a/src/renderer_d3d11.c +++ b/src/renderer_d3d11.c @@ -7,6 +7,7 @@ #include "math.h" #include "inc.h" #include "tar.h" +#include "texture.h" #include #define CINTERFACE @@ -57,7 +58,9 @@ struct dx11_buffer { struct renderer_cmd { struct dx11_shader *shader; - struct renderer_handle texture; + + struct renderer_handle texture_handle; /* Overrides texture_tag */ + struct texture_tag texture_tag; /* Associated buffer data */ u32 vertex_count; @@ -275,9 +278,15 @@ INTERNAL void *handle_data(struct renderer_handle handle) INTERNAL b32 handle_eq(struct renderer_handle h1, struct renderer_handle h2) { + CT_ASSERT(sizeof(struct renderer_handle) == 8); return h1.v[0] == h2.v[0]; } +INTERNAL b32 handle_is_nil(struct renderer_handle h) +{ + return h.v[0] == 0; +} + /* ========================== * * Shader * ========================== */ @@ -693,12 +702,15 @@ u32 renderer_canvas_push_vertices(struct renderer_canvas *canvas, u8 **vertices_ void renderer_canvas_ensure_texture_cmd(struct renderer_canvas *canvas, struct texture_shader_parameters params) { struct renderer_cmd *last_cmd = canvas->cpu_cmd_store.cmd_last; - if (!last_cmd || last_cmd->shader->kind != SHADER_TEXTURE || !handle_eq(last_cmd->texture, params.texture)) { + if (!last_cmd || last_cmd->shader->kind != SHADER_TEXTURE || + !handle_eq(last_cmd->texture_handle, params.texture_handle) || + !texture_tag_eq(last_cmd->texture_tag, params.texture_tag)) { /* Command parameters are not the same, insert new command */ struct renderer_cmd *cmd = arena_push(&canvas->cpu_cmd_store.arena, struct renderer_cmd); *cmd = (struct renderer_cmd){ .shader = &G.shaders[SHADER_TEXTURE], - .texture = params.texture + .texture_handle = params.texture_handle, + .texture_tag = params.texture_tag }; if (!canvas->cpu_cmd_store.cmd_first) { @@ -829,6 +841,8 @@ void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_cou { __prof; + struct texture_scope *texture_scope = texture_scope_begin(); + /* Resize back buffer */ if (!v2_eq(G.backbuffer_size, screen_size)) { resize_backbuffer(screen_size); @@ -864,7 +878,13 @@ void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_cou for (struct renderer_cmd *cmd = canvas->gpu_cmd_store.cmd_first; cmd; cmd = cmd->next) { struct dx11_shader *shader = cmd->shader; struct dx11_buffer *buffer = &canvas->buffers[shader->kind]; - struct renderer_handle texture_handle = cmd->texture; + + struct renderer_handle texture_handle; + if (handle_is_nil(cmd->texture_handle)) { + texture_handle = texture_from_tag_async(texture_scope, cmd->texture_tag)->renderer_handle; + } else { + texture_handle = cmd->texture_handle; + } /* Activate shader */ if (shader != last_shader) { @@ -904,6 +924,8 @@ void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_cou __profframe(0); } renderer_capture_image_for_profiler(viewport.width, viewport.height); + + texture_scope_end(texture_scope); } /* ========================== * @@ -964,6 +986,11 @@ void renderer_texture_release(struct renderer_handle handle) handle_release(handle); } +b32 renderer_texture_is_nil(struct renderer_handle handle) +{ + return handle_is_nil(handle); +} + /* ========================== * * Profiling frame capture * ========================== */ @@ -1054,9 +1081,11 @@ INTERNAL void renderer_capture_image_for_profiler(f32 width, f32 height) } } #else + INTERNAL void renderer_capture_image_for_profiler(f32 width, f32 height) { (UNUSED)width; (UNUSED)height; } + #endif diff --git a/src/resource.c b/src/resource.c index 81e6a691..aeb2f5e7 100644 --- a/src/resource.c +++ b/src/resource.c @@ -19,7 +19,6 @@ struct resource_startup_receipt resource_startup(void) { #if RESOURCES_EMBEDDED struct buffer embedded_data = inc_res_tar(); - //struct buffer embedded_data = ((struct buffer) { (u8 *)(_incbin_res_tar_end) - (u8 *)(_incbin_res_tar_start), (u8 *)_incbin_res_tar_start });; G.arena = arena_alloc(GIGABYTE(64)); if (embedded_data.size <= 0) { sys_panic(STR("No embedded resources found")); @@ -55,7 +54,7 @@ struct resource resource_open(struct string path) .bytes = entry ? entry->buff : BUFFER(0, 0) }; #else - struct sys_file file = sys_file_open_read(path); + struct sys_file file = sys_file_open_read_wait(path); struct sys_file_map file_map = sys_file_map_open_read(file); struct buffer bytes = sys_file_map_data(file_map); return (struct resource) { diff --git a/src/sheet.c b/src/sheet.c index 6958c8cc..6fd58bc4 100644 --- a/src/sheet.c +++ b/src/sheet.c @@ -4,14 +4,12 @@ #include "sys.h" #include "scratch.h" #include "resource.h" -#include "asset_cache.h" #include "ase.h" #include "util.h" #include "work.h" #include "atomic.h" #include "thread_local.h" #include "app.h" -#include "intrinsics.h" #define SHEET_ARENA_RESERVE MEGABYTE(64) #define SHEET_LOOKUP_TABLE_BUCKET_RATIO 2.0 @@ -21,13 +19,10 @@ #define MAX_LOADER_THREADS 4 -/* Size of cache memory until evictor starts evicting */ -#define CACHE_MEMORY_BUDGET MEGABYTE(8) - /* How long between evictor thread scans */ -#define EVICTOR_CYCLE_INTERVAL 0.500 +#define EVICTOR_CYCLE_INTERVAL (RESOURCE_RELOADING ? 0.100 : 0.500) -/* Time a cache entry spends unused until it's considered evictable (at granularity of EVICTOR_CYCLE_INTERVAL) */ +/* Time a cache entry spends unused until it's considered evictable (rounded up to multiple of of EVICTOR_CYCLE_INTERVAL) */ #define EVICTOR_GRACE_PERIOD 10.000 /* ========================== * @@ -168,12 +163,8 @@ INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(sheet_shutdown); INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_loader_thread_entry_point, arg); INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_evictor_thread_entry_point, arg); -struct sheet_startup_receipt sheet_startup(struct work_startup_receipt *work_sr, - struct asset_cache_startup_receipt *asset_cache_sr, - struct resource_startup_receipt *resource_sr) +struct sheet_startup_receipt sheet_startup(struct resource_startup_receipt *resource_sr) { - (UNUSED)work_sr; - (UNUSED)asset_cache_sr; (UNUSED)resource_sr; G.cache.node_pool_mutex = sys_mutex_alloc(); @@ -201,7 +192,7 @@ struct sheet_startup_receipt sheet_startup(struct work_startup_receipt *work_sr, } scratch_end(scratch); } - G.evictor_thread = sys_thread_alloc(sheet_evictor_thread_entry_point, NULL, STR("[P0] Sheet evictor thread")); + G.evictor_thread = sys_thread_alloc(sheet_evictor_thread_entry_point, NULL, STR("[P0] Sheet evictor")); app_register_exit_callback(&sheet_shutdown); @@ -249,6 +240,28 @@ struct sheet_tag sheet_tag_from_path(struct string path) return res; } +/* ========================== * + * Refcount + * ========================== */ + +INTERNAL void node_refcount_add(struct cache_node *n, i32 amount) +{ + u32 evictor_cycle = atomic_u32_eval(&G.evictor_cycle); + struct atomic_u64 *refcount_atomic = &n->refcount_struct; + u64 old_refcount_uncast = atomic_u64_eval(refcount_atomic); + do { + struct cache_node_refcount new_refcount = *(struct cache_node_refcount *)&old_refcount_uncast; + new_refcount.count += amount; + new_refcount.last_modified_cycle = evictor_cycle; + u64 v = atomic_u64_eval_compare_exchange(refcount_atomic, old_refcount_uncast, *(u64 *)&new_refcount); + if (v != old_refcount_uncast) { + old_refcount_uncast = v; + } else { + break; + } + } while (true); +} + /* ========================== * * Load * ========================== */ @@ -315,9 +328,6 @@ INTERNAL void sheet_load(struct cache_node *n, struct sheet_tag tag) decoded = ase_decode_sheet(scratch.arena, sheet_rs.bytes); #if RESOURCE_RELOADING n->initial_resource_file_modified_time = sys_file_get_time(sheet_rs.file).modified; - u64 cpy_len = min_u64(tag.path.len, ARRAY_COUNT(n->tag_path)); - n->tag_path_len = cpy_len; - MEMCPY(n->tag_path, tag.path.text, cpy_len); #endif resource_close(sheet_rs); @@ -329,14 +339,20 @@ INTERNAL void sheet_load(struct cache_node *n, struct sheet_tag tag) logf_error("Resource \"%F\" not found", path); } } +#if RESOURCE_RELOADING + u64 cpy_len = min_u64(tag.path.len, ARRAY_COUNT(n->tag_path)); + n->tag_path_len = cpy_len; + MEMCPY(n->tag_path, tag.path.text, cpy_len); +#endif arena_set_readonly(&n->arena); n->memory_usage = n->arena.committed; atomic_u64_eval_add(&G.cache.memory_usage, n->memory_usage); - logf_info("Finished loading sheet \"%F\" in %F seconds (final size: %F bytes).", + logf_info("Finished loading sheet \"%F\" in %F seconds (cache size: %F bytes).", FMT_STR(path), FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts)), - FMT_UINT(n->arena.pos)); + FMT_UINT(n->memory_usage)); + atomic_u32_eval_exchange(&n->state, CACHE_NODE_STATE_LOADED); @@ -364,19 +380,7 @@ INTERNAL void scope_ensure_reference(struct sheet_scope *scope, struct cache_nod if (!ref) { /* Increment refcount */ - u32 evictor_cycle = atomic_u32_eval(&G.evictor_cycle); - u64 old_refcount_uncast = atomic_u64_eval(&cache_node->refcount_struct); - do { - struct cache_node_refcount new_refcount = *(struct cache_node_refcount *)&old_refcount_uncast; - new_refcount.count += 1; - new_refcount.last_modified_cycle = evictor_cycle; - u64 v = atomic_u64_eval_compare_exchange(&cache_node->refcount_struct, old_refcount_uncast, *(u64 *)&new_refcount); - if (v != old_refcount_uncast) { - old_refcount_uncast = v; - } else { - break; - } - } while (true); + node_refcount_add(cache_node, 1); /* Add reference to scope */ struct sheet_tctx *tctx = thread_local_var_eval(&tl_sheet_tctx); if (tctx->first_free_reference) { @@ -418,20 +422,7 @@ void sheet_scope_end(struct sheet_scope *scope) struct sheet_scope_reference *ref = scope->reference_buckets[i]; while (ref) { /* Decrement refcount */ - struct cache_node *cache_node = ref->cache_node; - u32 evictor_cycle = atomic_u32_eval(&G.evictor_cycle); - u64 old_refcount_uncast = atomic_u64_eval(&cache_node->refcount_struct); - do { - struct cache_node_refcount new_refcount = *(struct cache_node_refcount *)&old_refcount_uncast; - new_refcount.count -= 1; - new_refcount.last_modified_cycle = evictor_cycle; - u64 v = atomic_u64_eval_compare_exchange(&cache_node->refcount_struct, old_refcount_uncast, *(u64 *)&new_refcount); - if (v != old_refcount_uncast) { - old_refcount_uncast = v; - } else { - break; - } - } while (true); + node_refcount_add(ref->cache_node, -1); /* Add reference to free list */ ref->next_free = tctx->first_free_reference; tctx->first_free_reference = ref; @@ -530,8 +521,9 @@ INTERNAL struct sheet *sheet_from_tag_internal(struct sheet_scope *scope, struct if (G.first_free_loader_cmd) { cmd = G.first_free_loader_cmd; G.first_free_loader_cmd = cmd->next_free; + MEMZERO_STRUCT(cmd); } else { - cmd = arena_push(&G.loader_cmd_arena, struct loader_cmd); + cmd = arena_push_zero(&G.loader_cmd_arena, struct loader_cmd); } /* Initialize cmd */ @@ -547,6 +539,9 @@ INTERNAL struct sheet *sheet_from_tag_internal(struct sheet_scope *scope, struct *(G.last_loader_cmd ? &G.last_loader_cmd->next : &G.first_loader_cmd) = cmd; G.last_loader_cmd = cmd; + /* Cmd holds reference to node */ + node_refcount_add(n, 1); + /* Signal work ready */ sys_condition_variable_signal(&G.loaders_cv); } @@ -641,6 +636,9 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_loader_thread_entry_point, arg) /* Free cmd */ cmd->next_free = G.first_free_loader_cmd; G.first_free_loader_cmd = cmd; + + /* Cmd no longer references node */ + node_refcount_add(cmd->cache_node, -1); } sys_mutex_unlock(&G.loaders_mutex); @@ -684,11 +682,10 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_evictor_thread_entry_point, arg) } if (!G.evictor_shutdown) { - sys_timestamp_t cur_timestamp = sys_timestamp(); - f64 cur_time = sys_timestamp_seconds(cur_timestamp); + u32 cur_cycle = *atomic_u32_raw(&G.evictor_cycle); /* Scan for evictable nodes */ - b32 cache_over_budget = atomic_u64_eval(&G.cache.memory_usage) > CACHE_MEMORY_BUDGET; + b32 cache_over_budget = atomic_u64_eval(&G.cache.memory_usage) > SHEET_CACHE_MEMORY_BUDGET; if (cache_over_budget || RESOURCE_RELOADING) { __profscope(eviction_scan); for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) { @@ -699,61 +696,58 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_evictor_thread_entry_point, arg) while (n) { b32 consider_for_eviction = false; b32 force_evict = false; - if (atomic_u32_eval(&n->state) == CACHE_NODE_STATE_LOADED) { - u64 refcount_uncast = atomic_u64_eval(&n->refcount_struct); - struct cache_node_refcount refcount = *(struct cache_node_refcount *)&refcount_uncast; - if (refcount.count <= 0) { + u64 refcount_uncast = atomic_u64_eval(&n->refcount_struct); + struct cache_node_refcount refcount = *(struct cache_node_refcount *)&refcount_uncast; + if (refcount.count <= 0) { #if RESOURCE_RELOADING - /* Check if file changed for resource reloading */ - if (!consider_for_eviction) { - struct string path = string_from_cstr_len((char *)n->tag_path, n->tag_path_len); - b32 file_changed = false; - if (!sys_is_file(path)) { - file_changed = true; - } else { - struct sys_datetime current_file_time; - { - struct sys_file file = sys_file_open_read(path); - current_file_time = sys_file_get_time(file).modified; - sys_file_close(file); - } - file_changed = MEMCMP_STRUCT(&n->initial_resource_file_modified_time, ¤t_file_time) != 0; - } - if (file_changed) { - logf_info("Resource file for sheet \"%F\" has changed. Evicting to allow for reloading.", FMT_STR(path)); - consider_for_eviction = true; - force_evict = true; - } - } -#endif - - /* Check usage time */ -#if RESOURCE_RELOADING - if (cache_over_budget) /* Only check conditional if RESOURCE_RELOADING, since over-budget is assumed to be true otherwise */ -#endif + /* Check if file changed for resource reloading */ + if (!consider_for_eviction) { + struct string path = string_from_cstr_len((char *)n->tag_path, n->tag_path_len); + b32 file_changed = false; + struct sys_datetime current_file_time; { - f64 last_used_time = (f64)refcount.last_modified_cycle * EVICTOR_CYCLE_INTERVAL; - if (cur_time - last_used_time > EVICTOR_GRACE_PERIOD) { - /* Cache is over budget and node hasn't been referenced in a while */ - consider_for_eviction = true; - } + struct sys_file file = sys_file_open_read(path); + current_file_time = sys_file_get_time(file).modified; + sys_file_close(file); } + file_changed = MEMCMP_STRUCT(&n->initial_resource_file_modified_time, ¤t_file_time) != 0; + if (file_changed) { + logf_info("Resource file for sheet \"%F\" has changed. Evicting to allow for reloading.", FMT_STR(path)); + consider_for_eviction = true; + force_evict = true; + } + } +#endif + /* Check usage time */ +#if RESOURCE_RELOADING + /* Only check conditional if * RESOURCE_RELOADING is enabled, + * since over-budget is assumed to be * true otherwise */ + if (cache_over_budget) +#endif + { + u32 last_used_cycle = refcount.last_modified_cycle; + f64 time_since_use = (f64)(cur_cycle - last_used_cycle) * EVICTOR_CYCLE_INTERVAL; + if (time_since_use > EVICTOR_GRACE_PERIOD) { + /* Cache is over budget and node hasn't been referenced in a while */ + consider_for_eviction = true; + } } - /* Add node to evict list */ - if (consider_for_eviction) { - struct evict_node *evict_node = arena_push_zero(scratch.arena, struct evict_node); - evict_node->cache_node = n; - evict_node->cache_bucket = bucket; - evict_node->refcount = refcount; - evict_node->force_evict = force_evict; - evict_node->next_consider = head_consider; - head_consider = evict_node; - } - - n = n->next_hash; } + + /* Add node to evict list */ + if (consider_for_eviction) { + struct evict_node *evict_node = arena_push_zero(scratch.arena, struct evict_node); + evict_node->cache_node = n; + evict_node->cache_bucket = bucket; + evict_node->refcount = refcount; + evict_node->force_evict = force_evict; + evict_node->next_consider = head_consider; + head_consider = evict_node; + } + + n = n->next_hash; } sys_rw_mutex_unlock_shared(&bucket->rw_mutex); } @@ -793,7 +787,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_evictor_thread_entry_point, arg) struct cache_node_refcount refcount = *(struct cache_node_refcount *)atomic_u64_raw(&n->refcount_struct); if (refcount.count > 0 || ((refcount.last_modified_cycle != en->refcount.last_modified_cycle) && !en->force_evict)) { /* Cache node has been referenced since scan, skip eviction. */ - } else if (en->force_evict || atomic_u64_eval(&G.cache.memory_usage) > CACHE_MEMORY_BUDGET) { + } else if (en->force_evict || atomic_u64_eval(&G.cache.memory_usage) > SHEET_CACHE_MEMORY_BUDGET) { /* Remove from cache bucket */ if (n->prev_hash) { n->prev_hash->next_hash = n->next_hash; diff --git a/src/sheet.h b/src/sheet.h index ba0f282e..21aa03b2 100644 --- a/src/sheet.h +++ b/src/sheet.h @@ -3,17 +3,9 @@ #include "util.h" -struct asset; struct sheet_frame; -struct work_startup_receipt; -struct asset_cache_startup_receipt; struct resource_startup_receipt; -struct sheet_tag { - u128 hash; - struct string path; -}; - struct sheet { b32 loading; struct v2 image_size; @@ -29,9 +21,7 @@ struct sheet { * ========================== */ struct sheet_startup_receipt { i32 _; }; -struct sheet_startup_receipt sheet_startup(struct work_startup_receipt *work_sr, - struct asset_cache_startup_receipt *asset_cache_srm, - struct resource_startup_receipt *resource_sr); +struct sheet_startup_receipt sheet_startup(struct resource_startup_receipt *resource_sr); /* ========================== * * Tag diff --git a/src/sys.h b/src/sys.h index 7b11d577..7b46e025 100644 --- a/src/sys.h +++ b/src/sys.h @@ -215,6 +215,7 @@ b32 sys_is_dir(struct string path); void sys_mkdir(struct string path); struct sys_file sys_file_open_read(struct string path); +struct sys_file sys_file_open_read_wait(struct string path); /* Waits until file is not being used by another program */ struct sys_file sys_file_open_write(struct string path); struct sys_file sys_file_open_append(struct string path); void sys_file_close(struct sys_file file); diff --git a/src/sys_win32.c b/src/sys_win32.c index ce392cde..b5d6089d 100644 --- a/src/sys_win32.c +++ b/src/sys_win32.c @@ -363,9 +363,8 @@ struct sys_file sys_file_open_read(struct string path) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); - wchar_t *path_wstr = wstr_from_string(scratch.arena, path); - /* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */ + wchar_t *path_wstr = wstr_from_string(scratch.arena, path); HANDLE handle = CreateFileW( path_wstr, GENERIC_READ, @@ -375,16 +374,43 @@ struct sys_file sys_file_open_read(struct string path) FILE_ATTRIBUTE_NORMAL, NULL ); + scratch_end(scratch); struct sys_file file = (struct sys_file) { (u64)handle }; return file; } +struct sys_file sys_file_open_read_wait(struct string path) +{ + __prof; + struct temp_arena scratch = scratch_begin_no_conflict(); + wchar_t *path_wstr = wstr_from_string(scratch.arena, path); + + i32 delay_ms = 1; + HANDLE handle; + while ((handle = CreateFileW(path_wstr, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) { + if (GetLastError() == ERROR_SHARING_VIOLATION) { + __profscope(file_share_conflict_delay); + Sleep(delay_ms); + if (delay_ms < 1024) { + delay_ms *= 2; + } + } else { + break; + } + } + + scratch_end(scratch); + return (struct sys_file) { (u64)handle }; +} + struct sys_file sys_file_open_write(struct string path) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *path_wstr = wstr_from_string(scratch.arena, path); + + /* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */ HANDLE handle = CreateFileW( path_wstr, GENERIC_WRITE, @@ -394,10 +420,9 @@ struct sys_file sys_file_open_write(struct string path) FILE_ATTRIBUTE_NORMAL, NULL ); - /* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */ + scratch_end(scratch); - struct sys_file file = (struct sys_file) { (u64)handle }; - return file; + return (struct sys_file) { (u64)handle }; } struct sys_file sys_file_open_append(struct string path) @@ -405,6 +430,7 @@ struct sys_file sys_file_open_append(struct string path) __prof; struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *path_wstr = wstr_from_string(scratch.arena, path); + /* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */ HANDLE handle = CreateFileW( path_wstr, FILE_APPEND_DATA, @@ -414,7 +440,6 @@ struct sys_file sys_file_open_append(struct string path) FILE_ATTRIBUTE_NORMAL, NULL ); - /* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */ scratch_end(scratch); struct sys_file file = (struct sys_file) { (u64)handle }; return file; @@ -450,6 +475,7 @@ struct buffer sys_file_read_all(struct arena *arena, struct sys_file file) NULL ); } + return buff; } @@ -491,26 +517,29 @@ struct sys_file_time sys_file_get_time(struct sys_file file) FILETIME ft_created; FILETIME ft_accessed; FILETIME ft_modified; - GetFileTime((HANDLE)file.handle, &ft_created, &ft_accessed, &ft_modified); + b32 success = !!GetFileTime((HANDLE)file.handle, &ft_created, &ft_accessed, &ft_modified); + if (success) { + /* Convert file times to local file time */ + FileTimeToLocalFileTime(&ft_created, &ft_created); + FileTimeToLocalFileTime(&ft_accessed, &ft_accessed); + FileTimeToLocalFileTime(&ft_modified, &ft_modified); - /* Convert file times to local file time */ - FileTimeToLocalFileTime(&ft_created, &ft_created); - FileTimeToLocalFileTime(&ft_accessed, &ft_accessed); - FileTimeToLocalFileTime(&ft_modified, &ft_modified); + /* Convert local file times to system times */ + SYSTEMTIME st_created; + SYSTEMTIME st_accessed; + SYSTEMTIME st_modified; + FileTimeToSystemTime(&ft_created, &st_created); + FileTimeToSystemTime(&ft_accessed, &st_accessed); + FileTimeToSystemTime(&ft_modified, &st_modified); - /* Convert local file times to system times */ - SYSTEMTIME st_created; - SYSTEMTIME st_accessed; - SYSTEMTIME st_modified; - FileTimeToSystemTime(&ft_created, &st_created); - FileTimeToSystemTime(&ft_accessed, &st_accessed); - FileTimeToSystemTime(&ft_modified, &st_modified); - - return (struct sys_file_time) { - .created = win32_time_to_sys_time(st_created), - .accessed = win32_time_to_sys_time(st_accessed), - .modified = win32_time_to_sys_time(st_modified) - }; + return (struct sys_file_time) { + .created = win32_time_to_sys_time(st_created), + .accessed = win32_time_to_sys_time(st_accessed), + .modified = win32_time_to_sys_time(st_modified) + }; + } else { + return (struct sys_file_time) { 0 }; + } } /* ========================== * @@ -536,7 +565,6 @@ struct sys_file_map sys_file_map_open_read(struct sys_file file) ); if (!map_handle) { - ASSERT(false); return (struct sys_file_map) { 0 }; } @@ -1839,6 +1867,7 @@ u32 sys_rand_u32(void) void sys_panic(struct string msg) { + ASSERT(false); if (atomic_i32_eval_compare_exchange(&G.panicking, 0, 1) == 0) { log_panic(msg); diff --git a/src/texture.c b/src/texture.c index 1586e21b..4076c112 100644 --- a/src/texture.c +++ b/src/texture.c @@ -1,29 +1,102 @@ #include "texture.h" #include "arena.h" -#include "memory.h" -#include "renderer.h" -#include "util.h" -#include "asset_cache.h" +#include "log.h" #include "sys.h" #include "scratch.h" #include "resource.h" #include "ase.h" -#include "log.h" +#include "util.h" +#include "work.h" +#include "atomic.h" +#include "thread_local.h" +#include "app.h" +#include "renderer.h" -#define LOOKUP_TABLE_CAPACITY_FACTOR 2.0f +/* Arena only used to store texture struct at the moment. Actual image data is allocated on GPU. */ +#define TEXTURE_ARENA_RESERVE MEGABYTE(1) -struct texture_task_params { - struct texture_task_params *next_free; +#define TCTX_ARENA_RESERVE MEGABYTE(64) +#define CACHE_BUCKETS_COUNT 1024 - struct asset *asset; - u64 path_len; - char path_cstr[1024]; +#define MAX_LOADER_THREADS 4 + +/* How long between evictor thread scans */ +#define EVICTOR_CYCLE_INTERVAL (RESOURCE_RELOADING ? 0.100 : 0.500) + +/* Time a cache entry spends unused until it's considered evictable (rounded up to multiple of of EVICTOR_CYCLE_INTERVAL) */ +#define EVICTOR_GRACE_PERIOD 10.000 + +/* ========================== * + * Loader cmd structs + * ========================== */ + +struct loader_cmd { + struct loader_cmd *next; + struct loader_cmd *next_free; + + struct cache_node *cache_node; + struct texture_tag tag; + u8 tag_path_buff[512]; }; -struct texture_task_params_store { - struct texture_task_params *head_free; +/* ========================== * + * Cache structs + * ========================== */ + +enum cache_node_state { + CACHE_NODE_STATE_NONE, + CACHE_NODE_STATE_QUEUED, + CACHE_NODE_STATE_WORKING, + CACHE_NODE_STATE_LOADED +}; + +struct cache_node_refcount { + i32 count; /* Number of scopes currently holding a reference to this texture */ + u32 last_modified_cycle; /* Last time that refcount was modified */ +}; +CT_ASSERT(sizeof(struct cache_node_refcount) == 8); /* Must fit into 64 bit atomic */ + +struct cache_node { + u128 hash; + struct atomic_u32 state; + struct atomic_u64 refcount_struct; /* Cast eval to `cache_node_refcount` */ + + /* Allocated data */ + u64 memory_usage; struct arena arena; - struct sys_mutex mutex; + struct texture *texture; + + /* Hash list */ + struct cache_node *next_hash; + struct cache_node *prev_hash; + + /* Free list */ + struct cache_node *next_free; + +#if RESOURCE_RELOADING + struct sys_datetime initial_resource_file_modified_time; + u64 tag_path_len; + u8 tag_path[4096]; +#endif +}; + +struct cache_bucket { + struct sys_rw_mutex rw_mutex; + struct cache_node *first; +}; + +struct cache { + struct atomic_u64 memory_usage; + struct arena arena; + struct cache_bucket *buckets; + struct sys_mutex node_pool_mutex; + struct cache_node *node_pool_first_free; +}; + +struct texture_scope_reference { + struct cache_node *cache_node; + struct texture_scope_reference *next_hash; + struct texture_scope_reference *next_free; }; /* ========================== * @@ -31,67 +104,61 @@ struct texture_task_params_store { * ========================== */ GLOBAL struct { - struct texture_task_params_store params; + struct arena perm_texture_arena; + struct texture *nil_texture; + struct texture *loading_texture; + + /* Cache */ + struct cache cache; + + /* Loader threads */ + b32 loaders_shutdown; + struct sys_mutex loaders_mutex; + struct sys_condition_variable loaders_cv; + struct arena loader_cmd_arena; + struct loader_cmd *first_free_loader_cmd; + struct loader_cmd *first_loader_cmd; + struct loader_cmd *last_loader_cmd; + u64 loader_threads_count; + struct sys_thread loader_threads[MAX_LOADER_THREADS]; + + /* Evictor thread */ + struct atomic_u32 evictor_cycle; + b32 evictor_shutdown; + struct sys_mutex evictor_mutex; + struct sys_condition_variable evictor_cv; + + struct sys_thread evictor_thread; } G = { 0 }, DEBUG_ALIAS(G, G_texture); /* ========================== * - * Startup + * Thread local state * ========================== */ -struct texture_startup_receipt texture_startup(struct work_startup_receipt *work_sr, - struct renderer_startup_receipt *renderer_sr, - struct asset_cache_startup_receipt *asset_cache_sr, - struct resource_startup_receipt *resource_sr) +struct texture_tctx { + struct arena arena; + struct texture_scope *first_free_scope; + struct texture_scope_reference *first_free_reference; +}; + +INTERNAL THREAD_LOCAL_VAR_ALLOC_FUNC_DEF(texture_tctx_alloc, vtctx) { - (UNUSED)work_sr; - (UNUSED)renderer_sr; - (UNUSED)asset_cache_sr; - (UNUSED)resource_sr; - - G.params.arena = arena_alloc(GIGABYTE(64)); - G.params.mutex = sys_mutex_alloc(); - - return (struct texture_startup_receipt) { 0 }; + struct texture_tctx *tctx = (struct texture_tctx *)vtctx; + tctx->arena = arena_alloc(MEGABYTE(64)); } +INTERNAL THREAD_LOCAL_VAR_RELEASE_FUNC_DEF(texture_tctx_release, vtctx) +{ + struct texture_tctx *tctx = (struct texture_tctx *)vtctx; + arena_release(&tctx->arena); +} + +GLOBAL THREAD_LOCAL_VAR_DEF(tl_texture_tctx, struct texture_tctx, texture_tctx_alloc, texture_tctx_release); + /* ========================== * - * Load task param store + * Purple-black image * ========================== */ -INTERNAL struct texture_task_params *texture_task_params_alloc(void) -{ - struct texture_task_params *p = NULL; - { - sys_mutex_lock(&G.params.mutex); - { - if (G.params.head_free) { - p = G.params.head_free; - G.params.head_free = p->next_free; - } else { - p = arena_push_zero(&G.params.arena, struct texture_task_params); - } - } - sys_mutex_unlock(&G.params.mutex); - } - return p; -} - -INTERNAL void texture_task_params_release(struct texture_task_params *p) -{ - sys_mutex_lock(&G.params.mutex); - { - p->next_free = G.params.head_free; - G.params.head_free = p; - } - sys_mutex_unlock(&G.params.mutex); -} - -/* ========================== * - * Default texture load - * ========================== */ - -#define DEFAULT_TEXTURE_NAME "__texture_default" - INTERNAL struct image_rgba generate_purple_black_image(struct arena *arena, u32 width, u32 height) { u32 *pixels = arena_push_array(arena, u32, width * height); @@ -126,160 +193,656 @@ INTERNAL struct image_rgba generate_purple_black_image(struct arena *arena, u32 }; } +/* ========================== * + * Startup + * ========================== */ + +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(texture_shutdown); +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_loader_thread_entry_point, arg); +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_evictor_thread_entry_point, arg); + +struct texture_startup_receipt texture_startup(struct renderer_startup_receipt *renderer_sr, + struct resource_startup_receipt *resource_sr) +{ + (UNUSED)renderer_sr; + (UNUSED)resource_sr; + + { + G.perm_texture_arena = arena_alloc(MEGABYTE(1)); + + /* Init nil texture */ + G.nil_texture = arena_push_zero(&G.perm_texture_arena, struct texture); + { + struct temp_arena scratch = scratch_begin_no_conflict(); + struct image_rgba purple_black_image = generate_purple_black_image(scratch.arena, 64, 64); + G.nil_texture->renderer_handle = renderer_texture_alloc(purple_black_image); + scratch_end(scratch); + } + + /* Init loading texture */ + G.loading_texture = arena_push_zero(&G.perm_texture_arena, struct texture); + G.loading_texture->loading = true; + + arena_set_readonly(&G.perm_texture_arena); + } + + + G.cache.node_pool_mutex = sys_mutex_alloc(); + G.cache.arena = arena_alloc(GIGABYTE(64)); + G.cache.buckets = arena_push_array_zero(&G.cache.arena, struct cache_bucket, CACHE_BUCKETS_COUNT); + for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) { + G.cache.buckets[i].rw_mutex = sys_rw_mutex_alloc(); + } + + G.loader_cmd_arena = arena_alloc(GIGABYTE(64)); + G.loaders_mutex = sys_mutex_alloc(); + G.loaders_cv = sys_condition_variable_alloc(); + + G.evictor_mutex = sys_mutex_alloc(); + G.evictor_cv = sys_condition_variable_alloc(); + + { + struct temp_arena scratch = scratch_begin_no_conflict(); + G.loader_threads_count = clamp_i64(1, MAX_LOADER_THREADS, sys_num_logical_processors() - 1); + for (u64 i = 0; i < G.loader_threads_count; ++i) { + struct string thread_name = string_format(scratch.arena, + STR("[P0] Texture loader %F"), + FMT_UINT(i)); + G.loader_threads[i] = sys_thread_alloc(texture_loader_thread_entry_point, NULL, thread_name); + } + scratch_end(scratch); + } + G.evictor_thread = sys_thread_alloc(texture_evictor_thread_entry_point, NULL, STR("[P0] Texture evictor")); + + app_register_exit_callback(&texture_shutdown); + + return (struct texture_startup_receipt) { 0 }; +} + +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(texture_shutdown) +{ + __prof; + + /* Signal loaders shutdown */ + sys_mutex_lock(&G.loaders_mutex); + { + G.loaders_shutdown = true; + sys_condition_variable_broadcast(&G.loaders_cv); + } + sys_mutex_unlock(&G.loaders_mutex); + + /* Signal evictor shutdown */ + sys_mutex_lock(&G.evictor_mutex); + { + G.evictor_shutdown = true; + sys_condition_variable_broadcast(&G.evictor_cv); + } + sys_mutex_unlock(&G.evictor_mutex); + + + /* Wait on threads */ + for (u64 i = 0; i < G.loader_threads_count; ++i) { + sys_thread_wait_release(&G.loader_threads[i]); + } + sys_thread_wait_release(&G.evictor_thread); +} + +/* ========================== * + * Tag + * ========================== */ + +struct texture_tag texture_tag_from_path(struct string path) +{ + struct texture_tag res = { 0 }; + res.hash = HASH_FNV128_BASIS; + res.hash = hash_fnv128(res.hash, BUFFER_FROM_STRING(path)); + res.path = path; + return res; +} + +b32 texture_tag_is_nil(struct texture_tag tag) +{ + return tag.hash == 0; +} + +b32 texture_tag_eq(struct texture_tag t1, struct texture_tag t2) +{ + return t1.hash == t2.hash; +} + +/* ========================== * + * Refcount + * ========================== */ + +INTERNAL void node_refcount_add(struct cache_node *n, i32 amount) +{ + u32 evictor_cycle = atomic_u32_eval(&G.evictor_cycle); + struct atomic_u64 *refcount_atomic = &n->refcount_struct; + u64 old_refcount_uncast = atomic_u64_eval(refcount_atomic); + do { + struct cache_node_refcount new_refcount = *(struct cache_node_refcount *)&old_refcount_uncast; + new_refcount.count += amount; + new_refcount.last_modified_cycle = evictor_cycle; + u64 v = atomic_u64_eval_compare_exchange(refcount_atomic, old_refcount_uncast, *(u64 *)&new_refcount); + if (v != old_refcount_uncast) { + old_refcount_uncast = v; + } else { + break; + } + } while (true); +} + /* ========================== * * Load * ========================== */ -INTERNAL WORK_TASK_FUNC_DEF(texture_load_asset_task, vparams) +INTERNAL void texture_load(struct cache_node *n, struct texture_tag tag) { __prof; - struct texture_task_params *params = (struct texture_task_params *)vparams; struct temp_arena scratch = scratch_begin_no_conflict(); - struct string path = string_from_cstr_len(params->path_cstr, params->path_len); - struct asset *asset = params->asset; + + atomic_u32_eval_exchange(&n->state, CACHE_NODE_STATE_WORKING); + struct string path = tag.path; logf_info("Loading texture \"%F\"", FMT_STR(path)); sys_timestamp_t start_ts = sys_timestamp(); - b32 success = false; - struct string error_msg = STR("Unknown error"); - ASSERT(string_ends_with(path, STR(".ase"))); - if (resource_exists(path)) { + + /* TODO: Arena probably overkill. Just using it to store texture struct. */ + n->arena = arena_alloc(TEXTURE_ARENA_RESERVE); + u64 texture_memory_size = 0; + { /* Decode */ - struct resource texture_rs = resource_open(path); - struct ase_decode_image_result decoded = ase_decode_image(scratch.arena, texture_rs.bytes); - resource_close(texture_rs); + struct ase_decode_image_result decoded = { 0 }; + if (resource_exists(path)) { + struct resource texture_rs = resource_open(path); + decoded = ase_decode_image(scratch.arena, texture_rs.bytes); +#if RESOURCE_RELOADING + n->initial_resource_file_modified_time = sys_file_get_time(texture_rs.file).modified; +#endif + resource_close(texture_rs); - /* Failure paths */ - if (decoded.errors.count > 0) { - /* FIXME: Read all errors from decode */ - struct string msg = decoded.errors.first->msg; - if (msg.len > 0) { - error_msg = msg; - } - goto abort; - } else if (decoded.image.width > RENDERER_TEXTURE_MAX_WIDTH || decoded.image.height > RENDERER_TEXTURE_MAX_HEIGHT) { - error_msg = string_format(scratch.arena, - STR("Image with dimensions %F x %F exceeds maximum allowed dimensions (%F x %F)"), - FMT_UINT(decoded.image.width), - FMT_UINT(decoded.image.height), - FMT_UINT(RENDERER_TEXTURE_MAX_WIDTH), - FMT_UINT(RENDERER_TEXTURE_MAX_HEIGHT)); - goto abort; + /* Initialize */ + n->texture = arena_push(&n->arena, struct texture); + n->texture->size = V2(decoded.image.width, decoded.image.height); + n->texture->renderer_handle = renderer_texture_alloc(decoded.image); + /* TODO: Query renderer for more accurate texture size in VRAM */ + texture_memory_size += (decoded.image.width * decoded.image.height) * sizeof(*decoded.image.pixels); } else { - success = true; + n->texture = G.nil_texture; + logf_error("Resource \"%F\" not found", path); } +#if RESOURCE_RELOADING + u64 cpy_len = min_u64(tag.path.len, ARRAY_COUNT(n->tag_path)); + n->tag_path_len = cpy_len; + MEMCPY(n->tag_path, tag.path.text, cpy_len); +#endif + } + arena_set_readonly(&n->arena); + n->memory_usage = n->arena.committed + texture_memory_size; + atomic_u64_eval_add(&G.cache.memory_usage, n->memory_usage); - struct image_rgba image_data = decoded.image; + logf_info("Finished loading texture \"%F\" in %F seconds (cache size: %F bytes).", + FMT_STR(path), + FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts)), + FMT_UINT(n->memory_usage)); - /* Create renderer texture */ - struct renderer_handle handle = renderer_texture_alloc(image_data); + atomic_u32_eval_exchange(&n->state, CACHE_NODE_STATE_LOADED); - /* Initialize texture & its data into store */ - struct texture *texture = NULL; - { - struct asset_cache_store store = asset_cache_store_open(); - texture = arena_push(store.arena, struct texture); - *texture = (struct texture) { - .size = V2(image_data.width, image_data.height), - .renderer_handle = handle - }; - asset_cache_store_close(&store); + scratch_end(scratch); +} + +/* ========================== * + * Scope + * ========================== */ + +INTERNAL void scope_ensure_reference(struct texture_scope *scope, struct cache_node *cache_node, u64 cache_bucket_index) +{ + __prof; + struct texture_scope_reference **ref_next = &scope->reference_buckets[cache_bucket_index]; + struct texture_scope_reference *ref = *ref_next; + while (ref) { + if (ref->cache_node == cache_node) { + /* Scope already references node */ + break; + } else { + ref_next = &ref->next_hash; + ref = *ref_next; } + } - logf_info("Finished loading texture \"%F\" in %F seconds", - FMT_STR(path), - FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts))); + if (!ref) { + /* Increment refcount */ + node_refcount_add(cache_node, 1); + /* Add reference to scope */ + struct texture_tctx *tctx = thread_local_var_eval(&tl_texture_tctx); + if (tctx->first_free_reference) { + ref = tctx->first_free_reference; + tctx->first_free_reference = ref->next_free; + MEMZERO_STRUCT(ref); + } else { + ref = arena_push_zero(&tctx->arena, struct texture_scope_reference); + } + ref->cache_node = cache_node; + *ref_next = ref; + } +} - asset_cache_mark_ready(asset, texture); +struct texture_scope *texture_scope_begin(void) +{ + struct texture_tctx *tctx = thread_local_var_eval(&tl_texture_tctx); + + struct texture_scope *res = NULL; + if (tctx->first_free_scope) { + res = tctx->first_free_scope; + tctx->first_free_scope = res->next_free; + MEMZERO(res->reference_buckets, sizeof(*res->reference_buckets) * CACHE_BUCKETS_COUNT); + *res = (struct texture_scope) { + .reference_buckets = res->reference_buckets + }; } else { - success = false; - error_msg = STR("Resource not found"); - goto abort; + res = arena_push_zero(&tctx->arena, struct texture_scope); + res->reference_buckets = arena_push_array_zero(&tctx->arena, struct texture_scope_reference *, CACHE_BUCKETS_COUNT); } - abort: + return res; +} - if (!success) { - logf_error("Error loading texture \"%F\": %F", FMT_STR(path), FMT_STR(error_msg)); +void texture_scope_end(struct texture_scope *scope) +{ + struct texture_tctx *tctx = thread_local_var_eval(&tl_texture_tctx); + for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) { + struct texture_scope_reference *ref = scope->reference_buckets[i]; + while (ref) { + /* Decrement refcount */ + node_refcount_add(ref->cache_node, -1); + /* Add reference to free list */ + ref->next_free = tctx->first_free_reference; + tctx->first_free_reference = ref; + ref = ref->next_hash; + } + } + scope->next_free = tctx->first_free_scope; + tctx->first_free_scope = scope; +} - /* Generate purple & black image */ - struct renderer_handle handle = { 0 }; - struct image_rgba default_image_data = generate_purple_black_image(scratch.arena, 64, 64); - handle = renderer_texture_alloc(default_image_data); +/* ========================== * + * Cache interface + * ========================== */ - /* Store */ - struct texture *texture = NULL; +INTERNAL struct cache_node *node_lookup_touch(struct texture_scope *scope, struct texture_tag tag) +{ + __prof; + + struct cache_node *n = NULL; + struct cache_node *nonmatching = NULL; + struct cache_node **nonmatching_next = NULL; + + u64 cache_bucket_index = tag.hash % CACHE_BUCKETS_COUNT; + struct cache_bucket *bucket = &G.cache.buckets[cache_bucket_index]; + + /* Lookup */ + /* TODO: Spinlock */ + sys_rw_mutex_lock_shared(&bucket->rw_mutex); + { + nonmatching_next = &bucket->first; + n = *nonmatching_next; + while (n) { + if (n->hash == tag.hash) { + scope_ensure_reference(scope, n, cache_bucket_index); + break; + } else { + nonmatching = n; + nonmatching_next = &nonmatching->next_hash; + n = *nonmatching_next; + } + } + } + sys_rw_mutex_unlock_shared(&bucket->rw_mutex); + + /* Allocate new node if necessary */ + if (!n) { + __profscope(node_lookup_allocate); + sys_rw_mutex_lock_exclusive(&bucket->rw_mutex); { - struct asset_cache_store store = asset_cache_store_open(); - texture = arena_push(store.arena, struct texture); - *texture = (struct texture) { - .renderer_handle = handle, - .size = V2(default_image_data.width, default_image_data.height) - }; - asset_cache_store_close(&store); + /* Alloc node */ + sys_mutex_lock(&G.cache.node_pool_mutex); + { + if (G.cache.node_pool_first_free) { + n = G.cache.node_pool_first_free; + G.cache.node_pool_first_free = n->next_free; + MEMZERO_STRUCT(n); + } else { + n = arena_push_zero(&G.cache.arena, struct cache_node); + } + } + sys_mutex_unlock(&G.cache.node_pool_mutex); + /* Init node and add to bucket */ + scope_ensure_reference(scope, n, cache_bucket_index); + *nonmatching_next = n; + if (nonmatching) { + nonmatching->next_hash = n; + n->prev_hash = nonmatching; + } + n->hash = tag.hash; } - - asset_cache_mark_ready(asset, texture); + sys_rw_mutex_unlock_exclusive(&bucket->rw_mutex); } - texture_task_params_release(params); - - scratch_end(scratch); + return n; } -struct asset *texture_load_asset(struct string path, b32 help) +INTERNAL struct texture *texture_from_tag_internal(struct texture_scope *scope, struct texture_tag tag, b32 await) { - __prof; - struct temp_arena scratch = scratch_begin_no_conflict(); + struct texture *res = G.loading_texture; + struct cache_node *n = node_lookup_touch(scope, tag); - struct string key = string_cat(scratch.arena, path, STR("_tex")); - u64 hash = asset_cache_hash(key); - b32 is_first_touch; - struct asset *asset = asset_cache_touch(key, hash, &is_first_touch); + u32 state = atomic_u32_eval(&n->state); + if (state == CACHE_NODE_STATE_LOADED) { + res = n->texture; + } else if (state == CACHE_NODE_STATE_NONE) { + if (atomic_u32_eval_compare_exchange(&n->state, CACHE_NODE_STATE_NONE, CACHE_NODE_STATE_QUEUED) == CACHE_NODE_STATE_NONE) { + /* Node is new, load texture */ + if (await) { + texture_load(n, tag); + res = n->texture; + } else { + sys_mutex_lock(&G.loaders_mutex); + { + /* Allocate cmd */ + struct loader_cmd *cmd = NULL; + if (G.first_free_loader_cmd) { + cmd = G.first_free_loader_cmd; + G.first_free_loader_cmd = cmd->next_free; + MEMZERO_STRUCT(cmd); + } else { + cmd = arena_push_zero(&G.loader_cmd_arena, struct loader_cmd); + } + + /* Initialize cmd */ + cmd->cache_node = n; + cmd->tag = tag; + { + u64 copy_len = min_u64(tag.path.len, ARRAY_COUNT(cmd->tag_path_buff)); + cmd->tag.path.text = cmd->tag_path_buff; + MEMCPY(cmd->tag.path.text, tag.path.text, copy_len); + } + + /* Add cmd to queue */ + *(G.last_loader_cmd ? &G.last_loader_cmd->next : &G.first_loader_cmd) = cmd; + G.last_loader_cmd = cmd; + + /* Cmd holds reference to node */ + node_refcount_add(n, 1); + + /* Signal work ready */ + sys_condition_variable_signal(&G.loaders_cv); + } + sys_mutex_unlock(&G.loaders_mutex); + } - if (is_first_touch) { - /* Assemble task params */ - struct texture_task_params *params = texture_task_params_alloc(); - if (path.len > (sizeof(params->path_cstr) - 1)) { - sys_panic(string_format(scratch.arena, - STR("Texture path \"%F\" too long!"), - FMT_STR(path))); } - cstr_buff_from_string(BUFFER_FROM_ARRAY(params->path_cstr), path); - params->path_len = path.len; - params->asset = asset; - - /* Push task */ - asset_cache_mark_loading(asset); - struct work_handle wh = { 0 }; - if (help) { - wh = work_push_task_and_help(&texture_load_asset_task, params, WORK_PRIORITY_NORMAL); - } else { - wh = work_push_task(&texture_load_asset_task, params, WORK_PRIORITY_NORMAL); - } - asset_cache_set_work(asset, &wh); } - scratch_end(scratch); - return asset; + return res; } -struct texture *texture_load_async(struct string path) +struct texture *texture_from_tag_await(struct texture_scope *scope, struct texture_tag tag) { __prof; - struct asset *asset = texture_load_asset(path, false); - struct texture *tex = (struct texture *)asset_cache_get_store_data(asset); - return tex; + return texture_from_tag_internal(scope, tag, true); } - -struct texture *texture_load(struct string path) +struct texture *texture_from_tag_async(struct texture_scope *scope, struct texture_tag tag) { __prof; - struct asset *asset = texture_load_asset(path, true); - asset_cache_wait(asset); - struct texture *tex = (struct texture *)asset_cache_get_store_data(asset); - return tex; + return texture_from_tag_internal(scope, tag, false); +} + +/* ========================== * + * Loader thread + * ========================== */ + +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_loader_thread_entry_point, arg) +{ + __prof; + (UNUSED)arg; + + b32 abort = false; + while (!abort) { + sys_mutex_lock(&G.loaders_mutex); + + if (G.loaders_shutdown) { + /* Exit thread */ + abort = true; + } else if (!G.first_loader_cmd) { + /* Wait for work */ + sys_condition_variable_wait(&G.loaders_cv, &G.loaders_mutex); + + } + + while (G.first_loader_cmd && !G.loaders_shutdown) { + /* Pull cmd from queue */ + struct loader_cmd *cmd = G.first_loader_cmd; + G.first_loader_cmd = cmd->next; + if (G.last_loader_cmd == cmd) { + G.last_loader_cmd = NULL; + } + + /* Do work (temporarily unlock) */ + sys_mutex_unlock(&G.loaders_mutex); + { + texture_load(cmd->cache_node, cmd->tag); + } + sys_mutex_lock(&G.loaders_mutex); + + /* Free cmd */ + cmd->next_free = G.first_free_loader_cmd; + G.first_free_loader_cmd = cmd; + + /* Cmd no longer references node */ + node_refcount_add(cmd->cache_node, -1); + } + + sys_mutex_unlock(&G.loaders_mutex); + } +} + +/* ========================== * + * Evictor thread + * ========================== */ + +struct evict_node { + b32 force_evict; + struct cache_node_refcount refcount; + struct cache_node *cache_node; + struct cache_bucket *cache_bucket; + struct evict_node *next_consider; + struct evict_node *next_consider_lru; + struct evict_node *next_evicted; + +}; + +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_evictor_thread_entry_point, arg) +{ + (UNUSED)arg; + + b32 abort = false; + while (!abort) { + struct temp_arena scratch = scratch_begin_no_conflict(); + struct evict_node *head_consider = NULL; + struct evict_node *head_consider_lru = NULL; + struct evict_node *head_evicted = NULL; + + sys_mutex_lock(&G.evictor_mutex); + { + /* Thread shutdown */ + if (G.evictor_shutdown) { + abort = true; + } else { + /* Wait */ + sys_condition_variable_wait_time(&G.evictor_cv, &G.evictor_mutex, EVICTOR_CYCLE_INTERVAL); + } + + if (!G.evictor_shutdown) { + u32 cur_cycle = *atomic_u32_raw(&G.evictor_cycle); + + /* Scan for evictable nodes */ + b32 cache_over_budget = atomic_u64_eval(&G.cache.memory_usage) > TEXTURE_CACHE_MEMORY_BUDGET; + if (cache_over_budget || RESOURCE_RELOADING) { + __profscope(eviction_scan); + for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) { + struct cache_bucket *bucket = &G.cache.buckets[i]; + sys_rw_mutex_lock_shared(&bucket->rw_mutex); + { + struct cache_node *n = bucket->first; + while (n) { + b32 consider_for_eviction = false; + b32 force_evict = false; + u64 refcount_uncast = atomic_u64_eval(&n->refcount_struct); + struct cache_node_refcount refcount = *(struct cache_node_refcount *)&refcount_uncast; + if (refcount.count <= 0) { +#if RESOURCE_RELOADING + /* Check if file changed for resource reloading */ + if (!consider_for_eviction) { + struct string path = string_from_cstr_len((char *)n->tag_path, n->tag_path_len); + b32 file_changed = false; + struct sys_datetime current_file_time; + { + struct sys_file file = sys_file_open_read(path); + current_file_time = sys_file_get_time(file).modified; + sys_file_close(file); + } + file_changed = MEMCMP_STRUCT(&n->initial_resource_file_modified_time, ¤t_file_time) != 0; + if (file_changed) { + logf_info("Resource file for texture \"%F\" has changed. Evicting to allow for reloading.", FMT_STR(path)); + consider_for_eviction = true; + force_evict = true; + } + } +#endif + + /* Check usage time */ +#if RESOURCE_RELOADING + /* Only check conditional if * RESOURCE_RELOADING is enabled, + * since over-budget is assumed to be * true otherwise */ + if (cache_over_budget) +#endif + { + u32 last_used_cycle = refcount.last_modified_cycle; + f64 time_since_use = (f64)(cur_cycle - last_used_cycle) * EVICTOR_CYCLE_INTERVAL; + if (time_since_use > EVICTOR_GRACE_PERIOD) { + /* Cache is over budget and node hasn't been referenced in a while */ + consider_for_eviction = true; + } + } + + } + + /* Add node to evict list */ + if (consider_for_eviction) { + struct evict_node *evict_node = arena_push_zero(scratch.arena, struct evict_node); + evict_node->cache_node = n; + evict_node->cache_bucket = bucket; + evict_node->refcount = refcount; + evict_node->force_evict = force_evict; + evict_node->next_consider = head_consider; + head_consider = evict_node; + } + + n = n->next_hash; + } + sys_rw_mutex_unlock_shared(&bucket->rw_mutex); + } + } + } + + /* Sort evict nodes by usage time */ + if (head_consider) { + /* TODO: Optimize sort if necessary. Currently O(n^2). */ + __profscope(eviction_sort); + for (struct evict_node *en = head_consider; en; en = en->next_consider) { + u32 last_modified_cycle = en->refcount.last_modified_cycle; + struct evict_node *prev = NULL; + struct evict_node *next = head_consider_lru; + while (next && !(last_modified_cycle <= next->refcount.last_modified_cycle || en->force_evict)) { + prev = next; + next = next->next_consider_lru; + } + if (prev) { + prev->next_consider_lru = en; + } else { + head_consider_lru = en; + } + en->next_consider_lru = next; + } + } + + /* Remove evictable nodes from cache table until under budget */ + if (head_consider_lru) { + __profscope(eviction_cache_removal); + b32 stop_evicting = false; + for (struct evict_node *en = head_consider_lru; en && !stop_evicting; en = en->next_consider_lru) { + struct cache_bucket *bucket = en->cache_bucket; + struct cache_node *n = en->cache_node; + sys_rw_mutex_lock_exclusive(&bucket->rw_mutex); + { + struct cache_node_refcount refcount = *(struct cache_node_refcount *)atomic_u64_raw(&n->refcount_struct); + if (refcount.count > 0 || ((refcount.last_modified_cycle != en->refcount.last_modified_cycle) && !en->force_evict)) { + /* Cache node has been referenced since scan, skip eviction. */ + } else if (en->force_evict || atomic_u64_eval(&G.cache.memory_usage) > TEXTURE_CACHE_MEMORY_BUDGET) { + /* Remove from cache bucket */ + if (n->prev_hash) { + n->prev_hash->next_hash = n->next_hash; + } else { + bucket->first = n->next_hash; + } + if (n->next_hash) { + n->next_hash->prev_hash = n->prev_hash; + } + atomic_u64_eval_add(&G.cache.memory_usage, -((i64)n->memory_usage)); + /* Add to evicted list */ + en->next_evicted = head_evicted; + head_evicted = en; + } else { + /* Cache is no longer over budget or force evicting, stop iteration */ + stop_evicting = true; + } + } + sys_rw_mutex_unlock_exclusive(&bucket->rw_mutex); + } + } + + if (head_evicted) { + /* Release evicted node memory */ + { + __profscope(eviction_memory_release); + for (struct evict_node *en = head_evicted; en; en = en->next_evicted) { + struct cache_node *n = en->cache_node; + if (n->texture->valid) { + renderer_texture_release(n->texture->renderer_handle); + } + arena_release(&n->arena); + } + } + + /* Add evicted nodes to free list */ + sys_mutex_lock(&G.cache.node_pool_mutex); + { + __profscope(eviction_free_list_append); + for (struct evict_node *en = head_evicted; en; en = en->next_evicted) { + struct cache_node *n = en->cache_node; + n->next_free = G.cache.node_pool_first_free; + G.cache.node_pool_first_free = n; + } + } + sys_mutex_unlock(&G.cache.node_pool_mutex); + } + } + atomic_u32_inc_eval(&G.evictor_cycle); + } + sys_mutex_unlock(&G.evictor_mutex); + scratch_end(scratch); + } } diff --git a/src/texture.h b/src/texture.h index d710593d..3b82a7ee 100644 --- a/src/texture.h +++ b/src/texture.h @@ -1,42 +1,51 @@ #ifndef TEXTURE_H #define TEXTURE_H -#include "renderer.h" #include "util.h" -struct asset; -struct work_startup_receipt; struct renderer_startup_receipt; -struct asset_cache_startup_receipt; struct resource_startup_receipt; -struct texture_frame { - struct clip_rect clip; - u32 duration_ms; -}; - -struct texture_slice { - struct clip_rect clip; -}; - -struct texture_anim { - u32 frame_start; - u32 frame_end; -}; - struct texture { + b32 loading; + b32 valid; struct renderer_handle renderer_handle; struct v2 size; }; +/* ========================== * + * Startup + * ========================== */ + struct texture_startup_receipt { i32 _; }; -struct texture_startup_receipt texture_startup(struct work_startup_receipt *work_sr, - struct renderer_startup_receipt *renderer_sr, - struct asset_cache_startup_receipt *asset_cache_sr, +struct texture_startup_receipt texture_startup(struct renderer_startup_receipt *renderer_sr, struct resource_startup_receipt *resource_sr); -struct asset *texture_load_asset(struct string path, b32 wait); -struct texture *texture_load_async(struct string path); -struct texture *texture_load(struct string path); +/* ========================== * + * Tag + * ========================== */ + +struct texture_tag texture_tag_from_path(struct string path); +b32 texture_tag_is_nil(struct texture_tag tag); +b32 texture_tag_eq(struct texture_tag t1, struct texture_tag t2); + +/* ========================== * + * Scope + * ========================== */ + +struct texture_scope { + struct texture_scope_reference **reference_buckets; + struct texture_scope *next_free; +}; + +struct texture_scope *texture_scope_begin(void); +void texture_scope_end(struct texture_scope *scope); + +/* ========================== * + * Load + * ========================== */ + +struct texture *texture_from_tag_await(struct texture_scope *scope, struct texture_tag tag); +struct texture *texture_from_tag_async(struct texture_scope *scope, struct texture_tag tag); #endif diff --git a/src/user.c b/src/user.c index 9a7cf65c..da610749 100644 --- a/src/user.c +++ b/src/user.c @@ -400,6 +400,8 @@ INTERNAL void user_update(void) * Begin frame cache scopes * ========================== */ + /* TODO: Remove texture scope once renderer is rewritten to work with tags */ + struct texture_scope *texture_frame_scope = texture_scope_begin(); struct sheet_scope *sheet_frame_scope = sheet_scope_begin(); /* ========================== * @@ -757,67 +759,66 @@ INTERNAL void user_update(void) b32 skip_debug_draw_transform = ent == active_camera; /* Draw sprite */ - if (ent->sprite_name.len > 0) { - struct string tex_name = ent->sprite_name; - + if (!texture_tag_is_nil(ent->sprite_texture_tag)) { /* Draw texture */ - struct texture *texture = texture_load_async(tex_name); - if (texture) { - struct xform xf = xform_mul(ent->world_xform, ent->sprite_quad_xform); - struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xf); + struct texture_tag texture_tag = ent->sprite_texture_tag; + /* TODO: Remove this once renderer is rewritten to work with tags */ + struct texture *texture = texture_from_tag_async(texture_frame_scope, texture_tag); - u32 tint = ent->sprite_tint; - struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.texture = texture, .tint = tint); + struct xform xf = xform_mul(ent->world_xform, ent->sprite_quad_xform); + struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xf); - struct sheet *sheet = sheet_from_tag_async(sheet_frame_scope, ent->sprite_sheet_tag); - { - struct sheet_span span = sheet_get_span(sheet, ent->sprite_span_name); - u64 frame_index = span.start + ent->animation_frame; - struct sheet_frame frame = sheet_get_frame(sheet, frame_index); - if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) { - f64 time_in_frame = ent->animation_time_in_frame; - while (time_in_frame > frame.duration) { - time_in_frame -= frame.duration; - ++frame_index; - if (frame_index > span.end) { - /* Loop animation */ - frame_index = span.start; - } - frame = sheet_get_frame(sheet, frame_index); + u32 tint = ent->sprite_tint; + + struct sheet *sheet = sheet_from_tag_async(sheet_frame_scope, ent->sprite_sheet_tag); + struct sheet_frame frame = { 0 }; + { + struct sheet_span span = sheet_get_span(sheet, ent->sprite_span_name); + u64 frame_index = span.start + ent->animation_frame; + frame = sheet_get_frame(sheet, frame_index); + if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) { + f64 time_in_frame = ent->animation_time_in_frame; + while (time_in_frame > frame.duration) { + time_in_frame -= frame.duration; + ++frame_index; + if (frame_index > span.end) { + /* Loop animation */ + frame_index = span.start; } + frame = sheet_get_frame(sheet, frame_index); } - - params.clip = frame.clip; } + } - /* TODO: Check for texture loading as well */ - if (!sheet->loading) { - /* TODO: Fade in a placeholder or something instead of drawing nothing. */ - draw_texture_quad(G.world_canvas, params, quad); - } + struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.texture_tag = texture_tag, .tint = tint, .clip = frame.clip); + + /* TODO: Check for texture loading as well */ + if (!sheet->loading && !texture->loading) { + /* TODO: Fade in a placeholder or something instead of drawing nothing. */ + draw_texture_quad(G.world_canvas, params, quad); + } #if 0 - if (G.debug_draw && !skip_debug_draw) { - /* Debug draw sprite quad */ - { - f32 thickness = 2.f; - u32 color = RGBA_F(1, 1, 0, 0.25); - draw_solid_quad_line(G.world_canvas, quad, (thickness / PIXELS_PER_UNIT / G.world_view.zoom), color); - } - - /* Debug draw sprite transform */ - { - debug_draw_xform(xf); - } - - /* Debug draw sprite pivot */ - { - u32 color = RGBA_F(1, 0, 0, 1); - draw_solid_circle(G.world_canvas, ent->world_xform.og, 0.02, color, 20); - } + if (G.debug_draw && !skip_debug_draw) { + /* Debug draw sprite quad */ + { + f32 thickness = 2.f; + u32 color = RGBA_F(1, 1, 0, 0.25); + draw_solid_quad_line(G.world_canvas, quad, (thickness / PIXELS_PER_UNIT / G.world_view.zoom), color); + } + + /* Debug draw sprite transform */ + { + debug_draw_xform(xf); + } + + /* Debug draw sprite pivot */ + { + u32 color = RGBA_F(1, 0, 0, 1); + draw_solid_circle(G.world_canvas, ent->world_xform.og, 0.02, color, 20); } -#endif } +#endif } /* Debug draw info */ @@ -911,14 +912,13 @@ INTERNAL void user_update(void) struct v2 crosshair_pos = G.viewport_cursor; u32 tint = RGBA_F(1, 1, 1, 1); - struct v2 size = V2(0, 0); - struct texture *t = texture_load_async(STR("res/graphics/crosshair.ase")); - if (t) { - size = t->size; - struct xform xf = XFORM_TRS(.t = crosshair_pos, .s = size); - struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xf); - draw_texture_quad(G.viewport_canvas, DRAW_TEXTURE_PARAMS(.texture = t, .tint = tint), quad); - } + struct texture_tag crosshair_tag = texture_tag_from_path(STR("res/graphics/crosshair.ase")); + struct texture *t = texture_from_tag_async(texture_frame_scope, crosshair_tag); + + struct v2 size = t->size; + struct xform xf = XFORM_TRS(.t = crosshair_pos, .s = size); + struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xf); + draw_texture_quad(G.viewport_canvas, DRAW_TEXTURE_PARAMS(.texture_tag = crosshair_tag, .tint = tint), quad); struct rect cursor_clip = RECT_FROM_V2(G.viewport_screen_offset, G.viewport_size); cursor_clip.pos = v2_add(cursor_clip.pos, v2_mul(size, 0.5f)); @@ -989,49 +989,52 @@ INTERNAL void user_update(void) /* Debug draw info */ if (G.debug_draw) { - struct temp_arena temp = arena_temp_begin(scratch.arena); f32 spacing = 20; struct v2 pos = V2(10, 8); - struct font *font = font_load(STR("res/fonts/fixedsys.ttf"), 12.0f); + struct font *font = font_load_async(STR("res/fonts/fixedsys.ttf"), 12.0f); + if (font) { + struct temp_arena temp = arena_temp_begin(scratch.arena); - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("time: %F"), FMT_FLOAT((f64)G.time))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("time: %F"), FMT_FLOAT((f64)G.time))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("screen_size: (%F, %F)"), FMT_FLOAT((f64)G.screen_size.x), FMT_FLOAT((f64)G.screen_size.y))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("screen_size: (%F, %F)"), FMT_FLOAT((f64)G.screen_size.x), FMT_FLOAT((f64)G.screen_size.y))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("screen_cursor: (%F, %F)"), FMT_FLOAT((f64)G.screen_cursor.x), FMT_FLOAT((f64)G.screen_cursor.y))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("screen_cursor: (%F, %F)"), FMT_FLOAT((f64)G.screen_cursor.x), FMT_FLOAT((f64)G.screen_cursor.y))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_screen_offset: (%F, %F)"), FMT_FLOAT((f64)G.viewport_screen_offset.x), FMT_FLOAT((f64)G.viewport_screen_offset.y))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_screen_offset: (%F, %F)"), FMT_FLOAT((f64)G.viewport_screen_offset.x), FMT_FLOAT((f64)G.viewport_screen_offset.y))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_size: (%F, %F)"), FMT_FLOAT((f64)G.viewport_size.x), FMT_FLOAT((f64)G.viewport_size.y))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_size: (%F, %F)"), FMT_FLOAT((f64)G.viewport_size.x), FMT_FLOAT((f64)G.viewport_size.y))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_center: (%F, %F)"), FMT_FLOAT((f64)G.viewport_center.x), FMT_FLOAT((f64)G.viewport_center.y))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_center: (%F, %F)"), FMT_FLOAT((f64)G.viewport_center.x), FMT_FLOAT((f64)G.viewport_center.y))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_cursor: (%F, %F)"), FMT_FLOAT((f64)G.viewport_cursor.x), FMT_FLOAT((f64)G.viewport_cursor.y))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("viewport_cursor: (%F, %F)"), FMT_FLOAT((f64)G.viewport_cursor.x), FMT_FLOAT((f64)G.viewport_cursor.y))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_view.og: (%F, %F)"), FMT_FLOAT((f64)G.world_view.og.x), FMT_FLOAT((f64)G.world_view.og.y))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_view.og: (%F, %F)"), FMT_FLOAT((f64)G.world_view.og.x), FMT_FLOAT((f64)G.world_view.og.y))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_view rotation: %F"), FMT_FLOAT((f64)xform_get_rotation(G.world_view)))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_view rotation: %F"), FMT_FLOAT((f64)xform_get_rotation(G.world_view)))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_view scale: (%F, %F)"), FMT_FLOAT((f64)xform_get_scale(G.world_view).x), FMT_FLOAT((f64)xform_get_scale(G.world_view).x))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_view scale: (%F, %F)"), FMT_FLOAT((f64)xform_get_scale(G.world_view).x), FMT_FLOAT((f64)xform_get_scale(G.world_view).x))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_cursor: (%F, %F)"), FMT_FLOAT((f64)G.world_cursor.x), FMT_FLOAT((f64)G.world_cursor.y))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world_cursor: (%F, %F)"), FMT_FLOAT((f64)G.world_cursor.x), FMT_FLOAT((f64)G.world_cursor.y))); + pos.y += spacing; - draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("debug_camera: %F"), FMT_STR(G.debug_camera ? STR("true") : STR("false")))); - pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("debug_camera: %F"), FMT_STR(G.debug_camera ? STR("true") : STR("false")))); + pos.y += spacing; + + arena_temp_end(temp); + } - arena_temp_end(temp); } /* Push game cmds */ @@ -1080,6 +1083,7 @@ INTERNAL void user_update(void) * ========================== */ sheet_scope_end(sheet_frame_scope); + texture_scope_end(texture_frame_scope); scratch_end(scratch); }