From 46454938c38d39593a28ac39ee2b4a26571e5db1 Mon Sep 17 00:00:00 2001 From: offtkp Date: Thu, 15 Aug 2024 04:43:29 +0300 Subject: [PATCH] Concurrency hell --- src/atlas.cpp | 139 +++++++++++++++++++++++++++++++++++++++++--------- src/atlas.h | 36 ++++++++----- 2 files changed, 137 insertions(+), 38 deletions(-) diff --git a/src/atlas.cpp b/src/atlas.cpp index 05d90739b..82ed75249 100644 --- a/src/atlas.cpp +++ b/src/atlas.cpp @@ -1,15 +1,30 @@ #include "atlas.h" #include "sokol_gfx.h" +#include +#include #include +#include +#include +#include +#include +#include #include #include -const int atlas_spacing = 4; // leaving some space between tiles to avoid bleeding +static_assert(sizeof(atlas_tile_id_t) == 8, "atlas_tile_id_t must be 8 bytes"); +static_assert(sizeof(atlas_map_id_t) == 1, "atlas_map_id_t must be 1 byte"); +static_assert(sizeof(atlas_tile_id_t::tile_id) == 3, "atlas_tile_id_t::tile_id must be 3 bytes"); -struct tile_size_t { - uint16_t x, y; -}; +[[noreturn]] void atlas_error(const char* msg, ...) { + va_list args; + va_start(args, msg); + vprintf(msg, args); + va_end(args); + exit(1); +} + +const int atlas_spacing = 4; // leaving some space between tiles to avoid bleeding struct cached_image_t { uint8_t* data; // always RGBA @@ -17,11 +32,17 @@ struct cached_image_t { int height; }; +struct atlas_map_t; + +std::mutex cached_images_mutex; +std::unordered_map cached_images; +std::atomic atlas_maps[256] = {}; + // Atlases are always square and power of two // This always starts as a single tile image, but if a new tile needs to be // added, it's resized to the next power of two struct atlas_t { - atlas_t(uint32_t tile_width, uint32_t tile_height) + atlas_t(uint16_t tile_width, uint16_t tile_height) : tile_width(tile_width), tile_height(tile_height) { image.id = SG_INVALID_ID; @@ -30,24 +51,32 @@ struct atlas_t { ~atlas_t() = default; atlas_t(const atlas_t&) = delete; atlas_t& operator=(const atlas_t&) = delete; - atlas_t(atlas_t&&) = default; - atlas_t& operator=(atlas_t&&) = default; + atlas_t(atlas_t&&) = delete; + atlas_t& operator=(atlas_t&&) = delete; + std::mutex mutex; std::vector data; // we construct the atlas here before uploading it to the GPU + std::unordered_map tiles; // maps a tile id to its position in the atlas + // this position can change if the atlas is resized + std::vector images; // images that are already added to the atlas sg_image image = {}; - int pixel_stride = 0; - int offset_x = 0, - offset_y = 0; // to keep track of where next tile needs to be placed, in pixels - int tile_width, tile_height; + uint32_t pixel_stride = 0; + uint16_t offset_x = 0, offset_y = 0; + const uint16_t tile_width, tile_height; bool resized = false; bool dirty = false; // needs the data to be reuploaded to the GPU void add_image(cached_image_t* image) { + std::unique_lock lock(mutex); dirty = true; - uint32_t tile_offset_x = offset_x; - uint32_t tile_offset_y = offset_y; + // First, check if we need to resize the atlas + // TODO: + + + uint16_t new_tile_offset_x = offset_x; + uint16_t new_tile_offset_y = offset_y; offset_x += tile_width + atlas_spacing; if (offset_x + tile_width > pixel_stride) @@ -62,22 +91,84 @@ struct atlas_t { { for (int x = 0; x < tile_width; x++) { - uint32_t atlas_offset = - ((tile_offset_x + x) * 4) + (((tile_offset_y + y) * pixel_stride) * 4); - uint32_t tile_offset = x * 4 + (y * 4 * tile_width); + uint32_t atlas_index = + ((new_tile_offset_x + x) * 4) + (((new_tile_offset_y + y) * pixel_stride) * 4); + uint32_t tile_index = x * 4 + (y * 4 * tile_width); - assert(atlas_offset + 3 < data.size()); - assert(tile_offset + 3 < tile_width * tile_height * 4); + assert(atlas_index + 3 < data.size()); + assert(tile_index + 3 < tile_width * tile_height * 4); - data[atlas_offset + 0] = image->data[tile_offset + 0]; - data[atlas_offset + 1] = image->data[tile_offset + 1]; - data[atlas_offset + 2] = image->data[tile_offset + 2]; - data[atlas_offset + 3] = image->data[tile_offset + 3]; + data[atlas_index + 0] = image->data[tile_index + 0]; + data[atlas_index + 1] = image->data[tile_index + 1]; + data[atlas_index + 2] = image->data[tile_index + 2]; + data[atlas_index + 3] = image->data[tile_index + 3]; } } } + +private: + void resize(); }; struct atlas_map_t { - std::unordered_map atlases; -}; \ No newline at end of file + TODO, use shared_ptr everywhere to make it fully thread safe? + std::unordered_map atlases; +}; + +atlas_map_id_t atlas_create_map() { + atlas_map_t* map = new atlas_map_t(); + for (int i = 0; i < 256; i++) { + atlas_map_t* expected = nullptr; + bool success = atlas_maps[i].compare_exchange_strong(expected, map); + if (success) { + return i; + } + } + + atlas_error("atlas_create_map: too many maps\n"); +} + +void atlas_destroy_map(atlas_map_id_t id) { + atlas_map_t* map = atlas_maps[id].exchange(nullptr); + delete map; +} + +atlas_tile_id_t atlas_add_tile_from_url(atlas_map_t* map, const char* url) { + +} + +bool atlas_has_tile(atlas_tile_id_t id) { + atlas_map_t* map = atlas_maps[id.map_id].load(); + + if (map == nullptr) { + atlas_error("atlas_has_tile: map is null\n"); + } + + atlas_t* atlas = nullptr; + + // Find atlas in atlas map + { + std::unique_lock lock(map->atlases_mutex); + + auto it = map->atlases.find(id.tile_width << 16 | id.tile_height); + if (it == map->atlases.end()) { + atlas_error("atlas_has_tile: atlas not found\n"); + } + + atlas = it->second; + if (atlas == nullptr) { + atlas_error("atlas_has_tile: atlas is null\n"); + } + } + + // Check for the tile in the atlas + { + std::unique_lock lock(atlas->mutex); + uint32_t tile_id = id.tile_id[0] | (id.tile_id[1] << 8) | (id.tile_id[2] << 16); + return atlas->tiles.find(tile_id) != atlas->tiles.end(); + } +} + +atlas_tile_t atlas_get_tile(atlas_tile_id_t id) { + +} \ No newline at end of file diff --git a/src/atlas.h b/src/atlas.h index 38eacd728..c951578bd 100644 --- a/src/atlas.h +++ b/src/atlas.h @@ -5,7 +5,6 @@ extern "C" { #endif -#include #include #include @@ -17,34 +16,43 @@ typedef struct { float x2, y2; } atlas_tile_t; -typedef uint32_t atlas_tile_id; +// There can be up to 256 atlas maps at a time, and each of them can have any number of atlases +// using different tile sizes +typedef uint8_t atlas_map_id_t; -// An atlas map is a map of atlases, which themselves are images with multiple tiles -typedef struct atlas_map_t atlas_map_t; +// Each atlas tile id is not a pointer, but a descriptor that contains the map id +// and the necessary info to find a tile in said atlas map +typedef struct { + uint16_t tile_width; + uint16_t tile_height; + atlas_map_id_t map_id; + uint8_t tile_id[3]; // 24-bits of precision, more than enough to represent any number of tiles in our + // supported tile sizes (width/height >= 8, atlas size <= 4096) +} atlas_tile_id_t; -atlas_map_t* atlas_create_map(); +atlas_map_id_t atlas_create_map(); -void atlas_destroy_map(atlas_map_t* map); +void atlas_destroy_map(atlas_map_id_t map); // Downloads an image from the url and adds it to the atlas map -atlas_tile_id atlas_add_tile_from_url(atlas_map_t* map, const char* url); +atlas_tile_id_t atlas_add_tile_from_url(atlas_map_id_t map, const char* url); // Loads an image from a path and adds it to the atlas map -atlas_tile_id atlas_add_tile_from_path(atlas_map_t* map, const char* path, bool use_separate_thread); +atlas_tile_id_t atlas_add_tile_from_path(atlas_map_id_t map, const char* path, bool use_separate_thread); // Checks if a tile is ready to be used -bool atlas_has_tile(atlas_map_t* map, atlas_tile_id id); +bool atlas_has_tile(atlas_tile_id_t id); // Must be called after atlas_has_tile ensures the tile is ready -atlas_tile_t atlas_get_tile(atlas_map_t* map, atlas_tile_id id); +atlas_tile_t atlas_get_tile(atlas_tile_id_t id); // Called from the main thread at the end of the frame to update the atlases if needed, for example if there's a need to resize // or if there are new tiles to add -void atlas_update_all(); - -void atlas_update_immediately(atlas_map_t* map); +void atlas_upload_all(); -void atlas_update_all_immediately(); +// Useful to upload a single atlas map immediately. For example if you add a bunch of images from paths and want to +// make them immediately available +void atlas_upload_single(atlas_map_id_t map); #ifdef __cplusplus }