merge texture & sheet into 'sprite'
This commit is contained in:
parent
cf3d678699
commit
00629aa988
BIN
res/graphics/white.ase
(Stored with Git LFS)
BIN
res/graphics/white.ase
(Stored with Git LFS)
Binary file not shown.
10
src/app.c
10
src/app.c
@ -11,9 +11,8 @@
|
||||
#include "resource.h"
|
||||
#include "asset_cache.h"
|
||||
#include "font.h"
|
||||
#include "texture.h"
|
||||
#include "sprite.h"
|
||||
#include "ttf.h"
|
||||
#include "sheet.h"
|
||||
#include "mixer.h"
|
||||
#include "sound.h"
|
||||
#include "util.h"
|
||||
@ -210,13 +209,12 @@ 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(&renderer_sr, &resource_sr);
|
||||
struct sheet_startup_receipt sheet_sr = sheet_startup(&resource_sr);
|
||||
struct sprite_startup_receipt sprite_sr = sprite_startup(&renderer_sr, &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);
|
||||
struct game_startup_receipt game_sr = game_startup(&mixer_sr, &sheet_sr, &sound_sr);
|
||||
struct user_startup_receipt user_sr = user_startup(&work_sr, &renderer_sr, &font_sr, &texture_sr, &draw_sr, &game_sr, &asset_cache_sr, &mixer_sr, &window);
|
||||
struct game_startup_receipt game_sr = game_startup(&mixer_sr, &sprite_sr, &sound_sr);
|
||||
struct user_startup_receipt user_sr = user_startup(&work_sr, &renderer_sr, &font_sr, &sprite_sr, &draw_sr, &game_sr, &asset_cache_sr, &mixer_sr, &window);
|
||||
struct playback_startup_receipt playback_sr = playback_startup(&mixer_sr);
|
||||
|
||||
(UNUSED)user_sr;
|
||||
|
||||
@ -370,12 +370,7 @@ struct renderer_handle {
|
||||
* Tag structs
|
||||
* ========================== */
|
||||
|
||||
struct texture_tag {
|
||||
u128 hash;
|
||||
struct string path;
|
||||
};
|
||||
|
||||
struct sheet_tag {
|
||||
struct sprite_tag {
|
||||
u128 hash;
|
||||
struct string path;
|
||||
};
|
||||
|
||||
23
src/config.h
23
src/config.h
@ -39,29 +39,6 @@
|
||||
#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
|
||||
* ========================== */
|
||||
|
||||
57
src/draw.c
57
src/draw.c
@ -3,10 +3,10 @@
|
||||
#include "math.h"
|
||||
#include "font.h"
|
||||
#include "scratch.h"
|
||||
#include "texture.h"
|
||||
#include "sprite.h"
|
||||
|
||||
GLOBAL struct {
|
||||
struct texture_tag solid_white;
|
||||
struct renderer_handle solid_white;
|
||||
} G = { 0 }, DEBUG_ALIAS(G, G_draw);
|
||||
|
||||
/* ========================== *
|
||||
@ -18,7 +18,8 @@ struct draw_startup_receipt draw_startup(struct renderer_startup_receipt *render
|
||||
{
|
||||
(UNUSED)renderer_sr;
|
||||
(UNUSED)font_sr;
|
||||
G.solid_white = texture_tag_from_path(STR("res/graphics/white.ase"));
|
||||
u32 pixel_white = 0xFFFFFFFF;
|
||||
G.solid_white = renderer_texture_alloc((struct image_rgba) { .width = 1, .height = 1, .pixels = &pixel_white } );
|
||||
return (struct draw_startup_receipt) { 0 };
|
||||
}
|
||||
|
||||
@ -26,7 +27,7 @@ struct draw_startup_receipt draw_startup(struct renderer_startup_receipt *render
|
||||
* Texture
|
||||
* ========================== */
|
||||
|
||||
INTERNAL void draw_texture_quad_internal(struct renderer_canvas *canvas, struct clip_rect clip, u32 tint, struct quad quad)
|
||||
INTERNAL void draw_sprite_quad_internal(struct renderer_canvas *canvas, struct clip_rect clip, u32 tint, struct quad quad)
|
||||
{
|
||||
struct texture_shader_vertex *vertices = NULL;
|
||||
vidx *indices = NULL;
|
||||
@ -68,17 +69,17 @@ INTERNAL void draw_texture_quad_internal(struct renderer_canvas *canvas, struct
|
||||
indices[5] = offset + 3;
|
||||
}
|
||||
|
||||
void draw_texture_quad(struct renderer_canvas *canvas, struct draw_texture_params params, struct quad quad)
|
||||
void draw_sprite_quad(struct renderer_canvas *canvas, struct draw_sprite_params params, struct quad quad)
|
||||
{
|
||||
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) { .sprite = params.sprite });
|
||||
|
||||
draw_texture_quad_internal(canvas, params.clip, params.tint, quad);
|
||||
draw_sprite_quad_internal(canvas, params.clip, params.tint, quad);
|
||||
}
|
||||
|
||||
void draw_texture_rect(struct renderer_canvas *canvas, struct draw_texture_params params, struct rect rect)
|
||||
void draw_sprite_rect(struct renderer_canvas *canvas, struct draw_sprite_params params, struct rect rect)
|
||||
{
|
||||
struct quad quad = quad_from_rect(rect);
|
||||
draw_texture_quad(canvas, params, quad);
|
||||
draw_sprite_quad(canvas, params, quad);
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
@ -117,7 +118,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_tag = G.solid_white });
|
||||
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
|
||||
draw_solid_poly_internal(canvas, array, color);
|
||||
}
|
||||
|
||||
@ -145,15 +146,15 @@ 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_tag = G.solid_white });
|
||||
draw_texture_quad_internal(canvas, CLIP_ALL, color, quad);
|
||||
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
|
||||
draw_sprite_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_tag = G.solid_white });
|
||||
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
|
||||
struct quad quad = quad_from_rect(rect);
|
||||
draw_texture_quad_internal(canvas, CLIP_ALL, color, quad);
|
||||
draw_sprite_quad_internal(canvas, CLIP_ALL, color, quad);
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
@ -162,16 +163,16 @@ 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_tag = G.solid_white });
|
||||
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
|
||||
struct quad quad = quad_from_line(start, end, thickness);
|
||||
draw_texture_quad_internal(canvas, CLIP_ALL, color, quad);
|
||||
draw_sprite_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_tag = G.solid_white });
|
||||
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
|
||||
struct quad quad = quad_from_ray(pos, rel, thickness);
|
||||
draw_texture_quad_internal(canvas, CLIP_ALL, color, quad);
|
||||
draw_sprite_quad_internal(canvas, CLIP_ALL, color, quad);
|
||||
}
|
||||
|
||||
void draw_solid_poly_line(struct renderer_canvas *canvas, struct v2_array array, b32 loop, f32 thickness, u32 color)
|
||||
@ -180,18 +181,18 @@ 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_tag = G.solid_white });
|
||||
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
|
||||
for (u64 i = 1; i < array.count; ++i) {
|
||||
struct v2 p1 = array.points[i - 1];
|
||||
struct v2 p2 = array.points[i];
|
||||
struct quad q = quad_from_line(p1, p2, thickness);
|
||||
draw_texture_quad_internal(canvas, CLIP_ALL, color, q);
|
||||
draw_sprite_quad_internal(canvas, CLIP_ALL, color, q);
|
||||
}
|
||||
if (loop && array.count > 2) {
|
||||
struct v2 p1 = array.points[array.count - 1];
|
||||
struct v2 p2 = array.points[0];
|
||||
struct quad q = quad_from_line(p1, p2, thickness);
|
||||
draw_texture_quad_internal(canvas, CLIP_ALL, color, q);
|
||||
draw_sprite_quad_internal(canvas, CLIP_ALL, color, q);
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,7 +211,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_tag = G.solid_white });
|
||||
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
|
||||
|
||||
const f32 head_width_ratio = 0.5f; /* Width of arrowhead relative to its length */
|
||||
|
||||
@ -237,7 +238,7 @@ void draw_solid_arrow_line(struct renderer_canvas *canvas, struct v2 start, stru
|
||||
draw_solid_poly_internal(canvas, head_points_v2_array, color);
|
||||
|
||||
struct quad line_quad = quad_from_line(start, head_start, thickness);
|
||||
draw_texture_quad_internal(canvas, CLIP_ALL, color, line_quad);
|
||||
draw_sprite_quad_internal(canvas, CLIP_ALL, color, line_quad);
|
||||
}
|
||||
|
||||
void draw_solid_arrow_ray(struct renderer_canvas *canvas, struct v2 pos, struct v2 rel, f32 thickness, f32 arrowhead_height, u32 color)
|
||||
@ -281,18 +282,18 @@ void draw_text_ex(struct renderer_canvas *canvas, struct font *font, struct v2 p
|
||||
|
||||
struct clip_rect clip = {
|
||||
{
|
||||
glyph->atlas_rect.x / font->image_size.x,
|
||||
glyph->atlas_rect.y / font->image_size.y
|
||||
glyph->atlas_rect.x / (f32)font->image_width,
|
||||
glyph->atlas_rect.y / (f32)font->image_height
|
||||
},
|
||||
|
||||
{
|
||||
(glyph->atlas_rect.x + glyph->atlas_rect.width) / font->image_size.x,
|
||||
(glyph->atlas_rect.y + glyph->atlas_rect.height) / font->image_size.y
|
||||
(glyph->atlas_rect.x + glyph->atlas_rect.width) / (f32)font->image_width,
|
||||
(glyph->atlas_rect.y + glyph->atlas_rect.height) / (f32)font->image_height
|
||||
}
|
||||
};
|
||||
|
||||
struct quad quad = quad_from_rect(RECT(x, y, width, height));
|
||||
draw_texture_quad_internal(canvas, clip, 0xFFFFFFFF, quad);
|
||||
draw_sprite_quad_internal(canvas, clip, 0xFFFFFFFF, quad);
|
||||
|
||||
draw_pos.x += glyph->advance * scale;
|
||||
}
|
||||
|
||||
10
src/draw.h
10
src/draw.h
@ -6,14 +6,14 @@ struct font;
|
||||
struct renderer_startup_receipt;
|
||||
struct font_startup_receipt;
|
||||
|
||||
#define DRAW_TEXTURE_PARAMS(...) ((struct draw_texture_params) { \
|
||||
#define DRAW_SPRITE_PARAMS(...) ((struct draw_sprite_params) { \
|
||||
.tint = COLOR_WHITE, \
|
||||
.clip = CLIP_ALL, \
|
||||
__VA_ARGS__ \
|
||||
})
|
||||
|
||||
struct draw_texture_params {
|
||||
struct texture_tag texture_tag;
|
||||
struct draw_sprite_params {
|
||||
struct sprite_tag sprite;
|
||||
struct clip_rect clip;
|
||||
u32 tint;
|
||||
};
|
||||
@ -22,8 +22,8 @@ struct draw_startup_receipt { i32 _; };
|
||||
struct draw_startup_receipt draw_startup(struct renderer_startup_receipt *renderer_sr,
|
||||
struct font_startup_receipt *font_sr);
|
||||
|
||||
void draw_texture_quad(struct renderer_canvas *canvas, struct draw_texture_params params, struct quad quad);
|
||||
void draw_texture_rect(struct renderer_canvas *canvas, struct draw_texture_params params, struct rect rect);
|
||||
void draw_sprite_quad(struct renderer_canvas *canvas, struct draw_sprite_params params, struct quad quad);
|
||||
void draw_sprite_rect(struct renderer_canvas *canvas, struct draw_sprite_params params, struct rect rect);
|
||||
|
||||
void draw_solid_poly(struct renderer_canvas *canvas, struct v2_array array, u32 color);
|
||||
void draw_solid_circle(struct renderer_canvas *canvas, struct v2 pos, f32 radius, u32 color, u32 detail);
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
#ifndef ENTITY_H
|
||||
#define ENTITY_H
|
||||
|
||||
#include "texture.h"
|
||||
#include "sheet.h"
|
||||
#include "sprite.h"
|
||||
#include "mixer.h"
|
||||
|
||||
enum entity_prop {
|
||||
@ -66,8 +65,7 @@ struct entity {
|
||||
/* ====================================================================== */
|
||||
/* Sprite */
|
||||
|
||||
struct texture_tag sprite_texture_tag;
|
||||
struct sheet_tag sprite_sheet_tag;
|
||||
struct sprite_tag sprite;
|
||||
struct string sprite_span_name;
|
||||
struct xform sprite_quad_xform;
|
||||
u32 sprite_tint;
|
||||
|
||||
@ -135,7 +135,8 @@ INTERNAL WORK_TASK_FUNC_DEF(font_load_asset_task, vparams)
|
||||
|
||||
/* Set font data */
|
||||
font->image_renderer_handle = image_renderer_handle;
|
||||
font->image_size = V2(result.image_data.width, result.image_data.height);
|
||||
font->image_width = result.image_data.width;
|
||||
font->image_height = result.image_data.height;
|
||||
font->glyphs_count = result.glyphs_count;
|
||||
font->point_size = point_size;
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#ifndef FONT_H
|
||||
#define FONT_H
|
||||
|
||||
#include "texture.h"
|
||||
#include "util.h"
|
||||
|
||||
struct asset;
|
||||
@ -22,7 +21,8 @@ struct font_glyph {
|
||||
|
||||
struct font {
|
||||
struct renderer_handle image_renderer_handle;
|
||||
struct v2 image_size;
|
||||
u32 image_width;
|
||||
u32 image_height;
|
||||
f32 point_size;
|
||||
u16 glyphs_count;
|
||||
struct font_glyph *glyphs;
|
||||
|
||||
57
src/game.c
57
src/game.c
@ -2,7 +2,7 @@
|
||||
#include "sys.h"
|
||||
#include "util.h"
|
||||
#include "world.h"
|
||||
#include "sheet.h"
|
||||
#include "sprite.h"
|
||||
#include "sound.h"
|
||||
#include "mixer.h"
|
||||
#include "math.h"
|
||||
@ -35,7 +35,7 @@ INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(game_shutdown);
|
||||
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(game_thread_entry_point, arg);
|
||||
|
||||
struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr,
|
||||
struct sheet_startup_receipt *sheet_sr,
|
||||
struct sprite_startup_receipt *sheet_sr,
|
||||
struct sound_startup_receipt *sound_sr)
|
||||
{
|
||||
(UNUSED)mixer_sr;
|
||||
@ -153,16 +153,16 @@ INTERNAL void recalculate_world_xform_recurse(struct entity *parent)
|
||||
}
|
||||
|
||||
#if 0
|
||||
INTERNAL struct v2 sheet_size_meters(struct sheet_tag s)
|
||||
INTERNAL struct v2 sprite_sheet_size_meters(struct sprite_tag s)
|
||||
{
|
||||
struct v2 size = { 0 };
|
||||
struct sheet_scope *scope = sheet_scope_begin();
|
||||
struct sprite_scope *scope = sprite_scope_begin();
|
||||
{
|
||||
struct sheet *sheet = sheet_from_tag_await(scope, s);
|
||||
struct sheet *sheet = sprite_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);
|
||||
sprite_scope_end(scope);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
@ -177,7 +177,7 @@ INTERNAL void game_update(void)
|
||||
* Begin frame cache scopes
|
||||
* ========================== */
|
||||
|
||||
struct sheet_scope *sheet_frame_scope = sheet_scope_begin();
|
||||
struct sprite_scope *sprite_frame_scope = sprite_scope_begin();
|
||||
|
||||
/* TODO: remove this (testing) */
|
||||
/* Initialize entities */
|
||||
@ -196,15 +196,10 @@ INTERNAL void game_update(void)
|
||||
e->valid = true;
|
||||
e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size);
|
||||
|
||||
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);
|
||||
|
||||
struct v2 sprite_pos = V2(0, 0);
|
||||
f32 sprite_rot = 0;
|
||||
struct v2 sprite_size = V2(0.5f, 0.5f);
|
||||
// struct v2 sprite_size = sheet_size_meters(sprite_sheet_tag);
|
||||
// struct v2 sprite_size = sprite_sheet_size_meters(sprite_sheet_tag);
|
||||
|
||||
struct v2 sprite_pivot;
|
||||
{
|
||||
@ -219,9 +214,8 @@ INTERNAL void game_update(void)
|
||||
sprite_xf = xform_scale(sprite_xf, sprite_size);
|
||||
e->sprite_quad_xform = sprite_xf;
|
||||
|
||||
e->sprite_texture_tag = sprite_texture_tag;
|
||||
e->sprite_sheet_tag = sprite_sheet_tag;
|
||||
e->sprite_span_name = sprite_span_name;
|
||||
e->sprite = sprite_tag_from_path(STR("res/graphics/tim.ase"));;
|
||||
e->sprite_span_name = STR("UNARMED");
|
||||
e->sprite_tint = COLOR_WHITE;
|
||||
|
||||
entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED);
|
||||
@ -248,15 +242,10 @@ INTERNAL void game_update(void)
|
||||
e->valid = true;
|
||||
e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size);
|
||||
|
||||
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);
|
||||
|
||||
struct v2 sprite_pos = V2(0, 0);
|
||||
f32 sprite_rot = 0;
|
||||
struct v2 sprite_size = V2(0.5f, 0.5f);
|
||||
// struct v2 sprite_size = sheet_size_meters(sprite_sheet_tag);
|
||||
// struct v2 sprite_size = sprite_sheet_size_meters(sprite_sheet_tag);
|
||||
|
||||
struct v2 sprite_pivot;
|
||||
{
|
||||
@ -271,9 +260,8 @@ INTERNAL void game_update(void)
|
||||
sprite_xf = xform_scale(sprite_xf, sprite_size);
|
||||
e->sprite_quad_xform = sprite_xf;
|
||||
|
||||
e->sprite_texture_tag = sprite_texture_tag;
|
||||
e->sprite_sheet_tag = sprite_sheet_tag;
|
||||
e->sprite_span_name = sprite_span_name;
|
||||
e->sprite = sprite_tag_from_path(STR("res/graphics/tim.ase"));;
|
||||
e->sprite_span_name = STR("UNARMED");
|
||||
e->sprite_tint = RGBA_F(0.5, 0.5, 0, 1);
|
||||
|
||||
//entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED);
|
||||
@ -318,16 +306,11 @@ INTERNAL void game_update(void)
|
||||
e->valid = true;
|
||||
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);
|
||||
|
||||
struct v2 sprite_size = V2(0.25f, 0.25f);
|
||||
// struct v2 sprite_size = sheet_size_meters(sprite_sheet_tag);
|
||||
// struct v2 sprite_size = sprite_sheet_size_meters(sprite_sheet_tag);
|
||||
e->sprite_quad_xform = xform_with_scale(XFORM_IDENT, sprite_size);
|
||||
|
||||
e->sprite_texture_tag = sprite_texture_tag;
|
||||
e->sprite_sheet_tag = sprite_sheet_tag;
|
||||
e->sprite = sprite_tag_from_path(STR("res/graphics/sound.ase"));;
|
||||
e->sprite_tint = RGBA_F(1, 1, 0, 1);
|
||||
|
||||
entity_enable_prop(e, ENTITY_PROP_TEST_SOUND_EMITTER);
|
||||
@ -409,11 +392,11 @@ INTERNAL void game_update(void)
|
||||
f64 time_in_frame = ent->animation_time_in_frame + G.world.dt;
|
||||
u64 span_frame_offset = ent->animation_frame;
|
||||
|
||||
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);
|
||||
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite);
|
||||
struct sprite_sheet_span span = sprite_sheet_get_span(sheet, ent->sprite_span_name);
|
||||
u64 frame_index = span.start + span_frame_offset;
|
||||
|
||||
struct sheet_frame frame = sheet_get_frame(sheet, frame_index);
|
||||
struct sprite_sheet_frame frame = sprite_sheet_get_frame(sheet, frame_index);
|
||||
while (time_in_frame > frame.duration) {
|
||||
time_in_frame -= frame.duration;
|
||||
++frame_index;
|
||||
@ -421,7 +404,7 @@ INTERNAL void game_update(void)
|
||||
/* Loop animation */
|
||||
frame_index = span.start;
|
||||
}
|
||||
frame = sheet_get_frame(sheet, frame_index);
|
||||
frame = sprite_sheet_get_frame(sheet, frame_index);
|
||||
}
|
||||
span_frame_offset = frame_index - span.start;
|
||||
|
||||
@ -604,7 +587,7 @@ INTERNAL void game_update(void)
|
||||
* End frame cache scopes
|
||||
* ========================== */
|
||||
|
||||
sheet_scope_end(sheet_frame_scope);
|
||||
sprite_scope_end(sprite_frame_scope);
|
||||
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
struct world;
|
||||
struct mixer_startup_receipt;
|
||||
struct sheet_startup_receipt;
|
||||
struct sprite_startup_receipt;
|
||||
struct sound_startup_receipt;
|
||||
|
||||
enum game_cmd_kind {
|
||||
@ -32,7 +32,7 @@ struct game_cmd_array {
|
||||
|
||||
struct game_startup_receipt { i32 _; };
|
||||
struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr,
|
||||
struct sheet_startup_receipt *sheet_sr,
|
||||
struct sprite_startup_receipt *sheet_sr,
|
||||
struct sound_startup_receipt *sound_sr);
|
||||
|
||||
void game_get_latest_tick(struct world *dest);
|
||||
|
||||
@ -22,8 +22,8 @@ enum shader_kind {
|
||||
};
|
||||
|
||||
struct texture_shader_parameters {
|
||||
struct renderer_handle texture_handle; /* Overrides texture_tag */
|
||||
struct texture_tag texture_tag;
|
||||
struct renderer_handle texture_handle; /* Overrides sprite */
|
||||
struct sprite_tag sprite;
|
||||
};
|
||||
|
||||
struct texture_shader_vertex {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#include "math.h"
|
||||
#include "inc.h"
|
||||
#include "tar.h"
|
||||
#include "texture.h"
|
||||
#include "sprite.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#define CINTERFACE
|
||||
@ -59,8 +59,8 @@ struct dx11_buffer {
|
||||
struct renderer_cmd {
|
||||
struct dx11_shader *shader;
|
||||
|
||||
struct renderer_handle texture_handle; /* Overrides texture_tag */
|
||||
struct texture_tag texture_tag;
|
||||
struct renderer_handle texture_handle; /* Overrides sprite */
|
||||
struct sprite_tag sprite;
|
||||
|
||||
/* Associated buffer data */
|
||||
u32 vertex_count;
|
||||
@ -704,13 +704,13 @@ void renderer_canvas_ensure_texture_cmd(struct renderer_canvas *canvas, struct t
|
||||
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_handle, params.texture_handle) ||
|
||||
!texture_tag_eq(last_cmd->texture_tag, params.texture_tag)) {
|
||||
!sprite_tag_eq(last_cmd->sprite, params.sprite)) {
|
||||
/* 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_handle = params.texture_handle,
|
||||
.texture_tag = params.texture_tag
|
||||
.sprite = params.sprite
|
||||
};
|
||||
|
||||
if (!canvas->cpu_cmd_store.cmd_first) {
|
||||
@ -841,7 +841,7 @@ void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_cou
|
||||
{
|
||||
__prof;
|
||||
|
||||
struct texture_scope *texture_scope = texture_scope_begin();
|
||||
struct sprite_scope *sprite_scope = sprite_scope_begin();
|
||||
|
||||
/* Resize back buffer */
|
||||
if (!v2_eq(G.backbuffer_size, screen_size)) {
|
||||
@ -881,7 +881,7 @@ void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_cou
|
||||
|
||||
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;
|
||||
texture_handle = sprite_texture_from_tag_async(sprite_scope, cmd->sprite)->renderer_handle;
|
||||
} else {
|
||||
texture_handle = cmd->texture_handle;
|
||||
}
|
||||
@ -925,7 +925,7 @@ void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_cou
|
||||
}
|
||||
renderer_capture_image_for_profiler(viewport.width, viewport.height);
|
||||
|
||||
texture_scope_end(texture_scope);
|
||||
sprite_scope_end(sprite_scope);
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
|
||||
841
src/sheet.c
841
src/sheet.c
@ -1,841 +0,0 @@
|
||||
#include "sheet.h"
|
||||
#include "arena.h"
|
||||
#include "log.h"
|
||||
#include "sys.h"
|
||||
#include "scratch.h"
|
||||
#include "resource.h"
|
||||
#include "ase.h"
|
||||
#include "util.h"
|
||||
#include "work.h"
|
||||
#include "atomic.h"
|
||||
#include "thread_local.h"
|
||||
#include "app.h"
|
||||
|
||||
#define SHEET_ARENA_RESERVE MEGABYTE(64)
|
||||
#define SHEET_LOOKUP_TABLE_BUCKET_RATIO 2.0
|
||||
|
||||
#define TCTX_ARENA_RESERVE MEGABYTE(64)
|
||||
#define CACHE_BUCKETS_COUNT 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 sheet_tag tag;
|
||||
u8 tag_path_buff[4096];
|
||||
};
|
||||
|
||||
/* ========================== *
|
||||
* 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 sheet */
|
||||
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 sheet *sheet;
|
||||
|
||||
/* 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 sheet_scope_reference {
|
||||
struct cache_node *cache_node;
|
||||
struct sheet_scope_reference *next_hash;
|
||||
struct sheet_scope_reference *next_free;
|
||||
};
|
||||
|
||||
/* ========================== *
|
||||
* Global state
|
||||
* ========================== */
|
||||
|
||||
GLOBAL struct {
|
||||
/* 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_sheet);
|
||||
|
||||
GLOBAL READONLY struct sheet g_sheet_nil = { 0 };
|
||||
GLOBAL READONLY struct sheet g_sheet_loading = {
|
||||
.loading = true
|
||||
};
|
||||
|
||||
/* ========================== *
|
||||
* Thread local state
|
||||
* ========================== */
|
||||
|
||||
struct sheet_tctx {
|
||||
struct arena arena;
|
||||
struct sheet_scope *first_free_scope;
|
||||
struct sheet_scope_reference *first_free_reference;
|
||||
};
|
||||
|
||||
INTERNAL THREAD_LOCAL_VAR_ALLOC_FUNC_DEF(sheet_tctx_alloc, vtctx)
|
||||
{
|
||||
struct sheet_tctx *tctx = (struct sheet_tctx *)vtctx;
|
||||
tctx->arena = arena_alloc(MEGABYTE(64));
|
||||
}
|
||||
|
||||
INTERNAL THREAD_LOCAL_VAR_RELEASE_FUNC_DEF(sheet_tctx_release, vtctx)
|
||||
{
|
||||
struct sheet_tctx *tctx = (struct sheet_tctx *)vtctx;
|
||||
arena_release(&tctx->arena);
|
||||
}
|
||||
|
||||
GLOBAL THREAD_LOCAL_VAR_DEF(tl_sheet_tctx, struct sheet_tctx, sheet_tctx_alloc, sheet_tctx_release);
|
||||
|
||||
/* ========================== *
|
||||
* Startup
|
||||
* ========================== */
|
||||
|
||||
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 resource_startup_receipt *resource_sr)
|
||||
{
|
||||
(UNUSED)resource_sr;
|
||||
|
||||
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] Sheet loader %F"),
|
||||
FMT_UINT(i));
|
||||
G.loader_threads[i] = sys_thread_alloc(sheet_loader_thread_entry_point, NULL, thread_name);
|
||||
}
|
||||
scratch_end(scratch);
|
||||
}
|
||||
G.evictor_thread = sys_thread_alloc(sheet_evictor_thread_entry_point, NULL, STR("[P0] Sheet evictor"));
|
||||
|
||||
app_register_exit_callback(&sheet_shutdown);
|
||||
|
||||
return (struct sheet_startup_receipt) { 0 };
|
||||
}
|
||||
|
||||
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(sheet_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 sheet_tag sheet_tag_from_path(struct string path)
|
||||
{
|
||||
struct sheet_tag res = { 0 };
|
||||
res.hash = HASH_FNV128_BASIS;
|
||||
res.hash = hash_fnv128(res.hash, BUFFER_FROM_STRING(path));
|
||||
res.path = 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
|
||||
* ========================== */
|
||||
|
||||
INTERNAL struct sheet init_sheet_from_ase_result(struct arena *arena, struct ase_decode_sheet_result ase)
|
||||
{
|
||||
struct sheet sheet = { 0 };
|
||||
|
||||
ASSERT(ase.num_frames >= 1);
|
||||
|
||||
/* Init frames */
|
||||
sheet.image_size = ase.image_size;
|
||||
sheet.frame_size = ase.frame_size;
|
||||
sheet.frames = arena_push_array_zero(arena, struct sheet_frame, ase.num_frames);
|
||||
sheet.frames_count = ase.num_frames;
|
||||
for (struct ase_frame *ase_frame = ase.frame_head; ase_frame; ase_frame = ase_frame->next) {
|
||||
u32 index = ase_frame->index;
|
||||
sheet.frames[index] = (struct sheet_frame) {
|
||||
.index = index,
|
||||
.duration = ase_frame->duration,
|
||||
.clip = ase_frame->clip
|
||||
};
|
||||
}
|
||||
|
||||
/* Init spans */
|
||||
sheet.spans_count = ase.num_spans;
|
||||
if (ase.num_spans > 0) {
|
||||
sheet.spans_dict = fixed_dict_init(arena, (u64)(ase.num_spans * SHEET_LOOKUP_TABLE_BUCKET_RATIO));
|
||||
for (struct ase_span *ase_span = ase.span_head; ase_span; ase_span = ase_span->next) {
|
||||
struct string name = string_copy(arena, ase_span->name);
|
||||
struct sheet_span *span = arena_push(arena, struct sheet_span);
|
||||
*span = (struct sheet_span) {
|
||||
.name = name,
|
||||
.start = ase_span->start,
|
||||
.end = ase_span->end
|
||||
};
|
||||
fixed_dict_set(arena, &sheet.spans_dict, name, span);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
INTERNAL void sheet_load(struct cache_node *n, struct sheet_tag tag)
|
||||
{
|
||||
__prof;
|
||||
struct temp_arena scratch = scratch_begin_no_conflict();
|
||||
|
||||
atomic_u32_eval_exchange(&n->state, CACHE_NODE_STATE_WORKING);
|
||||
struct string path = tag.path;
|
||||
|
||||
logf_info("Loading sheet \"%F\"", FMT_STR(path));
|
||||
sys_timestamp_t start_ts = sys_timestamp();
|
||||
|
||||
ASSERT(string_ends_with(path, STR(".ase")));
|
||||
|
||||
n->arena = arena_alloc(SHEET_ARENA_RESERVE);
|
||||
{
|
||||
/* Decode */
|
||||
struct ase_decode_sheet_result decoded = { 0 };
|
||||
if (resource_exists(path)) {
|
||||
struct resource sheet_rs = resource_open(path);
|
||||
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;
|
||||
#endif
|
||||
resource_close(sheet_rs);
|
||||
|
||||
/* Initialize */
|
||||
n->sheet = arena_push(&n->arena, struct sheet);
|
||||
*n->sheet = init_sheet_from_ase_result(&n->arena, decoded);
|
||||
} else {
|
||||
n->sheet = &g_sheet_nil;
|
||||
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 (cache size: %F bytes).",
|
||||
FMT_STR(path),
|
||||
FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts)),
|
||||
FMT_UINT(n->memory_usage));
|
||||
|
||||
|
||||
atomic_u32_eval_exchange(&n->state, CACHE_NODE_STATE_LOADED);
|
||||
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Scope
|
||||
* ========================== */
|
||||
|
||||
INTERNAL void scope_ensure_reference(struct sheet_scope *scope, struct cache_node *cache_node, u64 cache_bucket_index)
|
||||
{
|
||||
__prof;
|
||||
struct sheet_scope_reference **ref_next = &scope->reference_buckets[cache_bucket_index];
|
||||
struct sheet_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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ref) {
|
||||
/* Increment refcount */
|
||||
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) {
|
||||
ref = tctx->first_free_reference;
|
||||
tctx->first_free_reference = ref->next_free;
|
||||
MEMZERO_STRUCT(ref);
|
||||
} else {
|
||||
ref = arena_push_zero(&tctx->arena, struct sheet_scope_reference);
|
||||
}
|
||||
ref->cache_node = cache_node;
|
||||
*ref_next = ref;
|
||||
}
|
||||
}
|
||||
|
||||
struct sheet_scope *sheet_scope_begin(void)
|
||||
{
|
||||
struct sheet_tctx *tctx = thread_local_var_eval(&tl_sheet_tctx);
|
||||
|
||||
struct sheet_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 sheet_scope) {
|
||||
.reference_buckets = res->reference_buckets
|
||||
};
|
||||
} else {
|
||||
res = arena_push_zero(&tctx->arena, struct sheet_scope);
|
||||
res->reference_buckets = arena_push_array_zero(&tctx->arena, struct sheet_scope_reference *, CACHE_BUCKETS_COUNT);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void sheet_scope_end(struct sheet_scope *scope)
|
||||
{
|
||||
struct sheet_tctx *tctx = thread_local_var_eval(&tl_sheet_tctx);
|
||||
for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) {
|
||||
struct sheet_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;
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Cache interface
|
||||
* ========================== */
|
||||
|
||||
INTERNAL struct cache_node *node_lookup_touch(struct sheet_scope *scope, struct sheet_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);
|
||||
{
|
||||
/* 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;
|
||||
}
|
||||
sys_rw_mutex_unlock_exclusive(&bucket->rw_mutex);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
INTERNAL struct sheet *sheet_from_tag_internal(struct sheet_scope *scope, struct sheet_tag tag, b32 await)
|
||||
{
|
||||
struct sheet *res = &g_sheet_loading;
|
||||
struct cache_node *n = node_lookup_touch(scope, tag);
|
||||
|
||||
u32 state = atomic_u32_eval(&n->state);
|
||||
if (state == CACHE_NODE_STATE_LOADED) {
|
||||
res = n->sheet;
|
||||
} 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 sheet */
|
||||
if (await) {
|
||||
sheet_load(n, tag);
|
||||
res = n->sheet;
|
||||
} 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
struct sheet *sheet_from_tag_await(struct sheet_scope *scope, struct sheet_tag tag)
|
||||
{
|
||||
__prof;
|
||||
return sheet_from_tag_internal(scope, tag, true);
|
||||
}
|
||||
|
||||
struct sheet *sheet_from_tag_async(struct sheet_scope *scope, struct sheet_tag tag)
|
||||
{
|
||||
__prof;
|
||||
return sheet_from_tag_internal(scope, tag, false);
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Sheet data
|
||||
* ========================== */
|
||||
|
||||
struct sheet_span sheet_get_span(struct sheet *sheet, struct string name)
|
||||
{
|
||||
struct sheet_span res = { 0 };
|
||||
if (sheet->spans_count > 0) {
|
||||
struct sheet_span *entry = fixed_dict_get(&sheet->spans_dict, name);
|
||||
if (entry) {
|
||||
res = *entry;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
struct sheet_frame sheet_get_frame(struct sheet *sheet, u32 index)
|
||||
{
|
||||
if (sheet->frames_count > 0) {
|
||||
index = min_u32(sheet->frames_count - 1, index);
|
||||
return sheet->frames[index];
|
||||
} else {
|
||||
return (struct sheet_frame) {
|
||||
.index = 0,
|
||||
.duration = 0.1,
|
||||
.clip = CLIP_ALL
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Loader thread
|
||||
* ========================== */
|
||||
|
||||
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_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);
|
||||
{
|
||||
sheet_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(sheet_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) > SHEET_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 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;
|
||||
}
|
||||
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) > SHEET_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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
70
src/sheet.h
70
src/sheet.h
@ -1,70 +0,0 @@
|
||||
#ifndef SHEET_H
|
||||
#define SHEET_H
|
||||
|
||||
#include "util.h"
|
||||
|
||||
struct sheet_frame;
|
||||
struct resource_startup_receipt;
|
||||
|
||||
struct sheet {
|
||||
b32 loading;
|
||||
struct v2 image_size;
|
||||
struct v2 frame_size;
|
||||
u32 frames_count;
|
||||
struct sheet_frame *frames;
|
||||
u32 spans_count;
|
||||
struct fixed_dict spans_dict;
|
||||
};
|
||||
|
||||
/* ========================== *
|
||||
* Startup
|
||||
* ========================== */
|
||||
|
||||
struct sheet_startup_receipt { i32 _; };
|
||||
struct sheet_startup_receipt sheet_startup(struct resource_startup_receipt *resource_sr);
|
||||
|
||||
/* ========================== *
|
||||
* Tag
|
||||
* ========================== */
|
||||
|
||||
struct sheet_tag sheet_tag_from_path(struct string path);
|
||||
|
||||
/* ========================== *
|
||||
* Scope
|
||||
* ========================== */
|
||||
|
||||
struct sheet_scope {
|
||||
struct sheet_scope_reference **reference_buckets;
|
||||
struct sheet_scope *next_free;
|
||||
};
|
||||
|
||||
struct sheet_scope *sheet_scope_begin(void);
|
||||
void sheet_scope_end(struct sheet_scope *scope);
|
||||
|
||||
/* ========================== *
|
||||
* Load
|
||||
* ========================== */
|
||||
|
||||
struct sheet *sheet_from_tag_await(struct sheet_scope *scope, struct sheet_tag tag);
|
||||
struct sheet *sheet_from_tag_async(struct sheet_scope *scope, struct sheet_tag tag);
|
||||
|
||||
/* ========================== *
|
||||
* Inspect
|
||||
* ========================== */
|
||||
|
||||
struct sheet_span {
|
||||
struct string name;
|
||||
u32 start;
|
||||
u32 end;
|
||||
};
|
||||
|
||||
struct sheet_frame {
|
||||
u32 index;
|
||||
f64 duration;
|
||||
struct clip_rect clip;
|
||||
};
|
||||
|
||||
struct sheet_span sheet_get_span(struct sheet *sheet, struct string name);
|
||||
struct sheet_frame sheet_get_frame(struct sheet *sheet, u32 index);
|
||||
|
||||
#endif
|
||||
@ -1,4 +1,4 @@
|
||||
#include "texture.h"
|
||||
#include "sprite.h"
|
||||
#include "arena.h"
|
||||
#include "log.h"
|
||||
#include "sys.h"
|
||||
@ -12,10 +12,7 @@
|
||||
#include "app.h"
|
||||
#include "renderer.h"
|
||||
|
||||
/* Arena only used to store texture struct at the moment. Actual image data is allocated on GPU. */
|
||||
#define TEXTURE_ARENA_RESERVE MEGABYTE(1)
|
||||
|
||||
#define TCTX_ARENA_RESERVE MEGABYTE(64)
|
||||
#define CACHE_MEMORY_BUDGET (MEGABYTE(256))
|
||||
#define CACHE_BUCKETS_COUNT 1024
|
||||
|
||||
#define MAX_LOADER_THREADS 4
|
||||
@ -26,6 +23,14 @@
|
||||
/* 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 TCTX_ARENA_RESERVE MEGABYTE(64)
|
||||
|
||||
/* Texture arena only used to store texture struct at the moment. Actual image data is allocated on GPU. */
|
||||
#define TEXTURE_ARENA_RESERVE MEGABYTE(1)
|
||||
|
||||
#define SHEET_ARENA_RESERVE MEGABYTE(64)
|
||||
#define SHEET_SPAN_LOOKUP_TABLE_BUCKET_RATIO 2.0
|
||||
|
||||
/* ========================== *
|
||||
* Loader cmd structs
|
||||
* ========================== */
|
||||
@ -35,7 +40,7 @@ struct loader_cmd {
|
||||
struct loader_cmd *next_free;
|
||||
|
||||
struct cache_node *cache_node;
|
||||
struct texture_tag tag;
|
||||
struct sprite_tag tag;
|
||||
u8 tag_path_buff[512];
|
||||
};
|
||||
|
||||
@ -43,6 +48,11 @@ struct loader_cmd {
|
||||
* Cache structs
|
||||
* ========================== */
|
||||
|
||||
enum cache_node_kind {
|
||||
CACHE_NODE_KIND_TEXTURE,
|
||||
CACHE_NODE_KIND_SHEET
|
||||
};
|
||||
|
||||
enum cache_node_state {
|
||||
CACHE_NODE_STATE_NONE,
|
||||
CACHE_NODE_STATE_QUEUED,
|
||||
@ -51,20 +61,26 @@ enum cache_node_state {
|
||||
};
|
||||
|
||||
struct cache_node_refcount {
|
||||
i32 count; /* Number of scopes currently holding a reference to this texture */
|
||||
i32 count; /* Number of scopes currently holding a reference to this node */
|
||||
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_hash {
|
||||
u128 v;
|
||||
};
|
||||
|
||||
struct cache_node {
|
||||
u128 hash;
|
||||
enum cache_node_kind kind;
|
||||
struct cache_node_hash 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 texture *texture;
|
||||
struct sprite_texture *texture;
|
||||
struct sprite_sheet *sheet;
|
||||
|
||||
/* Hash list */
|
||||
struct cache_node *next_hash;
|
||||
@ -93,10 +109,10 @@ struct cache {
|
||||
struct cache_node *node_pool_first_free;
|
||||
};
|
||||
|
||||
struct texture_scope_reference {
|
||||
struct sprite_scope_reference {
|
||||
struct cache_node *cache_node;
|
||||
struct texture_scope_reference *next_hash;
|
||||
struct texture_scope_reference *next_free;
|
||||
struct sprite_scope_reference *next_hash;
|
||||
struct sprite_scope_reference *next_free;
|
||||
};
|
||||
|
||||
/* ========================== *
|
||||
@ -104,9 +120,11 @@ struct texture_scope_reference {
|
||||
* ========================== */
|
||||
|
||||
GLOBAL struct {
|
||||
struct arena perm_texture_arena;
|
||||
struct texture *nil_texture;
|
||||
struct texture *loading_texture;
|
||||
struct arena perm_arena;
|
||||
struct sprite_texture *nil_texture;
|
||||
struct sprite_texture *loading_texture;
|
||||
struct sprite_sheet *nil_sheet;
|
||||
struct sprite_sheet *loading_sheet;
|
||||
|
||||
/* Cache */
|
||||
struct cache cache;
|
||||
@ -129,31 +147,31 @@ GLOBAL struct {
|
||||
struct sys_condition_variable evictor_cv;
|
||||
|
||||
struct sys_thread evictor_thread;
|
||||
} G = { 0 }, DEBUG_ALIAS(G, G_texture);
|
||||
} G = { 0 }, DEBUG_ALIAS(G, G_sprite);
|
||||
|
||||
/* ========================== *
|
||||
* Thread local state
|
||||
* ========================== */
|
||||
|
||||
struct texture_tctx {
|
||||
struct sprite_tctx {
|
||||
struct arena arena;
|
||||
struct texture_scope *first_free_scope;
|
||||
struct texture_scope_reference *first_free_reference;
|
||||
struct sprite_scope *first_free_scope;
|
||||
struct sprite_scope_reference *first_free_reference;
|
||||
};
|
||||
|
||||
INTERNAL THREAD_LOCAL_VAR_ALLOC_FUNC_DEF(texture_tctx_alloc, vtctx)
|
||||
INTERNAL THREAD_LOCAL_VAR_ALLOC_FUNC_DEF(sprite_tctx_alloc, vtctx)
|
||||
{
|
||||
struct texture_tctx *tctx = (struct texture_tctx *)vtctx;
|
||||
struct sprite_tctx *tctx = (struct sprite_tctx *)vtctx;
|
||||
tctx->arena = arena_alloc(MEGABYTE(64));
|
||||
}
|
||||
|
||||
INTERNAL THREAD_LOCAL_VAR_RELEASE_FUNC_DEF(texture_tctx_release, vtctx)
|
||||
INTERNAL THREAD_LOCAL_VAR_RELEASE_FUNC_DEF(sprite_tctx_release, vtctx)
|
||||
{
|
||||
struct texture_tctx *tctx = (struct texture_tctx *)vtctx;
|
||||
struct sprite_tctx *tctx = (struct sprite_tctx *)vtctx;
|
||||
arena_release(&tctx->arena);
|
||||
}
|
||||
|
||||
GLOBAL THREAD_LOCAL_VAR_DEF(tl_texture_tctx, struct texture_tctx, texture_tctx_alloc, texture_tctx_release);
|
||||
GLOBAL THREAD_LOCAL_VAR_DEF(tl_sprite_tctx, struct sprite_tctx, sprite_tctx_alloc, sprite_tctx_release);
|
||||
|
||||
/* ========================== *
|
||||
* Purple-black image
|
||||
@ -197,35 +215,36 @@ 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);
|
||||
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(sprite_shutdown);
|
||||
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sprite_loader_thread_entry_point, arg);
|
||||
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sprite_evictor_thread_entry_point, arg);
|
||||
|
||||
struct texture_startup_receipt texture_startup(struct renderer_startup_receipt *renderer_sr,
|
||||
struct sprite_startup_receipt sprite_startup(struct renderer_startup_receipt *renderer_sr,
|
||||
struct resource_startup_receipt *resource_sr)
|
||||
{
|
||||
(UNUSED)renderer_sr;
|
||||
(UNUSED)resource_sr;
|
||||
|
||||
G.perm_arena = arena_alloc(MEGABYTE(1));
|
||||
{
|
||||
G.perm_texture_arena = arena_alloc(MEGABYTE(1));
|
||||
|
||||
/* Init loading texture */
|
||||
G.loading_texture = arena_push_zero(&G.perm_arena, struct sprite_texture);
|
||||
/* Init nil texture */
|
||||
G.nil_texture = arena_push_zero(&G.perm_texture_arena, struct texture);
|
||||
G.nil_texture = arena_push_zero(&G.perm_arena, struct sprite_texture);
|
||||
G.nil_texture->loaded = true;
|
||||
{
|
||||
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);
|
||||
/* Init loading sheet */
|
||||
G.loading_sheet = arena_push_zero(&G.perm_arena, struct sprite_sheet);
|
||||
/* Init nil sheet */
|
||||
G.nil_sheet = arena_push_zero(&G.perm_arena, struct sprite_sheet);
|
||||
G.nil_sheet->loaded = true;
|
||||
}
|
||||
|
||||
arena_set_readonly(&G.perm_arena);
|
||||
|
||||
G.cache.node_pool_mutex = sys_mutex_alloc();
|
||||
G.cache.arena = arena_alloc(GIGABYTE(64));
|
||||
@ -246,20 +265,20 @@ struct texture_startup_receipt texture_startup(struct renderer_startup_receipt *
|
||||
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"),
|
||||
STR("[P0] Sprite loader %F"),
|
||||
FMT_UINT(i));
|
||||
G.loader_threads[i] = sys_thread_alloc(texture_loader_thread_entry_point, NULL, thread_name);
|
||||
G.loader_threads[i] = sys_thread_alloc(sprite_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"));
|
||||
G.evictor_thread = sys_thread_alloc(sprite_evictor_thread_entry_point, NULL, STR("[P0] Sprite evictor"));
|
||||
|
||||
app_register_exit_callback(&texture_shutdown);
|
||||
app_register_exit_callback(&sprite_shutdown);
|
||||
|
||||
return (struct texture_startup_receipt) { 0 };
|
||||
return (struct sprite_startup_receipt) { 0 };
|
||||
}
|
||||
|
||||
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(texture_shutdown)
|
||||
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(sprite_shutdown)
|
||||
{
|
||||
__prof;
|
||||
|
||||
@ -291,25 +310,30 @@ INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(texture_shutdown)
|
||||
* Tag
|
||||
* ========================== */
|
||||
|
||||
struct texture_tag texture_tag_from_path(struct string path)
|
||||
struct sprite_tag sprite_tag_from_path(struct string path)
|
||||
{
|
||||
struct texture_tag res = { 0 };
|
||||
struct sprite_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)
|
||||
b32 sprite_tag_is_nil(struct sprite_tag tag)
|
||||
{
|
||||
return tag.hash == 0;
|
||||
}
|
||||
|
||||
b32 texture_tag_eq(struct texture_tag t1, struct texture_tag t2)
|
||||
b32 sprite_tag_eq(struct sprite_tag t1, struct sprite_tag t2)
|
||||
{
|
||||
return t1.hash == t2.hash;
|
||||
}
|
||||
|
||||
INTERNAL struct cache_node_hash cache_node_hash_from_tag_hash(u128 tag_hash, enum cache_node_kind kind)
|
||||
{
|
||||
return (struct cache_node_hash) { .v = hash_fnv128(tag_hash, BUFFER(1, (u8 *)&kind)) };
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Refcount
|
||||
* ========================== */
|
||||
@ -336,7 +360,7 @@ INTERNAL void node_refcount_add(struct cache_node *n, i32 amount)
|
||||
* Load
|
||||
* ========================== */
|
||||
|
||||
INTERNAL void texture_load(struct cache_node *n, struct texture_tag tag)
|
||||
INTERNAL void cache_node_load_texture(struct cache_node *n, struct sprite_tag tag)
|
||||
{
|
||||
__prof;
|
||||
struct temp_arena scratch = scratch_begin_no_conflict();
|
||||
@ -344,14 +368,15 @@ INTERNAL void texture_load(struct cache_node *n, struct texture_tag tag)
|
||||
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 sprite texture \"%F\"", FMT_STR(path));
|
||||
sys_timestamp_t start_ts = sys_timestamp();
|
||||
|
||||
ASSERT(string_ends_with(path, STR(".ase")));
|
||||
ASSERT(n->kind == CACHE_NODE_KIND_TEXTURE);
|
||||
|
||||
/* TODO: Arena probably overkill. Just using it to store texture struct. */
|
||||
n->arena = arena_alloc(TEXTURE_ARENA_RESERVE);
|
||||
u64 texture_memory_size = 0;
|
||||
u64 memory_size = 0;
|
||||
{
|
||||
/* Decode */
|
||||
struct ase_decode_image_result decoded = { 0 };
|
||||
@ -364,14 +389,16 @@ INTERNAL void texture_load(struct cache_node *n, struct texture_tag tag)
|
||||
resource_close(texture_rs);
|
||||
|
||||
/* Initialize */
|
||||
n->texture = arena_push(&n->arena, struct texture);
|
||||
n->texture->size = V2(decoded.image.width, decoded.image.height);
|
||||
n->texture = arena_push(&n->arena, struct sprite_texture);
|
||||
n->texture->width = decoded.image.width;
|
||||
n->texture->height = decoded.image.height;
|
||||
n->texture->renderer_handle = renderer_texture_alloc(decoded.image);
|
||||
n->texture->valid = true;
|
||||
n->texture->loaded = true;
|
||||
/* TODO: Query renderer for more accurate texture size in VRAM */
|
||||
texture_memory_size += (decoded.image.width * decoded.image.height) * sizeof(*decoded.image.pixels);
|
||||
memory_size += (decoded.image.width * decoded.image.height) * sizeof(*decoded.image.pixels);
|
||||
} else {
|
||||
n->texture = G.nil_texture;
|
||||
logf_error("Resource \"%F\" not found", path);
|
||||
logf_error("Sprite \"%F\" not found", path);
|
||||
}
|
||||
#if RESOURCE_RELOADING
|
||||
u64 cpy_len = min_u64(tag.path.len, ARRAY_COUNT(n->tag_path));
|
||||
@ -380,10 +407,10 @@ INTERNAL void texture_load(struct cache_node *n, struct texture_tag tag)
|
||||
#endif
|
||||
}
|
||||
arena_set_readonly(&n->arena);
|
||||
n->memory_usage = n->arena.committed + texture_memory_size;
|
||||
n->memory_usage = n->arena.committed + memory_size;
|
||||
atomic_u64_eval_add(&G.cache.memory_usage, n->memory_usage);
|
||||
|
||||
logf_info("Finished loading texture \"%F\" in %F seconds (cache size: %F bytes).",
|
||||
logf_info("Finished loading sprite 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));
|
||||
@ -393,15 +420,110 @@ INTERNAL void texture_load(struct cache_node *n, struct texture_tag tag)
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
INTERNAL struct sprite_sheet init_sheet_from_ase_result(struct arena *arena, struct ase_decode_sheet_result ase)
|
||||
{
|
||||
struct sprite_sheet sheet = { 0 };
|
||||
|
||||
ASSERT(ase.num_frames >= 1);
|
||||
|
||||
/* Init frames */
|
||||
sheet.image_size = ase.image_size;
|
||||
sheet.frame_size = ase.frame_size;
|
||||
sheet.frames = arena_push_array_zero(arena, struct sprite_sheet_frame, ase.num_frames);
|
||||
sheet.frames_count = ase.num_frames;
|
||||
for (struct ase_frame *ase_frame = ase.frame_head; ase_frame; ase_frame = ase_frame->next) {
|
||||
u32 index = ase_frame->index;
|
||||
sheet.frames[index] = (struct sprite_sheet_frame) {
|
||||
.index = index,
|
||||
.duration = ase_frame->duration,
|
||||
.clip = ase_frame->clip
|
||||
};
|
||||
}
|
||||
|
||||
/* Init spans */
|
||||
sheet.spans_count = ase.num_spans;
|
||||
if (ase.num_spans > 0) {
|
||||
sheet.spans_dict = fixed_dict_init(arena, (u64)(ase.num_spans * SHEET_SPAN_LOOKUP_TABLE_BUCKET_RATIO));
|
||||
for (struct ase_span *ase_span = ase.span_head; ase_span; ase_span = ase_span->next) {
|
||||
struct string name = string_copy(arena, ase_span->name);
|
||||
struct sprite_sheet_span *span = arena_push(arena, struct sprite_sheet_span);
|
||||
*span = (struct sprite_sheet_span) {
|
||||
.name = name,
|
||||
.start = ase_span->start,
|
||||
.end = ase_span->end
|
||||
};
|
||||
fixed_dict_set(arena, &sheet.spans_dict, name, span);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
INTERNAL void cache_node_load_sheet(struct cache_node *n, struct sprite_tag tag)
|
||||
{
|
||||
__prof;
|
||||
struct temp_arena scratch = scratch_begin_no_conflict();
|
||||
|
||||
atomic_u32_eval_exchange(&n->state, CACHE_NODE_STATE_WORKING);
|
||||
struct string path = tag.path;
|
||||
|
||||
logf_info("Loading sprite sheet \"%F\"", FMT_STR(path));
|
||||
sys_timestamp_t start_ts = sys_timestamp();
|
||||
|
||||
ASSERT(string_ends_with(path, STR(".ase")));
|
||||
ASSERT(n->kind == CACHE_NODE_KIND_SHEET);
|
||||
|
||||
n->arena = arena_alloc(SHEET_ARENA_RESERVE);
|
||||
{
|
||||
/* Decode */
|
||||
struct ase_decode_sheet_result decoded = { 0 };
|
||||
if (resource_exists(path)) {
|
||||
struct resource sheet_rs = resource_open(path);
|
||||
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;
|
||||
#endif
|
||||
resource_close(sheet_rs);
|
||||
|
||||
/* Initialize */
|
||||
n->sheet = arena_push(&n->arena, struct sprite_sheet);
|
||||
*n->sheet = init_sheet_from_ase_result(&n->arena, decoded);
|
||||
n->sheet->loaded = true;
|
||||
n->sheet->valid = true;
|
||||
} else {
|
||||
logf_error("Sprite \"%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 sprite sheet \"%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));
|
||||
|
||||
|
||||
atomic_u32_eval_exchange(&n->state, CACHE_NODE_STATE_LOADED);
|
||||
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Scope
|
||||
* ========================== */
|
||||
|
||||
INTERNAL void scope_ensure_reference(struct texture_scope *scope, struct cache_node *cache_node, u64 cache_bucket_index)
|
||||
INTERNAL void scope_ensure_reference(struct sprite_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;
|
||||
struct sprite_scope_reference **ref_next = &scope->reference_buckets[cache_bucket_index];
|
||||
struct sprite_scope_reference *ref = *ref_next;
|
||||
while (ref) {
|
||||
if (ref->cache_node == cache_node) {
|
||||
/* Scope already references node */
|
||||
@ -416,44 +538,44 @@ INTERNAL void scope_ensure_reference(struct texture_scope *scope, struct cache_n
|
||||
/* Increment refcount */
|
||||
node_refcount_add(cache_node, 1);
|
||||
/* Add reference to scope */
|
||||
struct texture_tctx *tctx = thread_local_var_eval(&tl_texture_tctx);
|
||||
struct sprite_tctx *tctx = thread_local_var_eval(&tl_sprite_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 = arena_push_zero(&tctx->arena, struct sprite_scope_reference);
|
||||
}
|
||||
ref->cache_node = cache_node;
|
||||
*ref_next = ref;
|
||||
}
|
||||
}
|
||||
|
||||
struct texture_scope *texture_scope_begin(void)
|
||||
struct sprite_scope *sprite_scope_begin(void)
|
||||
{
|
||||
struct texture_tctx *tctx = thread_local_var_eval(&tl_texture_tctx);
|
||||
struct sprite_tctx *tctx = thread_local_var_eval(&tl_sprite_tctx);
|
||||
|
||||
struct texture_scope *res = NULL;
|
||||
struct sprite_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) {
|
||||
*res = (struct sprite_scope) {
|
||||
.reference_buckets = res->reference_buckets
|
||||
};
|
||||
} else {
|
||||
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);
|
||||
res = arena_push_zero(&tctx->arena, struct sprite_scope);
|
||||
res->reference_buckets = arena_push_array_zero(&tctx->arena, struct sprite_scope_reference *, CACHE_BUCKETS_COUNT);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void texture_scope_end(struct texture_scope *scope)
|
||||
void sprite_scope_end(struct sprite_scope *scope)
|
||||
{
|
||||
struct texture_tctx *tctx = thread_local_var_eval(&tl_texture_tctx);
|
||||
struct sprite_tctx *tctx = thread_local_var_eval(&tl_sprite_tctx);
|
||||
for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) {
|
||||
struct texture_scope_reference *ref = scope->reference_buckets[i];
|
||||
struct sprite_scope_reference *ref = scope->reference_buckets[i];
|
||||
while (ref) {
|
||||
/* Decrement refcount */
|
||||
node_refcount_add(ref->cache_node, -1);
|
||||
@ -471,7 +593,7 @@ void texture_scope_end(struct texture_scope *scope)
|
||||
* Cache interface
|
||||
* ========================== */
|
||||
|
||||
INTERNAL struct cache_node *node_lookup_touch(struct texture_scope *scope, struct texture_tag tag)
|
||||
INTERNAL struct cache_node *node_lookup_touch(struct sprite_scope *scope, struct sprite_tag tag, enum cache_node_kind kind)
|
||||
{
|
||||
__prof;
|
||||
|
||||
@ -479,7 +601,8 @@ INTERNAL struct cache_node *node_lookup_touch(struct texture_scope *scope, struc
|
||||
struct cache_node *nonmatching = NULL;
|
||||
struct cache_node **nonmatching_next = NULL;
|
||||
|
||||
u64 cache_bucket_index = tag.hash % CACHE_BUCKETS_COUNT;
|
||||
struct cache_node_hash hash = cache_node_hash_from_tag_hash(tag.hash, kind);
|
||||
u64 cache_bucket_index = hash.v % CACHE_BUCKETS_COUNT;
|
||||
struct cache_bucket *bucket = &G.cache.buckets[cache_bucket_index];
|
||||
|
||||
/* Lookup */
|
||||
@ -489,7 +612,7 @@ INTERNAL struct cache_node *node_lookup_touch(struct texture_scope *scope, struc
|
||||
nonmatching_next = &bucket->first;
|
||||
n = *nonmatching_next;
|
||||
while (n) {
|
||||
if (n->hash == tag.hash) {
|
||||
if (n->hash.v == hash.v) {
|
||||
scope_ensure_reference(scope, n, cache_bucket_index);
|
||||
break;
|
||||
} else {
|
||||
@ -525,7 +648,10 @@ INTERNAL struct cache_node *node_lookup_touch(struct texture_scope *scope, struc
|
||||
nonmatching->next_hash = n;
|
||||
n->prev_hash = nonmatching;
|
||||
}
|
||||
n->hash = tag.hash;
|
||||
n->hash = cache_node_hash_from_tag_hash(tag.hash, kind);
|
||||
n->kind = kind;
|
||||
n->texture = G.nil_texture;
|
||||
n->sheet = G.nil_sheet;
|
||||
}
|
||||
sys_rw_mutex_unlock_exclusive(&bucket->rw_mutex);
|
||||
}
|
||||
@ -533,20 +659,37 @@ INTERNAL struct cache_node *node_lookup_touch(struct texture_scope *scope, struc
|
||||
return n;
|
||||
}
|
||||
|
||||
INTERNAL struct texture *texture_from_tag_internal(struct texture_scope *scope, struct texture_tag tag, b32 await)
|
||||
INTERNAL void *data_from_tag_internal(struct sprite_scope *scope, struct sprite_tag tag, enum cache_node_kind kind, b32 await)
|
||||
{
|
||||
struct texture *res = G.loading_texture;
|
||||
struct cache_node *n = node_lookup_touch(scope, tag);
|
||||
/* TODO: Replace switch statements */
|
||||
void *res = NULL;
|
||||
switch (kind) {
|
||||
case CACHE_NODE_KIND_TEXTURE: { res = G.loading_texture; } break;
|
||||
case CACHE_NODE_KIND_SHEET: { res = G.loading_sheet; } break;
|
||||
}
|
||||
|
||||
struct cache_node *n = node_lookup_touch(scope, tag, kind);
|
||||
|
||||
u32 state = atomic_u32_eval(&n->state);
|
||||
if (state == CACHE_NODE_STATE_LOADED) {
|
||||
res = n->texture;
|
||||
switch (kind) {
|
||||
case CACHE_NODE_KIND_TEXTURE: { res = n->texture; } break;
|
||||
case CACHE_NODE_KIND_SHEET: { res = n->sheet; } break;
|
||||
}
|
||||
} 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);
|
||||
switch (kind) {
|
||||
case CACHE_NODE_KIND_TEXTURE: {
|
||||
cache_node_load_texture(n, tag);
|
||||
res = n->texture;
|
||||
} break;
|
||||
case CACHE_NODE_KIND_SHEET: {
|
||||
cache_node_load_sheet(n, tag);
|
||||
res = n->sheet;
|
||||
} break;
|
||||
}
|
||||
} else {
|
||||
sys_mutex_lock(&G.loaders_mutex);
|
||||
{
|
||||
@ -588,23 +731,71 @@ INTERNAL struct texture *texture_from_tag_internal(struct texture_scope *scope,
|
||||
return res;
|
||||
}
|
||||
|
||||
struct texture *texture_from_tag_await(struct texture_scope *scope, struct texture_tag tag)
|
||||
/* ========================== *
|
||||
* Texture
|
||||
* ========================== */
|
||||
|
||||
struct sprite_texture *sprite_texture_from_tag_await(struct sprite_scope *scope, struct sprite_tag tag)
|
||||
{
|
||||
__prof;
|
||||
return texture_from_tag_internal(scope, tag, true);
|
||||
return (struct sprite_texture *)data_from_tag_internal(scope, tag, CACHE_NODE_KIND_TEXTURE, true);
|
||||
}
|
||||
|
||||
struct texture *texture_from_tag_async(struct texture_scope *scope, struct texture_tag tag)
|
||||
struct sprite_texture *sprite_texture_from_tag_async(struct sprite_scope *scope, struct sprite_tag tag)
|
||||
{
|
||||
__prof;
|
||||
return texture_from_tag_internal(scope, tag, false);
|
||||
return (struct sprite_texture *)data_from_tag_internal(scope, tag, CACHE_NODE_KIND_TEXTURE, false);
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Sheet
|
||||
* ========================== */
|
||||
|
||||
struct sprite_sheet *sprite_sheet_from_tag_await(struct sprite_scope *scope, struct sprite_tag tag)
|
||||
{
|
||||
__prof;
|
||||
return (struct sprite_sheet *)data_from_tag_internal(scope, tag, CACHE_NODE_KIND_SHEET, true);
|
||||
}
|
||||
|
||||
struct sprite_sheet *sprite_sheet_from_tag_async(struct sprite_scope *scope, struct sprite_tag tag)
|
||||
{
|
||||
__prof;
|
||||
return (struct sprite_sheet *)data_from_tag_internal(scope, tag, CACHE_NODE_KIND_SHEET, false);
|
||||
}
|
||||
|
||||
struct sprite_sheet_span sprite_sheet_get_span(struct sprite_sheet *sheet, struct string name)
|
||||
{
|
||||
__prof;
|
||||
struct sprite_sheet_span res = { 0 };
|
||||
if (sheet->spans_count > 0) {
|
||||
struct sprite_sheet_span *entry = fixed_dict_get(&sheet->spans_dict, name);
|
||||
if (entry) {
|
||||
res = *entry;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
struct sprite_sheet_frame sprite_sheet_get_frame(struct sprite_sheet *sheet, u32 index)
|
||||
{
|
||||
__prof;
|
||||
if (sheet->frames_count > 0) {
|
||||
index = min_u32(sheet->frames_count - 1, index);
|
||||
return sheet->frames[index];
|
||||
} else {
|
||||
return (struct sprite_sheet_frame) {
|
||||
.index = 0,
|
||||
.duration = 0.1,
|
||||
.clip = CLIP_ALL
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Loader thread
|
||||
* ========================== */
|
||||
|
||||
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_loader_thread_entry_point, arg)
|
||||
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sprite_loader_thread_entry_point, arg)
|
||||
{
|
||||
__prof;
|
||||
(UNUSED)arg;
|
||||
@ -633,7 +824,15 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_loader_thread_entry_point, arg)
|
||||
/* Do work (temporarily unlock) */
|
||||
sys_mutex_unlock(&G.loaders_mutex);
|
||||
{
|
||||
texture_load(cmd->cache_node, cmd->tag);
|
||||
struct cache_node *n = cmd->cache_node;
|
||||
switch (n->kind) {
|
||||
case CACHE_NODE_KIND_TEXTURE: {
|
||||
cache_node_load_texture(n, cmd->tag);
|
||||
} break;
|
||||
case CACHE_NODE_KIND_SHEET: {
|
||||
cache_node_load_sheet(n, cmd->tag);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
sys_mutex_lock(&G.loaders_mutex);
|
||||
|
||||
@ -664,7 +863,7 @@ struct evict_node {
|
||||
|
||||
};
|
||||
|
||||
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_evictor_thread_entry_point, arg)
|
||||
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sprite_evictor_thread_entry_point, arg)
|
||||
{
|
||||
(UNUSED)arg;
|
||||
|
||||
@ -689,7 +888,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_evictor_thread_entry_point, arg
|
||||
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;
|
||||
b32 cache_over_budget = atomic_u64_eval(&G.cache.memory_usage) > CACHE_MEMORY_BUDGET;
|
||||
if (cache_over_budget || RESOURCE_RELOADING) {
|
||||
__profscope(eviction_scan);
|
||||
for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) {
|
||||
@ -716,7 +915,14 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_evictor_thread_entry_point, arg
|
||||
}
|
||||
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));
|
||||
switch (n->kind) {
|
||||
case CACHE_NODE_KIND_TEXTURE: {
|
||||
logf_info("Resource file for sprite texture \"%F\" has changed. Evicting to allow for reloading.", FMT_STR(path));
|
||||
} break;
|
||||
case CACHE_NODE_KIND_SHEET: {
|
||||
logf_info("Resource file for sprite sheet \"%F\" has changed. Evicting to allow for reloading.", FMT_STR(path));
|
||||
} break;
|
||||
}
|
||||
consider_for_eviction = true;
|
||||
force_evict = true;
|
||||
}
|
||||
@ -791,7 +997,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_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) > TEXTURE_CACHE_MEMORY_BUDGET) {
|
||||
} else if (en->force_evict || atomic_u64_eval(&G.cache.memory_usage) > CACHE_MEMORY_BUDGET) {
|
||||
/* Remove from cache bucket */
|
||||
if (n->prev_hash) {
|
||||
n->prev_hash->next_hash = n->next_hash;
|
||||
@ -820,7 +1026,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(texture_evictor_thread_entry_point, arg
|
||||
__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) {
|
||||
if (n->kind == CACHE_NODE_KIND_TEXTURE && n->texture->valid) {
|
||||
renderer_texture_release(n->texture->renderer_handle);
|
||||
}
|
||||
arena_release(&n->arena);
|
||||
87
src/sprite.h
Normal file
87
src/sprite.h
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef SPRITE_H
|
||||
#define SPRITE_H
|
||||
|
||||
#include "util.h"
|
||||
|
||||
struct renderer_startup_receipt;
|
||||
struct resource_startup_receipt;
|
||||
|
||||
/* ========================== *
|
||||
* Startup
|
||||
* ========================== */
|
||||
|
||||
struct sprite_startup_receipt { i32 _; };
|
||||
struct sprite_startup_receipt sprite_startup(struct renderer_startup_receipt *renderer_sr,
|
||||
struct resource_startup_receipt *resource_sr);
|
||||
|
||||
/* ========================== *
|
||||
* Tag
|
||||
* ========================== */
|
||||
|
||||
struct sprite_tag sprite_tag_from_path(struct string path);
|
||||
b32 sprite_tag_is_nil(struct sprite_tag tag);
|
||||
b32 sprite_tag_eq(struct sprite_tag t1, struct sprite_tag t2);
|
||||
|
||||
/* ========================== *
|
||||
* Scope
|
||||
* ========================== */
|
||||
|
||||
struct sprite_scope {
|
||||
struct sprite_scope_reference **reference_buckets;
|
||||
struct sprite_scope *next_free;
|
||||
};
|
||||
|
||||
struct sprite_scope *sprite_scope_begin(void);
|
||||
void sprite_scope_end(struct sprite_scope *scope);
|
||||
|
||||
/* ========================== *
|
||||
* Texture
|
||||
* ========================== */
|
||||
|
||||
struct sprite_texture {
|
||||
b32 loaded;
|
||||
b32 valid;
|
||||
struct renderer_handle renderer_handle;
|
||||
u32 width;
|
||||
u32 height;
|
||||
};
|
||||
|
||||
struct sprite_texture *sprite_texture_from_tag_await(struct sprite_scope *scope, struct sprite_tag tag);
|
||||
struct sprite_texture *sprite_texture_from_tag_async(struct sprite_scope *scope, struct sprite_tag tag);
|
||||
|
||||
/* ========================== *
|
||||
* Sheet
|
||||
* ========================== */
|
||||
|
||||
struct sprite_sheet {
|
||||
b32 loaded;
|
||||
b32 valid;
|
||||
struct v2 image_size;
|
||||
struct v2 frame_size;
|
||||
u32 frames_count;
|
||||
struct sprite_sheet_frame *frames;
|
||||
u32 spans_count;
|
||||
struct fixed_dict spans_dict;
|
||||
};
|
||||
|
||||
struct sprite_sheet_span {
|
||||
struct string name;
|
||||
u32 start;
|
||||
u32 end;
|
||||
};
|
||||
|
||||
struct sprite_sheet_frame {
|
||||
u32 index;
|
||||
f64 duration;
|
||||
struct clip_rect clip;
|
||||
};
|
||||
|
||||
/* Load */
|
||||
struct sprite_sheet *sprite_sheet_from_tag_await(struct sprite_scope *scope, struct sprite_tag tag);
|
||||
struct sprite_sheet *sprite_sheet_from_tag_async(struct sprite_scope *scope, struct sprite_tag tag);
|
||||
|
||||
/* Index */
|
||||
struct sprite_sheet_span sprite_sheet_get_span(struct sprite_sheet *sheet, struct string name);
|
||||
struct sprite_sheet_frame sprite_sheet_get_frame(struct sprite_sheet *sheet, u32 index);
|
||||
|
||||
#endif
|
||||
@ -565,6 +565,7 @@ struct sys_file_map sys_file_map_open_read(struct sys_file file)
|
||||
);
|
||||
|
||||
if (!map_handle) {
|
||||
ASSERT(false);
|
||||
return (struct sys_file_map) { 0 };
|
||||
}
|
||||
|
||||
@ -584,6 +585,7 @@ struct sys_file_map sys_file_map_open_read(struct sys_file file)
|
||||
}
|
||||
} else {
|
||||
/* File is empty */
|
||||
ASSERT(false);
|
||||
return (struct sys_file_map) { 0 };
|
||||
}
|
||||
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
#ifndef TEXTURE_H
|
||||
#define TEXTURE_H
|
||||
|
||||
#include "util.h"
|
||||
|
||||
struct renderer_startup_receipt;
|
||||
struct resource_startup_receipt;
|
||||
|
||||
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 renderer_startup_receipt *renderer_sr,
|
||||
struct resource_startup_receipt *resource_sr);
|
||||
|
||||
/* ========================== *
|
||||
* 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
|
||||
42
src/user.c
42
src/user.c
@ -1,7 +1,7 @@
|
||||
#include "user.h"
|
||||
#include "renderer.h"
|
||||
#include "font.h"
|
||||
#include "texture.h"
|
||||
#include "sprite.h"
|
||||
#include "draw.h"
|
||||
#include "intrinsics.h"
|
||||
#include "app.h"
|
||||
@ -104,7 +104,7 @@ INTERNAL SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(window_event_callback, event);
|
||||
struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr,
|
||||
struct renderer_startup_receipt *renderer_sr,
|
||||
struct font_startup_receipt *font_sr,
|
||||
struct texture_startup_receipt *texture_sr,
|
||||
struct sprite_startup_receipt *sprite_sr,
|
||||
struct draw_startup_receipt *draw_sr,
|
||||
struct game_startup_receipt *game_sr,
|
||||
struct asset_cache_startup_receipt *asset_cache_sr,
|
||||
@ -114,7 +114,7 @@ struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr,
|
||||
(UNUSED)work_sr;
|
||||
(UNUSED)renderer_sr;
|
||||
(UNUSED)font_sr;
|
||||
(UNUSED)texture_sr;
|
||||
(UNUSED)sprite_sr;
|
||||
(UNUSED)draw_sr;
|
||||
(UNUSED)game_sr;
|
||||
(UNUSED)asset_cache_sr;
|
||||
@ -401,8 +401,7 @@ INTERNAL void user_update(void)
|
||||
* ========================== */
|
||||
|
||||
/* 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 sprite_scope *sprite_frame_scope = sprite_scope_begin();
|
||||
|
||||
/* ========================== *
|
||||
* Produce interpolated tick
|
||||
@ -759,23 +758,23 @@ INTERNAL void user_update(void)
|
||||
b32 skip_debug_draw_transform = ent == active_camera;
|
||||
|
||||
/* Draw sprite */
|
||||
if (!texture_tag_is_nil(ent->sprite_texture_tag)) {
|
||||
if (!sprite_tag_is_nil(ent->sprite)) {
|
||||
/* Draw texture */
|
||||
struct texture_tag texture_tag = ent->sprite_texture_tag;
|
||||
struct sprite_tag sprite = ent->sprite;
|
||||
/* TODO: Remove this once renderer is rewritten to work with tags */
|
||||
struct texture *texture = texture_from_tag_async(texture_frame_scope, texture_tag);
|
||||
struct sprite_texture *texture = sprite_texture_from_tag_async(sprite_frame_scope, sprite);
|
||||
|
||||
struct xform xf = xform_mul(ent->world_xform, ent->sprite_quad_xform);
|
||||
struct quad quad = quad_mul_xform(QUAD_UNIT_SQUARE_CENTERED, xf);
|
||||
|
||||
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 sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, sprite);
|
||||
struct sprite_sheet_frame frame = { 0 };
|
||||
{
|
||||
struct sheet_span span = sheet_get_span(sheet, ent->sprite_span_name);
|
||||
struct sprite_sheet_span span = sprite_sheet_get_span(sheet, ent->sprite_span_name);
|
||||
u64 frame_index = span.start + ent->animation_frame;
|
||||
frame = sheet_get_frame(sheet, frame_index);
|
||||
frame = sprite_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) {
|
||||
@ -785,17 +784,17 @@ INTERNAL void user_update(void)
|
||||
/* Loop animation */
|
||||
frame_index = span.start;
|
||||
}
|
||||
frame = sheet_get_frame(sheet, frame_index);
|
||||
frame = sprite_sheet_get_frame(sheet, frame_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.texture_tag = texture_tag, .tint = tint, .clip = frame.clip);
|
||||
struct draw_sprite_params params = DRAW_SPRITE_PARAMS(.sprite = sprite, .tint = tint, .clip = frame.clip);
|
||||
|
||||
/* TODO: Check for texture loading as well */
|
||||
if (!sheet->loading && !texture->loading) {
|
||||
if (sheet->loaded && texture->loaded) {
|
||||
/* TODO: Fade in a placeholder or something instead of drawing nothing. */
|
||||
draw_texture_quad(G.world_canvas, params, quad);
|
||||
draw_sprite_quad(G.world_canvas, params, quad);
|
||||
}
|
||||
|
||||
#if 0
|
||||
@ -912,13 +911,13 @@ INTERNAL void user_update(void)
|
||||
struct v2 crosshair_pos = G.viewport_cursor;
|
||||
u32 tint = RGBA_F(1, 1, 1, 1);
|
||||
|
||||
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 sprite_tag crosshair_tag = sprite_tag_from_path(STR("res/graphics/crosshair.ase"));
|
||||
struct sprite_texture *t = sprite_texture_from_tag_async(sprite_frame_scope, crosshair_tag);
|
||||
|
||||
struct v2 size = t->size;
|
||||
struct v2 size = V2(t->width, t->height);
|
||||
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);
|
||||
draw_sprite_quad(G.viewport_canvas, DRAW_SPRITE_PARAMS(.sprite = 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));
|
||||
@ -1082,8 +1081,7 @@ INTERNAL void user_update(void)
|
||||
* End frame cache scopes
|
||||
* ========================== */
|
||||
|
||||
sheet_scope_end(sheet_frame_scope);
|
||||
texture_scope_end(texture_frame_scope);
|
||||
sprite_scope_end(sprite_frame_scope);
|
||||
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ struct sys_window;
|
||||
struct work_startup_receipt;
|
||||
struct renderer_startup_receipt;
|
||||
struct font_startup_receipt;
|
||||
struct texture_startup_receipt;
|
||||
struct sprite_startup_receipt;
|
||||
struct draw_startup_receipt;
|
||||
struct game_startup_receipt;
|
||||
struct asset_cache_startup_receipt;
|
||||
@ -37,7 +37,7 @@ struct user_startup_receipt { i32 _; };
|
||||
struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr,
|
||||
struct renderer_startup_receipt *renderer_sr,
|
||||
struct font_startup_receipt *font_sr,
|
||||
struct texture_startup_receipt *texture_sr,
|
||||
struct sprite_startup_receipt *sprite_sr,
|
||||
struct draw_startup_receipt *draw_sr,
|
||||
struct game_startup_receipt *game_sr,
|
||||
struct asset_cache_startup_receipt *asset_cache_sr,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user