diff --git a/src/main.c b/src/main.c index 0f48f06c6..15ee1b057 100644 --- a/src/main.c +++ b/src/main.c @@ -519,26 +519,22 @@ typedef struct se_ra_tracker_node{ }se_ra_tracker_node_t; typedef struct se_ra_challenge_indicator_node{ uint32_t id; - sg_image image; + atlas_tile_t image; struct se_ra_challenge_indicator_node* next; } se_ra_challenge_indicator_node_t; -typedef struct{ - int x, y; -}se_ra_atlas_offset_t; typedef struct{ rc_client_t* client; char username[256]; char password[256]; - sg_image image; - se_ra_atlas_offset_t** achievement_images; + atlas_tile_t image; + atlas_tile_t** achievement_images; // TODO: make widgets that use these lists and progress indicator rc_client_achievement_list_t* achievement_list; se_ra_tracker_node_t* tracker_list; se_ra_challenge_indicator_node_t* challenge_indicator_list; - sg_image progress_indicator_image; + atlas_tile_t progress_indicator_image; bool progress_indicator_shown; char measured_progress[24]; - sg_image atlas; // for achievement images bool pending_login; }se_ra_info_t; #endif @@ -1741,10 +1737,7 @@ static uint32_t se_ra_read_memory_callback(uint32_t address, uint8_t* buffer, ui return 0; } static void se_ra_game_cleanup(){ - if(ra_info.image.id != SG_INVALID_ID){ - sg_destroy_image((sg_image){ra_info.image.id}); - ra_info.image.id = SG_INVALID_ID; - } + ra_reset(); rc_client_achievement_list_t* list = ra_info.achievement_list; if (list){ for (int i = 0; i < list->num_buckets; i++) @@ -1789,7 +1782,6 @@ static void se_ra_load_game_callback(int result, const char* error_message, rc_c ra_get_image(url, &ra_info.image); } - mutex_lock(ra_get_mutex()); if(ra_info.achievement_list) // TODO: deduplicate this code { rc_client_destroy_achievement_list(ra_info.achievement_list); @@ -1798,12 +1790,12 @@ static void se_ra_load_game_callback(int result, const char* error_message, rc_c RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS); rc_client_achievement_list_t* list = ra_info.achievement_list; - ra_info.achievement_images = (se_ra_atlas_offset_t**)malloc(sizeof(se_ra_atlas_offset_t*)*list->num_buckets); + ra_info.achievement_images = (atlas_tile_t**)malloc(sizeof(atlas_tile_t*)*list->num_buckets); for (int i = 0; i < list->num_buckets; i++) { uint32_t num_achievements=list->buckets[i].num_achievements; - ra_info.achievement_images[i] = (se_ra_atlas_offset_t*)malloc(sizeof(se_ra_atlas_offset_t)*num_achievements); - memset(ra_info.achievement_images[i], 0, sizeof(se_ra_atlas_offset_t)*num_achievements); + ra_info.achievement_images[i] = (atlas_tile_t*)malloc(sizeof(atlas_tile_t)*num_achievements); + memset(ra_info.achievement_images[i], 0, sizeof(atlas_tile_t)*num_achievements); for (int j = 0; j < num_achievements; j++) { char url[512]; @@ -1822,7 +1814,6 @@ static void se_ra_load_game_callback(int result, const char* error_message, rc_c printf("locked\n"); } } - mutex_unlock(ra_get_mutex()); } static void se_ra_load_game(){ if(!emu_state.rom_loaded)return; @@ -1947,7 +1938,7 @@ static void se_ra_challenge_indicator_show(const rc_client_achievement_t* achiev char url[128]; if (rc_client_achievement_get_image_url(achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED, url, sizeof(url)) == RC_OK) { - ra_get_image(url, &new_indicator->image); + // ra_get_image(url, &new_indicator->image); } } static void se_ra_challenge_indicator_hide(const rc_client_achievement_t* achievement) @@ -1979,7 +1970,7 @@ static void se_ra_progress_indicator_update(const rc_client_achievement_t* achie if (rc_client_achievement_get_image_url(achievement, RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE, url, sizeof(url)) == RC_OK) { - ra_get_image(url, &ra_info.progress_indicator_image); + // ra_get_image(url, &ra_info.progress_indicator_image); } strncpy(ra_info.measured_progress, achievement->measured_progress, sizeof(ra_info.measured_progress)); @@ -2101,30 +2092,6 @@ static void se_ra_initialize() { free(text); } } - - // Create the texture - sg_image_desc desc={ - .type= SG_IMAGETYPE_2D, - .render_target= false, - .width= atlas_pixel_stride, - .height= atlas_pixel_stride, - .num_slices= 1, - .num_mipmaps= 1, - .usage= SG_USAGE_STREAM, - .pixel_format= SG_PIXELFORMAT_RGBA8, - .sample_count= 1, - .min_filter= SG_FILTER_LINEAR, - .mag_filter= SG_FILTER_LINEAR, - .wrap_u= SG_WRAP_CLAMP_TO_EDGE, - .wrap_v= SG_WRAP_CLAMP_TO_EDGE, - .wrap_w= SG_WRAP_CLAMP_TO_EDGE, - .border_color= SG_BORDERCOLOR_TRANSPARENT_BLACK, - .max_anisotropy= 1, - .min_lod= 0.0f, - .max_lod= 1e9f, - }; - - ra_info.atlas = sg_make_image(&desc); } #endif @@ -6374,13 +6341,13 @@ void se_draw_menu_panel(){ se_text("Username"); igSameLine(win_w-150,0); if(pending_login)se_push_disabled(); - igInputText("##Username",ra_info.username,sizeof(ra_info.username),ImGuiInputTextFlags_None,NULL,NULL); + bool enter = igInputText("##Username",ra_info.username,sizeof(ra_info.username),ImGuiInputTextFlags_EnterReturnsTrue,NULL,NULL); if(pending_login)se_pop_disabled(); se_text("Password"); igSameLine(win_w-150,0); if(pending_login)se_push_disabled(); - igInputText("##Password",ra_info.password,sizeof(ra_info.password),ImGuiInputTextFlags_Password,NULL,NULL); - if(se_button(ICON_FK_SIGN_IN " Login", (ImVec2){0,0})){ + enter |= igInputText("##Password",ra_info.password,sizeof(ra_info.password),ImGuiInputTextFlags_Password|ImGuiInputTextFlags_EnterReturnsTrue,NULL,NULL); + if(se_button(ICON_FK_SIGN_IN " Login", (ImVec2){0,0}) || enter){ ra_info.pending_login = true; rc_client_begin_login_with_password(ra_info.client, ra_info.username, ra_info.password, se_ra_login_callback, NULL); } @@ -6389,22 +6356,27 @@ void se_draw_menu_panel(){ }else { const rc_client_game_t* game = rc_client_get_game_info(ra_info.client); ImVec2 pos; - sg_image image; + sg_image image = {0}; const char* play_string = "No Game Loaded"; char line1[256]; char line2[256]; snprintf(line1,256,se_localize_and_cache("Logged in as %s"),user->display_name); if(game){ - image.id=ra_info.image.id; + image.id=ra_info.image.atlas_id; snprintf(line2,256,se_localize_and_cache("Playing: %s"),game->title); }else snprintf(line2,256,"%s",se_localize_and_cache("No Game Loaded")); - se_boxed_image_dual_label(line1,line2, ICON_FK_TROPHY, image, 0, (ImVec2){0,0}, (ImVec2){1,1}); + se_boxed_image_dual_label(line1,line2, ICON_FK_TROPHY, image, 0, (ImVec2){ra_info.image.x1,ra_info.image.y1}, (ImVec2){ra_info.image.x2,ra_info.image.y2}); if(se_button(ICON_FK_SIGN_OUT " Logout", (ImVec2){0,0})){ char login_info_path[SB_FILE_PATH_SIZE]; snprintf(login_info_path,SB_FILE_PATH_SIZE,"%sra_token.txt",se_get_pref_path()); remove(login_info_path); rc_client_logout(ra_info.client); } +#ifndef NDEBUG + if (se_button("Dump atlases", (ImVec2){0,0})){ + ra_dump_atlases(); + } +#endif mutex_lock(ra_get_mutex()); rc_client_achievement_list_t* list = ra_info.achievement_list; if (list){ @@ -6414,10 +6386,10 @@ void se_draw_menu_panel(){ sg_image image; ImVec2 uv0, uv1; if(ra_info.achievement_images && ra_info.achievement_images[i]){ - image = ra_info.atlas; - se_ra_atlas_offset_t* tile = &ra_info.achievement_images[i][j]; - uv0 = (ImVec2){ tile->x / atlas_pixel_stride, tile->y / atlas_pixel_stride }; - uv1 = (ImVec2){ (tile->x + atlas_tile_size) / atlas_pixel_stride, (tile->y + atlas_tile_size) / atlas_pixel_stride }; + atlas_tile_t* tile = &ra_info.achievement_images[i][j]; + uv0 = (ImVec2){ tile->x1, tile->y1 }; + uv1 = (ImVec2){ tile->x2, tile->y2 }; + image.id = tile->atlas_id; } se_boxed_image_dual_label(list->buckets[i].achievements[j]->title, list->buckets[i].achievements[j]->description, ICON_FK_SPINNER, image, 0, uv0, uv1); @@ -7401,6 +7373,7 @@ static void frame(void) { screen_x = left_padding; screen_width-=(left_padding+right_padding)*se_dpi_scale(); #ifdef ENABLE_RETRO_ACHIEVEMENTS + ra_update_atlases(); ra_run_pending_callbacks(); #endif if(gui_state.sidebar_open){ @@ -8240,6 +8213,7 @@ static void cleanup(void) { SDL_Quit(); #endif https_shutdown(); + ra_cleanup(); } #ifdef EMSCRIPTEN static void emsc_load_callback(const sapp_html5_fetch_response* response) { diff --git a/src/retro_achievements.cpp b/src/retro_achievements.cpp index fedd1312d..6a558956f 100644 --- a/src/retro_achievements.cpp +++ b/src/retro_achievements.cpp @@ -16,8 +16,6 @@ extern "C" { #define STBI_ONLY_PNG #include "stb_image.h" -#include - // Access to some parts such as the atlas and the pending callbacks can happen from multiple threads // so we need to synchronize access to them std::mutex* synchronization_mutex = new std::mutex(); @@ -37,7 +35,7 @@ struct atlas_t { sg_image image = {}; std::vector data; // we construct the atlas here before uploading it to the GPU - int pixel_stride; + 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; bool resized = false; @@ -94,7 +92,7 @@ std::thread thread([type, url, post_data, callback, callback_data](){ // TODO: r // Heavy work (http request) is done, do rest of the work in the ui thread to avoid // potential synchronization issues - std::lock_guard lock(*synchronization_mutex); + std::unique_lock lock(*synchronization_mutex); pending_callbacks.push_back([result, callback, callback_data]() { // TODO: std::move? rc_api_server_response_t response; response.body = (const char*)result.data(); @@ -116,8 +114,7 @@ extern "C" void ra_log_callback(const char* message, const rc_client_t* client) // We got some data (either by downloading it, or from the cache), let's handle it void handle_downloaded_image(downloaded_image_t* image, atlas_tile_t* out_image) { - std::lock_guard lock(*synchronization_mutex); // no access to the atlases or the image cache without locking - + printf("[rcheevos]: handling downloaded image\n"); atlas_t* atlas = nullptr; // Check if we already have an atlas for this exact tile size @@ -142,7 +139,7 @@ void handle_downloaded_image(downloaded_image_t* image, atlas_tile_t* out_image) atlas->resized = true; // Find a sufficient power of two - uint32_t power = 256; + uint32_t power = 2048; // TODO: reduce to 256x256 uint32_t max = std::max(minimum_width, minimum_height); while (power < max) { power *= 2; @@ -185,11 +182,13 @@ void handle_downloaded_image(downloaded_image_t* image, atlas_tile_t* out_image) } atlas->data.swap(new_data); - // TODO: maybe we can use TLS to immediately create the atlas if this is the UI thread } // At this point we should have an atlas that has enough room for our incoming tile + int offset_x = atlas->offset_x; + int offset_y = atlas->offset_y; + // Prepare offsets for next tile atlas->offset_x += atlas->tile_width + atlas_spacing; if (atlas->offset_x + atlas->tile_width > atlas->pixel_stride) { @@ -197,10 +196,12 @@ void handle_downloaded_image(downloaded_image_t* image, atlas_tile_t* out_image) atlas->offset_y += atlas->tile_width + atlas_spacing; } + printf("atlas size: %dx%d\n", atlas->pixel_stride, atlas->pixel_stride); + // Copy tile to atlas for (int y = 0; y < atlas->tile_height; y++) { for (int x = 0; x < atlas->tile_width; x++) { - uint32_t atlas_offset = ((atlas->offset_x + x) * 4) + (((atlas->offset_y + y) * atlas->pixel_stride) * 4); + uint32_t atlas_offset = ((offset_x + x) * 4) + (((offset_y + y) * atlas->pixel_stride) * 4); atlas->data[atlas_offset + 0] = image->data[x * 4 + (y * 4 * atlas->tile_width) + 0]; atlas->data[atlas_offset + 1] = image->data[x * 4 + (y * 4 * atlas->tile_width) + 1]; atlas->data[atlas_offset + 2] = image->data[x * 4 + (y * 4 * atlas->tile_width) + 2]; @@ -208,10 +209,13 @@ void handle_downloaded_image(downloaded_image_t* image, atlas_tile_t* out_image) } } - out_image->offset_x = atlas->offset_x; - out_image->offset_y = atlas->offset_y; - out_image->width = image->width; - out_image->height = image->height; + out_image->x1 = (float)offset_x/ atlas->pixel_stride; + out_image->y1 = (float)offset_y / atlas->pixel_stride; + out_image->x2 = (float)(offset_x + image->width) / atlas->pixel_stride; + out_image->y2 = (float)(offset_y + image->height) / atlas->pixel_stride; + + printf("%d %d %d %d\n", atlas->offset_x, atlas->offset_y, image->width, image->height); + printf("atlas tile: %f %f %f %f\n", out_image->x1, out_image->y1, out_image->x2, out_image->y2); // Note: at this point atlas->dirty might be true and we can't be certain we are on the UI thread // (this might be called from the UI thread if the image is cached, @@ -227,9 +231,8 @@ void handle_downloaded_image(downloaded_image_t* image, atlas_tile_t* out_image) // or from the retro achievements event handler void ra_get_image(const char* url, atlas_tile_t* out_image) { - // Images might be getting downloaded and added to the cache asynchronously - // so let's lock it here std::unique_lock lock(*synchronization_mutex); + if (image_cache.find(url) != image_cache.end()) { // Great, image was already downloaded in the past and is in the cache @@ -240,10 +243,9 @@ void ra_get_image(const char* url, atlas_tile_t* out_image) // When this function returns, the const char* will be invalid, so we need to copy the contents std::string url_str = url; - #ifndef EMSCRIPTEN - std::thread thread([url_str, out_image](){ // TODO: again is this really needed? - #endif https_request(http_request_e::GET, url_str, {}, {}, [out_image, url_str](const std::vector& result) { + printf("downloaded: %s\n", url_str.c_str()); + std::unique_lock lock(*synchronization_mutex); rc_api_server_response_t response; response.body = (const char*)result.data(); response.body_length = result.size(); @@ -261,29 +263,39 @@ void ra_get_image(const char* url, atlas_tile_t* out_image) handle_downloaded_image(image, out_image); }); }); - #ifndef EMSCRIPTEN - }); - thread.detach(); - #endif } void ra_run_pending_callbacks() { // Pending callbacks is always added to from non-UI threads, so before we run them // we need to lock the mutex - std::lock_guard lock(*synchronization_mutex); + std::unique_lock lock(*synchronization_mutex); if(pending_callbacks.empty()) return; + std::vector> callbacks; + callbacks.swap(pending_callbacks); + lock.unlock(); - for (auto& callback : pending_callbacks) + for (auto& callback : callbacks) { callback(); } +} + +void ra_reset() { + std::unique_lock lock(*synchronization_mutex); + for (auto& atlas : atlases) { + if (atlas->image.id != SG_INVALID_ID) { + sg_destroy_image(atlas->image); + } + delete atlas; + } + atlases.clear(); pending_callbacks.clear(); } void ra_update_atlases() { - std::lock_guard lock(*synchronization_mutex); + std::unique_lock lock(*synchronization_mutex); for (atlas_t* atlas : atlases) { if (atlas->resized) { if (atlas->image.id != SG_INVALID_ID) { @@ -334,4 +346,18 @@ void ra_update_atlases() { mutex_t ra_get_mutex() { return synchronization_mutex; +} + +void ra_cleanup() { + ra_reset(); + delete synchronization_mutex; + for (auto& image : image_cache) { + stbi_image_free(image.second->data); + delete image.second; + } + image_cache.clear(); +} + +void ra_dump_atlases() { + printf("todo\n"); } \ No newline at end of file diff --git a/src/retro_achievements.h b/src/retro_achievements.h index bce896499..36840fd85 100644 --- a/src/retro_achievements.h +++ b/src/retro_achievements.h @@ -15,8 +15,8 @@ // This is because there's a lot of them and they are of similar sizes typedef struct { uint32_t atlas_id; - int offset_x, offset_y; - int width, height; + float x1, y1; + float x2, y2; } atlas_tile_t; void ra_server_callback(const rc_api_request_t* request, @@ -27,11 +27,10 @@ void ra_log_callback(const char* message, const rc_client_t* client); // and creates a pending callback so out_image is set on the UI thread void ra_get_image(const char* url, atlas_tile_t* out_image); void ra_run_pending_callbacks(); +void ra_reset(); +void ra_cleanup(); void ra_update_atlases(); mutex_t ra_get_mutex(); // TODO: can we delete this and only lock mutex stuff from .cpp file - -#ifndef NDEBUG void ra_dump_atlases(); -#endif #endif \ No newline at end of file