texture cache w/ eviction & reloading

This commit is contained in:
jacob 2024-05-01 00:22:06 -05:00
parent 42a2d97d3d
commit cf3d678699
21 changed files with 1149 additions and 516 deletions

BIN
res/graphics/white.ase (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -20,6 +20,7 @@
#include "settings.h" #include "settings.h"
#include "draw.h" #include "draw.h"
#include "math.h" #include "math.h"
#include "renderer.h"
struct exit_callback { struct exit_callback {
app_exit_callback_func *func; 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 asset_cache_startup_receipt asset_cache_sr = asset_cache_startup(&work_sr);
struct ttf_startup_receipt ttf_sr = ttf_startup(); 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 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 texture_startup_receipt texture_sr = texture_startup(&renderer_sr, &resource_sr);
struct sheet_startup_receipt sheet_sr = sheet_startup(&work_sr, &asset_cache_sr, &resource_sr); struct sheet_startup_receipt sheet_sr = sheet_startup(&resource_sr);
struct mixer_startup_receipt mixer_sr = mixer_startup(); struct mixer_startup_receipt mixer_sr = mixer_startup();
struct sound_startup_receipt sound_sr = sound_startup(&work_sr, &asset_cache_sr, &resource_sr); 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); struct draw_startup_receipt draw_sr = draw_startup(&renderer_sr, &font_sr);

View File

@ -240,7 +240,7 @@ INTERNAL u16 huffman_decode(struct huffman *huffman, struct bitbuf *bb)
return res; return res;
} }
INTERNAL void inflate(struct arena *arena, u8 *dest, u8 *encoded) INTERNAL void inflate(u8 *dest, u8 *encoded)
{ {
__prof; __prof;
struct bitbuf bb = { encoded }; 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_FIXED:
case BLOCK_TYPE_COMPRESSED_DYNAMIC: { 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 lit_len_dist_table[512] = { 0 };
u32 hlit; u32 hlit;
u32 hdist; 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->height = br_read_u16(&br);
cel->pixels = arena_push_array(scratch.arena, u32, cel->width * cel->height); 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); br_seek_to(&br, chunk_end_pos);
} break; } break;

View File

@ -8,12 +8,6 @@
extern "C" { extern "C" {
#endif #endif
/* ========================== *
* Configurable constants
* ========================== */
#include "config.h"
/* ========================== * /* ========================== *
* Compiler headers * Compiler headers
* ========================== */ * ========================== */
@ -368,6 +362,24 @@ struct buffer {
u8 *data; 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 * Buffer utils
* ========================== */ * ========================== */
@ -566,6 +578,12 @@ INLINE void __prof_zone_cleanup_func(TracyCZoneCtx *__tracy_ctx) { TracyCZoneEnd
#endif /* PROFILING */ #endif /* PROFILING */
/* ========================== *
* Configurable constants
* ========================== */
#include "config.h"
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -39,6 +39,28 @@
#define USER_INTERP_OFFSET_TICK_RATIO 1.1 #define USER_INTERP_OFFSET_TICK_RATIO 1.1
#define USER_INTERP_ENABLED 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 * Settings

View File

@ -3,9 +3,10 @@
#include "math.h" #include "math.h"
#include "font.h" #include "font.h"
#include "scratch.h" #include "scratch.h"
#include "texture.h"
GLOBAL struct { GLOBAL struct {
struct renderer_handle solid_white; struct texture_tag solid_white;
} G = { 0 }, DEBUG_ALIAS(G, G_draw); } 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)renderer_sr;
(UNUSED)font_sr; (UNUSED)font_sr;
G.solid_white = texture_tag_from_path(STR("res/graphics/white.ase"));
/* 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);
}
return (struct draw_startup_receipt) { 0 }; 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) 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_tag = params.texture_tag });
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = params.texture->renderer_handle });
draw_texture_quad_internal(canvas, params.clip, params.tint, quad); 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; 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); 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) 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); draw_texture_quad_internal(canvas, CLIP_ALL, color, quad);
} }
void draw_solid_rect(struct renderer_canvas *canvas, struct rect rect, u32 color) 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); struct quad quad = quad_from_rect(rect);
draw_texture_quad_internal(canvas, CLIP_ALL, color, quad); 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) 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); struct quad quad = quad_from_line(start, end, thickness);
draw_texture_quad_internal(canvas, CLIP_ALL, color, quad); 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) 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); struct quad quad = quad_from_ray(pos, rel, thickness);
draw_texture_quad_internal(canvas, CLIP_ALL, color, quad); 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; 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) { for (u64 i = 1; i < array.count; ++i) {
struct v2 p1 = array.points[i - 1]; struct v2 p1 = array.points[i - 1];
struct v2 p2 = array.points[i]; 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) 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 */ 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) 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; struct v2 draw_pos = pos;
draw_pos.y += font->point_size * scale; 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 = { struct clip_rect clip = {
{ {
glyph->atlas_rect.x / font->texture.size.x, glyph->atlas_rect.x / font->image_size.x,
glyph->atlas_rect.y / font->texture.size.y glyph->atlas_rect.y / font->image_size.y
}, },
{ {
(glyph->atlas_rect.x + glyph->atlas_rect.width) / font->texture.size.x, (glyph->atlas_rect.x + glyph->atlas_rect.width) / font->image_size.x,
(glyph->atlas_rect.y + glyph->atlas_rect.height) / font->texture.size.y (glyph->atlas_rect.y + glyph->atlas_rect.height) / font->image_size.y
} }
}; };

View File

@ -11,8 +11,9 @@ struct font_startup_receipt;
.clip = CLIP_ALL, \ .clip = CLIP_ALL, \
__VA_ARGS__ \ __VA_ARGS__ \
}) })
struct draw_texture_params { struct draw_texture_params {
struct texture *texture; struct texture_tag texture_tag;
struct clip_rect clip; struct clip_rect clip;
u32 tint; u32 tint;
}; };

View File

@ -1,7 +1,7 @@
#ifndef ENTITY_H #ifndef ENTITY_H
#define ENTITY_H #define ENTITY_H
#include "mixer.h" #include "texture.h"
#include "sheet.h" #include "sheet.h"
#include "mixer.h" #include "mixer.h"
@ -66,7 +66,7 @@ struct entity {
/* ====================================================================== */ /* ====================================================================== */
/* Sprite */ /* Sprite */
struct string sprite_name; struct texture_tag sprite_texture_tag;
struct sheet_tag sprite_sheet_tag; struct sheet_tag sprite_sheet_tag;
struct string sprite_span_name; struct string sprite_span_name;
struct xform sprite_quad_xform; struct xform sprite_quad_xform;

View File

@ -121,7 +121,7 @@ INTERNAL WORK_TASK_FUNC_DEF(font_load_asset_task, vparams)
resource_close(res); resource_close(res);
/* Send texture to GPU */ /* 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 */ /* Allocate store memory */
struct font *font = NULL; struct font *font = NULL;
@ -134,10 +134,8 @@ INTERNAL WORK_TASK_FUNC_DEF(font_load_asset_task, vparams)
} }
/* Set font data */ /* Set font data */
font->texture = (struct texture) { font->image_renderer_handle = image_renderer_handle;
.renderer_handle = texture_renderer_handle, font->image_size = V2(result.image_data.width, result.image_data.height);
.size = V2(result.image_data.width, result.image_data.height),
};
font->glyphs_count = result.glyphs_count; font->glyphs_count = result.glyphs_count;
font->point_size = point_size; font->point_size = point_size;

View File

@ -21,8 +21,9 @@ struct font_glyph {
}; };
struct font { struct font {
struct renderer_handle image_renderer_handle;
struct v2 image_size;
f32 point_size; f32 point_size;
struct texture texture;
u16 glyphs_count; u16 glyphs_count;
struct font_glyph *glyphs; struct font_glyph *glyphs;
u16 *lookup; u16 *lookup;

View File

@ -152,6 +152,21 @@ INTERNAL void recalculate_world_xform_recurse(struct entity *parent)
scratch_end(scratch); 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) INTERNAL void game_update(void)
{ {
__prof; __prof;
@ -183,22 +198,13 @@ INTERNAL void game_update(void)
struct string sprite_name = STR("res/graphics/tim.ase"); struct string sprite_name = STR("res/graphics/tim.ase");
struct string sprite_span_name = STR("UNARMED"); 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); 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); struct v2 sprite_pos = V2(0, 0);
f32 sprite_rot = 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; struct v2 sprite_pivot;
{ {
@ -213,7 +219,7 @@ INTERNAL void game_update(void)
sprite_xf = xform_scale(sprite_xf, sprite_size); sprite_xf = xform_scale(sprite_xf, sprite_size);
e->sprite_quad_xform = sprite_xf; 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_sheet_tag = sprite_sheet_tag;
e->sprite_span_name = sprite_span_name; e->sprite_span_name = sprite_span_name;
e->sprite_tint = COLOR_WHITE; 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_name = STR("res/graphics/tim.ase");
struct string sprite_span_name = STR("UNARMED"); 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); 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); struct v2 sprite_pos = V2(0, 0);
f32 sprite_rot = 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; struct v2 sprite_pivot;
{ {
@ -274,7 +271,7 @@ INTERNAL void game_update(void)
sprite_xf = xform_scale(sprite_xf, sprite_size); sprite_xf = xform_scale(sprite_xf, sprite_size);
e->sprite_quad_xform = sprite_xf; 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_sheet_tag = sprite_sheet_tag;
e->sprite_span_name = sprite_span_name; e->sprite_span_name = sprite_span_name;
e->sprite_tint = RGBA_F(0.5, 0.5, 0, 1); 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)); e->rel_xform = XFORM_POS(V2(-3, -3));
struct string sprite_name = STR("res/graphics/sound.ase"); 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); struct sheet_tag sprite_sheet_tag = sheet_tag_from_path(sprite_name);
f32 meters_width, meters_height; struct v2 sprite_size = V2(0.25f, 0.25f);
{ // struct v2 sprite_size = sheet_size_meters(sprite_sheet_tag);
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);
e->sprite_quad_xform = xform_with_scale(XFORM_IDENT, sprite_size); 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_sheet_tag = sprite_sheet_tag;
e->sprite_tint = RGBA_F(1, 1, 0, 1); e->sprite_tint = RGBA_F(1, 1, 0, 1);

View File

@ -2,7 +2,6 @@
#define RENDERER_H #define RENDERER_H
struct sys_window; struct sys_window;
struct texture;
#define RENDERER_TEXTURE_MAX_WIDTH 16384 #define RENDERER_TEXTURE_MAX_WIDTH 16384
#define RENDERER_TEXTURE_MAX_HEIGHT 16384 #define RENDERER_TEXTURE_MAX_HEIGHT 16384
@ -11,10 +10,6 @@ typedef u32 vidx;
struct renderer_canvas; struct renderer_canvas;
struct renderer_handle {
u64 v[1];
};
/* ========================== * /* ========================== *
* Shaders * Shaders
* ========================== */ * ========================== */
@ -27,7 +22,8 @@ enum shader_kind {
}; };
struct texture_shader_parameters { 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 { 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); struct renderer_handle renderer_texture_alloc(struct image_rgba data);
void renderer_texture_release(struct renderer_handle handle); void renderer_texture_release(struct renderer_handle handle);
b32 renderer_texture_is_nil(struct renderer_handle handle);
#endif #endif

View File

@ -7,6 +7,7 @@
#include "math.h" #include "math.h"
#include "inc.h" #include "inc.h"
#include "tar.h" #include "tar.h"
#include "texture.h"
#include <Windows.h> #include <Windows.h>
#define CINTERFACE #define CINTERFACE
@ -57,7 +58,9 @@ struct dx11_buffer {
struct renderer_cmd { struct renderer_cmd {
struct dx11_shader *shader; struct dx11_shader *shader;
struct renderer_handle texture;
struct renderer_handle texture_handle; /* Overrides texture_tag */
struct texture_tag texture_tag;
/* Associated buffer data */ /* Associated buffer data */
u32 vertex_count; 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) 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]; return h1.v[0] == h2.v[0];
} }
INTERNAL b32 handle_is_nil(struct renderer_handle h)
{
return h.v[0] == 0;
}
/* ========================== * /* ========================== *
* Shader * 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) 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; 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 */ /* Command parameters are not the same, insert new command */
struct renderer_cmd *cmd = arena_push(&canvas->cpu_cmd_store.arena, struct renderer_cmd); struct renderer_cmd *cmd = arena_push(&canvas->cpu_cmd_store.arena, struct renderer_cmd);
*cmd = (struct renderer_cmd){ *cmd = (struct renderer_cmd){
.shader = &G.shaders[SHADER_TEXTURE], .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) { if (!canvas->cpu_cmd_store.cmd_first) {
@ -829,6 +841,8 @@ void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_cou
{ {
__prof; __prof;
struct texture_scope *texture_scope = texture_scope_begin();
/* Resize back buffer */ /* Resize back buffer */
if (!v2_eq(G.backbuffer_size, screen_size)) { if (!v2_eq(G.backbuffer_size, screen_size)) {
resize_backbuffer(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) { for (struct renderer_cmd *cmd = canvas->gpu_cmd_store.cmd_first; cmd; cmd = cmd->next) {
struct dx11_shader *shader = cmd->shader; struct dx11_shader *shader = cmd->shader;
struct dx11_buffer *buffer = &canvas->buffers[shader->kind]; 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 */ /* Activate shader */
if (shader != last_shader) { if (shader != last_shader) {
@ -904,6 +924,8 @@ void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_cou
__profframe(0); __profframe(0);
} }
renderer_capture_image_for_profiler(viewport.width, viewport.height); 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); handle_release(handle);
} }
b32 renderer_texture_is_nil(struct renderer_handle handle)
{
return handle_is_nil(handle);
}
/* ========================== * /* ========================== *
* Profiling frame capture * Profiling frame capture
* ========================== */ * ========================== */
@ -1054,9 +1081,11 @@ INTERNAL void renderer_capture_image_for_profiler(f32 width, f32 height)
} }
} }
#else #else
INTERNAL void renderer_capture_image_for_profiler(f32 width, f32 height) INTERNAL void renderer_capture_image_for_profiler(f32 width, f32 height)
{ {
(UNUSED)width; (UNUSED)width;
(UNUSED)height; (UNUSED)height;
} }
#endif #endif

View File

@ -19,7 +19,6 @@ struct resource_startup_receipt resource_startup(void)
{ {
#if RESOURCES_EMBEDDED #if RESOURCES_EMBEDDED
struct buffer embedded_data = inc_res_tar(); 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)); G.arena = arena_alloc(GIGABYTE(64));
if (embedded_data.size <= 0) { if (embedded_data.size <= 0) {
sys_panic(STR("No embedded resources found")); 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) .bytes = entry ? entry->buff : BUFFER(0, 0)
}; };
#else #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 sys_file_map file_map = sys_file_map_open_read(file);
struct buffer bytes = sys_file_map_data(file_map); struct buffer bytes = sys_file_map_data(file_map);
return (struct resource) { return (struct resource) {

View File

@ -4,14 +4,12 @@
#include "sys.h" #include "sys.h"
#include "scratch.h" #include "scratch.h"
#include "resource.h" #include "resource.h"
#include "asset_cache.h"
#include "ase.h" #include "ase.h"
#include "util.h" #include "util.h"
#include "work.h" #include "work.h"
#include "atomic.h" #include "atomic.h"
#include "thread_local.h" #include "thread_local.h"
#include "app.h" #include "app.h"
#include "intrinsics.h"
#define SHEET_ARENA_RESERVE MEGABYTE(64) #define SHEET_ARENA_RESERVE MEGABYTE(64)
#define SHEET_LOOKUP_TABLE_BUCKET_RATIO 2.0 #define SHEET_LOOKUP_TABLE_BUCKET_RATIO 2.0
@ -21,13 +19,10 @@
#define MAX_LOADER_THREADS 4 #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 */ /* 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 #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_loader_thread_entry_point, arg);
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_evictor_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 sheet_startup_receipt sheet_startup(struct resource_startup_receipt *resource_sr)
struct asset_cache_startup_receipt *asset_cache_sr,
struct resource_startup_receipt *resource_sr)
{ {
(UNUSED)work_sr;
(UNUSED)asset_cache_sr;
(UNUSED)resource_sr; (UNUSED)resource_sr;
G.cache.node_pool_mutex = sys_mutex_alloc(); 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); 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); app_register_exit_callback(&sheet_shutdown);
@ -249,6 +240,28 @@ struct sheet_tag sheet_tag_from_path(struct string path)
return res; 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 * 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); decoded = ase_decode_sheet(scratch.arena, sheet_rs.bytes);
#if RESOURCE_RELOADING #if RESOURCE_RELOADING
n->initial_resource_file_modified_time = sys_file_get_time(sheet_rs.file).modified; 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 #endif
resource_close(sheet_rs); 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); 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); arena_set_readonly(&n->arena);
n->memory_usage = n->arena.committed; n->memory_usage = n->arena.committed;
atomic_u64_eval_add(&G.cache.memory_usage, n->memory_usage); 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_STR(path),
FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts)), 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); 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) { if (!ref) {
/* Increment refcount */ /* Increment refcount */
u32 evictor_cycle = atomic_u32_eval(&G.evictor_cycle); node_refcount_add(cache_node, 1);
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);
/* Add reference to scope */ /* Add reference to scope */
struct sheet_tctx *tctx = thread_local_var_eval(&tl_sheet_tctx); struct sheet_tctx *tctx = thread_local_var_eval(&tl_sheet_tctx);
if (tctx->first_free_reference) { 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]; struct sheet_scope_reference *ref = scope->reference_buckets[i];
while (ref) { while (ref) {
/* Decrement refcount */ /* Decrement refcount */
struct cache_node *cache_node = ref->cache_node; node_refcount_add(ref->cache_node, -1);
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);
/* Add reference to free list */ /* Add reference to free list */
ref->next_free = tctx->first_free_reference; ref->next_free = tctx->first_free_reference;
tctx->first_free_reference = ref; 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) { if (G.first_free_loader_cmd) {
cmd = G.first_free_loader_cmd; cmd = G.first_free_loader_cmd;
G.first_free_loader_cmd = cmd->next_free; G.first_free_loader_cmd = cmd->next_free;
MEMZERO_STRUCT(cmd);
} else { } else {
cmd = arena_push(&G.loader_cmd_arena, struct loader_cmd); cmd = arena_push_zero(&G.loader_cmd_arena, struct loader_cmd);
} }
/* Initialize 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 ? &G.last_loader_cmd->next : &G.first_loader_cmd) = cmd;
G.last_loader_cmd = cmd; G.last_loader_cmd = cmd;
/* Cmd holds reference to node */
node_refcount_add(n, 1);
/* Signal work ready */ /* Signal work ready */
sys_condition_variable_signal(&G.loaders_cv); 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 */ /* Free cmd */
cmd->next_free = G.first_free_loader_cmd; cmd->next_free = G.first_free_loader_cmd;
G.first_free_loader_cmd = 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); 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) { if (!G.evictor_shutdown) {
sys_timestamp_t cur_timestamp = sys_timestamp(); u32 cur_cycle = *atomic_u32_raw(&G.evictor_cycle);
f64 cur_time = sys_timestamp_seconds(cur_timestamp);
/* Scan for evictable nodes */ /* 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) { if (cache_over_budget || RESOURCE_RELOADING) {
__profscope(eviction_scan); __profscope(eviction_scan);
for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) { 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) { while (n) {
b32 consider_for_eviction = false; b32 consider_for_eviction = false;
b32 force_evict = false; b32 force_evict = false;
if (atomic_u32_eval(&n->state) == CACHE_NODE_STATE_LOADED) { u64 refcount_uncast = atomic_u64_eval(&n->refcount_struct);
u64 refcount_uncast = atomic_u64_eval(&n->refcount_struct); struct cache_node_refcount refcount = *(struct cache_node_refcount *)&refcount_uncast;
struct cache_node_refcount refcount = *(struct cache_node_refcount *)&refcount_uncast; if (refcount.count <= 0) {
if (refcount.count <= 0) {
#if RESOURCE_RELOADING #if RESOURCE_RELOADING
/* Check if file changed for resource reloading */ /* Check if file changed for resource reloading */
if (!consider_for_eviction) { if (!consider_for_eviction) {
struct string path = string_from_cstr_len((char *)n->tag_path, n->tag_path_len); struct string path = string_from_cstr_len((char *)n->tag_path, n->tag_path_len);
b32 file_changed = false; b32 file_changed = false;
if (!sys_is_file(path)) { struct sys_datetime current_file_time;
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, &current_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
{ {
f64 last_used_time = (f64)refcount.last_modified_cycle * EVICTOR_CYCLE_INTERVAL; struct sys_file file = sys_file_open_read(path);
if (cur_time - last_used_time > EVICTOR_GRACE_PERIOD) { current_file_time = sys_file_get_time(file).modified;
/* Cache is over budget and node hasn't been referenced in a while */ sys_file_close(file);
consider_for_eviction = true;
}
} }
file_changed = MEMCMP_STRUCT(&n->initial_resource_file_modified_time, &current_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); 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); 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)) { 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. */ /* 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 */ /* Remove from cache bucket */
if (n->prev_hash) { if (n->prev_hash) {
n->prev_hash->next_hash = n->next_hash; n->prev_hash->next_hash = n->next_hash;

View File

@ -3,17 +3,9 @@
#include "util.h" #include "util.h"
struct asset;
struct sheet_frame; struct sheet_frame;
struct work_startup_receipt;
struct asset_cache_startup_receipt;
struct resource_startup_receipt; struct resource_startup_receipt;
struct sheet_tag {
u128 hash;
struct string path;
};
struct sheet { struct sheet {
b32 loading; b32 loading;
struct v2 image_size; struct v2 image_size;
@ -29,9 +21,7 @@ struct sheet {
* ========================== */ * ========================== */
struct sheet_startup_receipt { i32 _; }; struct sheet_startup_receipt { i32 _; };
struct sheet_startup_receipt sheet_startup(struct work_startup_receipt *work_sr, struct sheet_startup_receipt sheet_startup(struct resource_startup_receipt *resource_sr);
struct asset_cache_startup_receipt *asset_cache_srm,
struct resource_startup_receipt *resource_sr);
/* ========================== * /* ========================== *
* Tag * Tag

View File

@ -215,6 +215,7 @@ b32 sys_is_dir(struct string path);
void sys_mkdir(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(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_write(struct string path);
struct sys_file sys_file_open_append(struct string path); struct sys_file sys_file_open_append(struct string path);
void sys_file_close(struct sys_file file); void sys_file_close(struct sys_file file);

View File

@ -363,9 +363,8 @@ struct sys_file sys_file_open_read(struct string path)
{ {
__prof; __prof;
struct temp_arena scratch = scratch_begin_no_conflict(); 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( HANDLE handle = CreateFileW(
path_wstr, path_wstr,
GENERIC_READ, GENERIC_READ,
@ -375,16 +374,43 @@ struct sys_file sys_file_open_read(struct string path)
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL,
NULL NULL
); );
scratch_end(scratch); scratch_end(scratch);
struct sys_file file = (struct sys_file) { (u64)handle }; struct sys_file file = (struct sys_file) { (u64)handle };
return file; 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) struct sys_file sys_file_open_write(struct string path)
{ {
__prof; __prof;
struct temp_arena scratch = scratch_begin_no_conflict(); struct temp_arena scratch = scratch_begin_no_conflict();
wchar_t *path_wstr = wstr_from_string(scratch.arena, path); wchar_t *path_wstr = wstr_from_string(scratch.arena, path);
/* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */
HANDLE handle = CreateFileW( HANDLE handle = CreateFileW(
path_wstr, path_wstr,
GENERIC_WRITE, GENERIC_WRITE,
@ -394,10 +420,9 @@ struct sys_file sys_file_open_write(struct string path)
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL,
NULL NULL
); );
/* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */
scratch_end(scratch); scratch_end(scratch);
struct sys_file file = (struct sys_file) { (u64)handle }; return (struct sys_file) { (u64)handle };
return file;
} }
struct sys_file sys_file_open_append(struct string path) 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; __prof;
struct temp_arena scratch = scratch_begin_no_conflict(); struct temp_arena scratch = scratch_begin_no_conflict();
wchar_t *path_wstr = wstr_from_string(scratch.arena, path); wchar_t *path_wstr = wstr_from_string(scratch.arena, path);
/* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */
HANDLE handle = CreateFileW( HANDLE handle = CreateFileW(
path_wstr, path_wstr,
FILE_APPEND_DATA, FILE_APPEND_DATA,
@ -414,7 +440,6 @@ struct sys_file sys_file_open_append(struct string path)
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL,
NULL NULL
); );
/* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */
scratch_end(scratch); scratch_end(scratch);
struct sys_file file = (struct sys_file) { (u64)handle }; struct sys_file file = (struct sys_file) { (u64)handle };
return file; return file;
@ -450,6 +475,7 @@ struct buffer sys_file_read_all(struct arena *arena, struct sys_file file)
NULL NULL
); );
} }
return buff; return buff;
} }
@ -491,26 +517,29 @@ struct sys_file_time sys_file_get_time(struct sys_file file)
FILETIME ft_created; FILETIME ft_created;
FILETIME ft_accessed; FILETIME ft_accessed;
FILETIME ft_modified; 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 */ /* Convert local file times to system times */
FileTimeToLocalFileTime(&ft_created, &ft_created); SYSTEMTIME st_created;
FileTimeToLocalFileTime(&ft_accessed, &ft_accessed); SYSTEMTIME st_accessed;
FileTimeToLocalFileTime(&ft_modified, &ft_modified); 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 */ return (struct sys_file_time) {
SYSTEMTIME st_created; .created = win32_time_to_sys_time(st_created),
SYSTEMTIME st_accessed; .accessed = win32_time_to_sys_time(st_accessed),
SYSTEMTIME st_modified; .modified = win32_time_to_sys_time(st_modified)
FileTimeToSystemTime(&ft_created, &st_created); };
FileTimeToSystemTime(&ft_accessed, &st_accessed); } else {
FileTimeToSystemTime(&ft_modified, &st_modified); return (struct sys_file_time) { 0 };
}
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)
};
} }
/* ========================== * /* ========================== *
@ -536,7 +565,6 @@ struct sys_file_map sys_file_map_open_read(struct sys_file file)
); );
if (!map_handle) { if (!map_handle) {
ASSERT(false);
return (struct sys_file_map) { 0 }; return (struct sys_file_map) { 0 };
} }
@ -1839,6 +1867,7 @@ u32 sys_rand_u32(void)
void sys_panic(struct string msg) void sys_panic(struct string msg)
{ {
ASSERT(false);
if (atomic_i32_eval_compare_exchange(&G.panicking, 0, 1) == 0) { if (atomic_i32_eval_compare_exchange(&G.panicking, 0, 1) == 0) {
log_panic(msg); log_panic(msg);

View File

@ -1,29 +1,102 @@
#include "texture.h" #include "texture.h"
#include "arena.h" #include "arena.h"
#include "memory.h" #include "log.h"
#include "renderer.h"
#include "util.h"
#include "asset_cache.h"
#include "sys.h" #include "sys.h"
#include "scratch.h" #include "scratch.h"
#include "resource.h" #include "resource.h"
#include "ase.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 { #define TCTX_ARENA_RESERVE MEGABYTE(64)
struct texture_task_params *next_free; #define CACHE_BUCKETS_COUNT 1024
struct asset *asset; #define MAX_LOADER_THREADS 4
u64 path_len;
char path_cstr[1024]; /* 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 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 { 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); } G = { 0 }, DEBUG_ALIAS(G, G_texture);
/* ========================== * /* ========================== *
* Startup * Thread local state
* ========================== */ * ========================== */
struct texture_startup_receipt texture_startup(struct work_startup_receipt *work_sr, struct texture_tctx {
struct renderer_startup_receipt *renderer_sr, struct arena arena;
struct asset_cache_startup_receipt *asset_cache_sr, struct texture_scope *first_free_scope;
struct resource_startup_receipt *resource_sr) struct texture_scope_reference *first_free_reference;
};
INTERNAL THREAD_LOCAL_VAR_ALLOC_FUNC_DEF(texture_tctx_alloc, vtctx)
{ {
(UNUSED)work_sr; struct texture_tctx *tctx = (struct texture_tctx *)vtctx;
(UNUSED)renderer_sr; tctx->arena = arena_alloc(MEGABYTE(64));
(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 };
} }
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) INTERNAL struct image_rgba generate_purple_black_image(struct arena *arena, u32 width, u32 height)
{ {
u32 *pixels = arena_push_array(arena, u32, width * 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 * Load
* ========================== */ * ========================== */
INTERNAL WORK_TASK_FUNC_DEF(texture_load_asset_task, vparams) INTERNAL void texture_load(struct cache_node *n, struct texture_tag tag)
{ {
__prof; __prof;
struct texture_task_params *params = (struct texture_task_params *)vparams;
struct temp_arena scratch = scratch_begin_no_conflict(); 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)); logf_info("Loading texture \"%F\"", FMT_STR(path));
sys_timestamp_t start_ts = sys_timestamp(); sys_timestamp_t start_ts = sys_timestamp();
b32 success = false;
struct string error_msg = STR("Unknown error");
ASSERT(string_ends_with(path, STR(".ase"))); 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 */ /* Decode */
struct resource texture_rs = resource_open(path); struct ase_decode_image_result decoded = { 0 };
struct ase_decode_image_result decoded = ase_decode_image(scratch.arena, texture_rs.bytes); if (resource_exists(path)) {
resource_close(texture_rs); 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 */ /* Initialize */
if (decoded.errors.count > 0) { n->texture = arena_push(&n->arena, struct texture);
/* FIXME: Read all errors from decode */ n->texture->size = V2(decoded.image.width, decoded.image.height);
struct string msg = decoded.errors.first->msg; n->texture->renderer_handle = renderer_texture_alloc(decoded.image);
if (msg.len > 0) { /* TODO: Query renderer for more accurate texture size in VRAM */
error_msg = msg; texture_memory_size += (decoded.image.width * decoded.image.height) * sizeof(*decoded.image.pixels);
}
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;
} else { } 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 */ atomic_u32_eval_exchange(&n->state, CACHE_NODE_STATE_LOADED);
struct renderer_handle handle = renderer_texture_alloc(image_data);
/* Initialize texture & its data into store */ scratch_end(scratch);
struct texture *texture = NULL; }
{
struct asset_cache_store store = asset_cache_store_open(); /* ========================== *
texture = arena_push(store.arena, struct texture); * Scope
*texture = (struct texture) { * ========================== */
.size = V2(image_data.width, image_data.height),
.renderer_handle = handle INTERNAL void scope_ensure_reference(struct texture_scope *scope, struct cache_node *cache_node, u64 cache_bucket_index)
}; {
asset_cache_store_close(&store); __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", if (!ref) {
FMT_STR(path), /* Increment refcount */
FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts))); 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 { } else {
success = false; res = arena_push_zero(&tctx->arena, struct texture_scope);
error_msg = STR("Resource not found"); res->reference_buckets = arena_push_array_zero(&tctx->arena, struct texture_scope_reference *, CACHE_BUCKETS_COUNT);
goto abort;
} }
abort: return res;
}
if (!success) { void texture_scope_end(struct texture_scope *scope)
logf_error("Error loading texture \"%F\": %F", FMT_STR(path), FMT_STR(error_msg)); {
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 }; * Cache interface
struct image_rgba default_image_data = generate_purple_black_image(scratch.arena, 64, 64); * ========================== */
handle = renderer_texture_alloc(default_image_data);
/* Store */ INTERNAL struct cache_node *node_lookup_touch(struct texture_scope *scope, struct texture_tag tag)
struct texture *texture = NULL; {
__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(); /* Alloc node */
texture = arena_push(store.arena, struct texture); sys_mutex_lock(&G.cache.node_pool_mutex);
*texture = (struct texture) { {
.renderer_handle = handle, if (G.cache.node_pool_first_free) {
.size = V2(default_image_data.width, default_image_data.height) n = G.cache.node_pool_first_free;
}; G.cache.node_pool_first_free = n->next_free;
asset_cache_store_close(&store); 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;
} }
sys_rw_mutex_unlock_exclusive(&bucket->rw_mutex);
asset_cache_mark_ready(asset, texture);
} }
texture_task_params_release(params); return n;
scratch_end(scratch);
} }
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 texture *res = G.loading_texture;
struct temp_arena scratch = scratch_begin_no_conflict(); struct cache_node *n = node_lookup_touch(scope, tag);
struct string key = string_cat(scratch.arena, path, STR("_tex")); u32 state = atomic_u32_eval(&n->state);
u64 hash = asset_cache_hash(key); if (state == CACHE_NODE_STATE_LOADED) {
b32 is_first_touch; res = n->texture;
struct asset *asset = asset_cache_touch(key, hash, &is_first_touch); } 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 res;
return asset;
} }
struct texture *texture_load_async(struct string path) struct texture *texture_from_tag_await(struct texture_scope *scope, struct texture_tag tag)
{ {
__prof; __prof;
struct asset *asset = texture_load_asset(path, false); return texture_from_tag_internal(scope, tag, true);
struct texture *tex = (struct texture *)asset_cache_get_store_data(asset);
return tex;
} }
struct texture *texture_from_tag_async(struct texture_scope *scope, struct texture_tag tag)
struct texture *texture_load(struct string path)
{ {
__prof; __prof;
struct asset *asset = texture_load_asset(path, true); return texture_from_tag_internal(scope, tag, false);
asset_cache_wait(asset); }
struct texture *tex = (struct texture *)asset_cache_get_store_data(asset);
return tex; /* ========================== *
* 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, &current_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);
}
} }

View File

@ -1,42 +1,51 @@
#ifndef TEXTURE_H #ifndef TEXTURE_H
#define TEXTURE_H #define TEXTURE_H
#include "renderer.h"
#include "util.h" #include "util.h"
struct asset;
struct work_startup_receipt;
struct renderer_startup_receipt; struct renderer_startup_receipt;
struct asset_cache_startup_receipt;
struct resource_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 { struct texture {
b32 loading;
b32 valid;
struct renderer_handle renderer_handle; struct renderer_handle renderer_handle;
struct v2 size; struct v2 size;
}; };
/* ========================== *
* Startup
* ========================== */
struct texture_startup_receipt { i32 _; }; struct texture_startup_receipt { i32 _; };
struct texture_startup_receipt texture_startup(struct work_startup_receipt *work_sr, struct texture_startup_receipt texture_startup(struct renderer_startup_receipt *renderer_sr,
struct renderer_startup_receipt *renderer_sr,
struct asset_cache_startup_receipt *asset_cache_sr,
struct resource_startup_receipt *resource_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); * Tag
struct texture *texture_load(struct string path); * ========================== */
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 #endif

View File

@ -400,6 +400,8 @@ INTERNAL void user_update(void)
* Begin frame cache scopes * 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(); 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; b32 skip_debug_draw_transform = ent == active_camera;
/* Draw sprite */ /* Draw sprite */
if (ent->sprite_name.len > 0) { if (!texture_tag_is_nil(ent->sprite_texture_tag)) {
struct string tex_name = ent->sprite_name;
/* Draw texture */ /* Draw texture */
struct texture *texture = texture_load_async(tex_name); struct texture_tag texture_tag = ent->sprite_texture_tag;
if (texture) { /* TODO: Remove this once renderer is rewritten to work with tags */
struct xform xf = xform_mul(ent->world_xform, ent->sprite_quad_xform); struct texture *texture = texture_from_tag_async(texture_frame_scope, texture_tag);
struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xf);
u32 tint = ent->sprite_tint; struct xform xf = xform_mul(ent->world_xform, ent->sprite_quad_xform);
struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.texture = texture, .tint = tint); 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); u32 tint = ent->sprite_tint;
{
struct sheet_span span = sheet_get_span(sheet, ent->sprite_span_name); struct sheet *sheet = sheet_from_tag_async(sheet_frame_scope, ent->sprite_sheet_tag);
u64 frame_index = span.start + ent->animation_frame; struct sheet_frame frame = { 0 };
struct sheet_frame frame = sheet_get_frame(sheet, frame_index); {
if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) { struct sheet_span span = sheet_get_span(sheet, ent->sprite_span_name);
f64 time_in_frame = ent->animation_time_in_frame; u64 frame_index = span.start + ent->animation_frame;
while (time_in_frame > frame.duration) { frame = sheet_get_frame(sheet, frame_index);
time_in_frame -= frame.duration; if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) {
++frame_index; f64 time_in_frame = ent->animation_time_in_frame;
if (frame_index > span.end) { while (time_in_frame > frame.duration) {
/* Loop animation */ time_in_frame -= frame.duration;
frame_index = span.start; ++frame_index;
} if (frame_index > span.end) {
frame = sheet_get_frame(sheet, frame_index); /* Loop animation */
frame_index = span.start;
} }
frame = sheet_get_frame(sheet, frame_index);
} }
params.clip = frame.clip;
} }
}
/* TODO: Check for texture loading as well */ struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.texture_tag = texture_tag, .tint = tint, .clip = frame.clip);
if (!sheet->loading) {
/* TODO: Fade in a placeholder or something instead of drawing nothing. */ /* TODO: Check for texture loading as well */
draw_texture_quad(G.world_canvas, params, quad); 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 0
if (G.debug_draw && !skip_debug_draw) { if (G.debug_draw && !skip_debug_draw) {
/* Debug draw sprite quad */ /* Debug draw sprite quad */
{ {
f32 thickness = 2.f; f32 thickness = 2.f;
u32 color = RGBA_F(1, 1, 0, 0.25); 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); draw_solid_quad_line(G.world_canvas, quad, (thickness / PIXELS_PER_UNIT / G.world_view.zoom), color);
} }
/* Debug draw sprite transform */ /* Debug draw sprite transform */
{ {
debug_draw_xform(xf); debug_draw_xform(xf);
} }
/* Debug draw sprite pivot */ /* Debug draw sprite pivot */
{ {
u32 color = RGBA_F(1, 0, 0, 1); u32 color = RGBA_F(1, 0, 0, 1);
draw_solid_circle(G.world_canvas, ent->world_xform.og, 0.02, color, 20); draw_solid_circle(G.world_canvas, ent->world_xform.og, 0.02, color, 20);
}
} }
#endif
} }
#endif
} }
/* Debug draw info */ /* Debug draw info */
@ -911,14 +912,13 @@ INTERNAL void user_update(void)
struct v2 crosshair_pos = G.viewport_cursor; struct v2 crosshair_pos = G.viewport_cursor;
u32 tint = RGBA_F(1, 1, 1, 1); u32 tint = RGBA_F(1, 1, 1, 1);
struct v2 size = V2(0, 0); struct texture_tag crosshair_tag = texture_tag_from_path(STR("res/graphics/crosshair.ase"));
struct texture *t = texture_load_async(STR("res/graphics/crosshair.ase")); struct texture *t = texture_from_tag_async(texture_frame_scope, crosshair_tag);
if (t) {
size = t->size; struct v2 size = t->size;
struct xform xf = XFORM_TRS(.t = crosshair_pos, .s = size); struct xform xf = XFORM_TRS(.t = crosshair_pos, .s = size);
struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xf); 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); 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); 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)); 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 */ /* Debug draw info */
if (G.debug_draw) { if (G.debug_draw) {
struct temp_arena temp = arena_temp_begin(scratch.arena);
f32 spacing = 20; f32 spacing = 20;
struct v2 pos = V2(10, 8); 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))); draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("time: %F"), FMT_FLOAT((f64)G.time)));
pos.y += spacing; 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))); 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; 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))); 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; 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))); 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; 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))); 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; 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))); 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; 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))); 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; 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))); 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; 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)))); 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; 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))); 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; 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))); 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; 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")))); 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; pos.y += spacing;
arena_temp_end(temp);
}
arena_temp_end(temp);
} }
/* Push game cmds */ /* Push game cmds */
@ -1080,6 +1083,7 @@ INTERNAL void user_update(void)
* ========================== */ * ========================== */
sheet_scope_end(sheet_frame_scope); sheet_scope_end(sheet_frame_scope);
texture_scope_end(texture_frame_scope);
scratch_end(scratch); scratch_end(scratch);
} }