From 2e730419941c2981ad45d712a3a1882607566789 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Fri, 17 Dec 2021 17:28:57 +0100 Subject: [PATCH 01/19] Finished a first version of the new attached project type. This adds a new project type that allows skipping the import phase when creating a new project. It uses a new dynamic image loading and rendering system. There is still a lot of work to be done on this and a lot of testing. Definitely not production ready. --- src/Core/Configuration.cc | 5 +- src/Core/Configuration.h | 36 +- src/Core/Image/GlobalTileCache.h | 254 +++++++++ src/Core/Image/Image.h | 31 +- src/Core/Image/Manipulation/ScalingManager.h | 67 ++- src/Core/Image/TileCache.h | 565 +++++-------------- src/Core/Image/TileCacheAttached.h | 259 +++++++++ src/Core/Image/TileCacheBase.h | 74 +++ src/Core/Image/TileCacheFactory.h | 94 +++ src/Core/Image/TileCacheStore.h | 150 +++++ src/Core/Image/TileImage.h | 70 +-- src/Core/LogicModel/Layer.cc | 23 +- src/Core/LogicModel/Layer.h | 14 +- src/Core/LogicModel/LogicModel.cc | 7 +- src/Core/LogicModel/LogicModel.h | 4 +- src/Core/LogicModel/LogicModelHelper.cc | 34 +- src/Core/LogicModel/LogicModelHelper.h | 13 - src/Core/LogicModel/LogicModelImporter.cc | 4 +- src/Core/LogicModel/LogicModelImporter.h | 2 +- src/Core/Project/Project.cc | 23 +- src/Core/Project/Project.h | 38 +- src/Core/Project/ProjectExporter.cc | 15 +- src/Core/Project/ProjectImporter.cc | 32 +- src/GUI/Dialog/NewProjectDialog.cc | 9 +- src/GUI/Dialog/NewProjectDialog.h | 3 + src/GUI/MainWindow.cc | 15 +- src/GUI/Widget/LayersEditWidget.cc | 66 ++- src/GUI/Workspace/WorkspaceBackground.cc | 16 +- src/Globals.h | 9 + tests/src/ImageTests.cc | 4 +- tests/src/LogicModelDOTExporterTests.cc | 2 +- tests/src/LogicModelImporterTests.cc | 4 +- tests/src/LogicModelTests.cc | 6 +- tests/src/ScalingManagerTests.cc | 2 +- 34 files changed, 1323 insertions(+), 627 deletions(-) create mode 100644 src/Core/Image/GlobalTileCache.h create mode 100644 src/Core/Image/TileCacheAttached.h create mode 100644 src/Core/Image/TileCacheBase.h create mode 100644 src/Core/Image/TileCacheFactory.h create mode 100644 src/Core/Image/TileCacheStore.h diff --git a/src/Core/Configuration.cc b/src/Core/Configuration.cc index 07547c87..d66b7285 100644 --- a/src/Core/Configuration.cc +++ b/src/Core/Configuration.cc @@ -22,11 +22,8 @@ #define _CRT_SECURE_NO_WARNINGS 1 #include "Core/Configuration.h" -#include "Core/Utils/FileSystem.h" #include "GUI/Preferences/PreferencesHandler.h" -#include - using namespace degate; Configuration::Configuration() = default; @@ -34,4 +31,4 @@ Configuration::Configuration() = default; uint_fast64_t Configuration::get_max_tile_cache_size() { return static_cast(PREFERENCES_HANDLER.get_preferences().cache_size); -} +} \ No newline at end of file diff --git a/src/Core/Configuration.h b/src/Core/Configuration.h index 3bb2edc1..b147bcdd 100644 --- a/src/Core/Configuration.h +++ b/src/Core/Configuration.h @@ -25,26 +25,28 @@ #include "Globals.h" #include "Core/Primitive/SingletonBase.h" -namespace degate { - - class Configuration : public SingletonBase { - - friend class SingletonBase; - - private: - - Configuration(); - - public: - +namespace degate +{ /** - * Get the cache size for image tiles in MB. - * @return Returns the maximum cache size (in Mb) from the preferences. + * @class Configuration + * @brief Singleton class to store important parameters needed everywhere. */ - static uint_fast64_t get_max_tile_cache_size(); + class Configuration : public SingletonBase + { + + friend class SingletonBase; + + private: + Configuration(); - }; + public: + /** + * Get the cache size for image tiles in MB. + * @return Returns the maximum cache size (in Mb) from the preferences. + */ + static uint_fast64_t get_max_tile_cache_size(); + }; -} +} // namespace degate #endif diff --git a/src/Core/Image/GlobalTileCache.h b/src/Core/Image/GlobalTileCache.h new file mode 100644 index 00000000..f6ce8d63 --- /dev/null +++ b/src/Core/Image/GlobalTileCache.h @@ -0,0 +1,254 @@ +/** + * This file is part of the IC reverse engineering tool Degate. + * + * Copyright 2008, 2009, 2010 by Martin Schobert + * Copyright 2021 Dorian Bachelot + * + * Degate is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Degate is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with degate. If not, see . + * + */ + +#ifndef __GLOBALTILECACHE_H__ +#define __GLOBALTILECACHE_H__ + +#include "Core/Image/TileCacheBase.h" +#include "Core/Primitive/SingletonBase.h" + +#include +#include +#include +#include + +namespace degate +{ + /** + * @class GlobalTileCache + * @brief Keep in memory all the used memory at any instant (by tile caches). + * + * This can ask for every single tile cache to release memory if needed. + * + * @warning This is a singleton, only one instance can exists. + */ + template + class GlobalTileCache : public SingletonBase> + { + friend class SingletonBase; + + private: + uint_fast64_t max_cache_memory; + uint_fast64_t allocated_memory; + + typedef std::pair cache_entry_t; + typedef std::map cache_t; + + cache_t cache; + + private: + + /** + * Create a new global tile cache object (singleton). + */ + GlobalTileCache() : allocated_memory(0) + { + if (Configuration::get_max_tile_cache_size() < MINIMUM_CACHE_SIZE) + max_cache_memory = MINIMUM_CACHE_SIZE * uint_fast64_t(1024) * uint_fast64_t(1024); + else + max_cache_memory = Configuration::get_max_tile_cache_size() * uint_fast64_t(1024) * uint_fast64_t(1024); + } + + /** + * Search for the oldest cache that requested memory and make it release memory. + */ + void remove_oldest() + { + struct timespec now; + GET_CLOCK(now); + + TileCacheBase* oldest = nullptr; + + // Search for oldest cache + for (auto iter = cache.begin(); iter != cache.end(); ++iter) + { + cache_entry_t& entry = iter->second; + if (entry.first < now) + { + now.tv_sec = entry.first.tv_sec; + now.tv_nsec = entry.first.tv_nsec; + oldest = iter->first; + } + } + + if (oldest != nullptr) + { +#ifdef TILECACHE_DEBUG + debug(TM, "Will call cleanup on %p", oldest); +#endif + + // If found, make the oldest release memory + oldest->cleanup_cache(); + } + else + { +#ifdef TILECACHE_DEBUG + debug(TM, "there is nothing to free."); + print_table(); +#endif + } + } + + public: + + /** + * Print a status table about the global tile cache. + */ + void print_table() const + { + std::cout << "Global Image Tile Cache:\n" + << "Used memory : " << allocated_memory << " bytes\n" + << "Max memory : " << max_cache_memory << " bytes\n\n" + << "Holder | Last access (sec,nsec) | Amount of memory\n" + << "-----------------+---------------------------+------------------------------------\n"; + + for (auto iter = cache.begin(); iter != cache.end(); ++iter) + { + cache_entry_t const& entry = iter->second; + std::cout << std::setw(16) << std::hex << static_cast(iter->first); + std::cout << " | "; + std::cout << std::setw(12) << entry.first.tv_sec; + std::cout << "."; + std::cout << std::setw(12) << entry.first.tv_nsec; + std::cout << " | "; + std::cout << entry.second / static_cast(1024 * 1024) << " M (" << entry.second << " bytes)\n"; + iter->first->print(); + } + std::cout << "\n"; + } + + /** + * Request memory from the cache (this is virtual). + * + * If too less memory remaining, then will call remove_oldest(). + */ + bool request_cache_memory(TileCacheBase* requestor, uint_fast64_t amount) + { +#ifdef TILECACHE_DEBUG + debug(TM, "Local cache %p requests %d bytes.", requestor, amount); +#endif + while (allocated_memory + amount > max_cache_memory) + { +#ifdef TILECACHE_DEBUG + debug(TM, "Try to free memory"); +#endif + remove_oldest(); + } + + if (allocated_memory + amount <= max_cache_memory) + { + struct timespec now{}; + GET_CLOCK(now); + + auto found = cache.find(requestor); + if (found == cache.end()) + { + cache[requestor] = std::make_pair(now, amount); + } + else + { + cache_entry_t& entry = found->second; + entry.first.tv_sec = now.tv_sec; + entry.first.tv_nsec = now.tv_nsec; + entry.second += amount; + } + + allocated_memory += amount; +#ifdef TILECACHE_DEBUG + print_table(); +#endif + return true; + } + + debug(TM, "Can't free memory."); + + print_table(); + return false; + } + + /** + * Release memory from the cache (virtual). + */ + void release_cache_memory(TileCacheBase* requestor, uint_fast64_t amount) + { +#ifdef TILECACHE_DEBUG + debug(TM, "Local cache %p releases %d bytes.", requestor, amount); +#endif + + auto found = cache.find(requestor); + + if (found == cache.end()) + { + debug(TM, "Unknown memory should be released."); + print_table(); + assert(1 == 0); + } + else + { + cache_entry_t& entry = found->second; + + if (entry.second >= amount) + { + entry.second -= amount; + assert(allocated_memory >= amount); + if (allocated_memory >= amount) + allocated_memory -= amount; + else + { + debug(TM, "More mem to release than available."); + print_table(); + assert(1 == 0); + } + } + else + { + print_table(); + assert(entry.second >= amount); // will break + } + + if (entry.second == 0) + { +#ifdef TILECACHE_DEBUG + debug(TM, "Memory completely released. Remove entry from global cache."); +#endif + cache.erase(found); + } + } + } + + inline uint_fast64_t get_max_cache_memory() const + { + return max_cache_memory; + } + + inline uint_fast64_t get_allocated_memory() const + { + return allocated_memory; + } + + inline bool is_full(uint_fast64_t amount) const + { + return allocated_memory + amount > max_cache_memory; + } + }; +} + +#endif //__GLOBALTILECACHE_H__ diff --git a/src/Core/Image/Image.h b/src/Core/Image/Image.h index bbbeb4fa..8699b0ca 100644 --- a/src/Core/Image/Image.h +++ b/src/Core/Image/Image.h @@ -217,30 +217,57 @@ namespace degate /** * Constructor for temporary virtual images. + * + * @param width : the width of the image. + * @param height : the height of the image. + * @param scale : the scale to apply when loading the image (e.g. scale = 2 + * will load the image with final size of width/2 and height/2). + * @see ScalingManager. + * @param tile_width_exp : the width (and height) for image tiles. This + * value is specified as an exponent to the base 2. This means for + * example that if you want to use a width of 1024 pixel, you have + * to give a value of 10, because 2^10 is 1024. */ Image(unsigned int width, unsigned int height, + unsigned int scale = 1, unsigned int tile_width_exp = 10) : ImageBase(width, height), StoragePolicy_Tile(width, height, create_temp_directory(), false, + scale, tile_width_exp) { } /** * Constructor for persistent virtual images. + * + * @param width : the width of the image. + * @param height : the height of the image. + * @param path : the path of the image, can be a directory (store mode) or + * a file path (attached mode). @see TileCache. + * @param persistent : if true, will persist on the disk. + * @param scale : the scale to apply when loading the image (e.g. scale = 2 + * will load the image with final size of width/2 and height/2). + * @see ScalingManager. + * @param tile_width_exp : the width (and height) for image tiles. This + * value is specified as an exponent to the base 2. This means for + * example that if you want to use a width of 1024 pixel, you have + * to give a value of 10, because 2^10 is 1024. */ Image(unsigned int width, unsigned int height, - std::string const& directory, + std::string const& path, bool persistent = true, + unsigned int scale = 1, unsigned int tile_width_exp = 10) : ImageBase(width, height), StoragePolicy_Tile(width, height, - directory, + path, persistent, + scale, tile_width_exp) { } diff --git a/src/Core/Image/Manipulation/ScalingManager.h b/src/Core/Image/Manipulation/ScalingManager.h index 97e05f67..524aee83 100644 --- a/src/Core/Image/Manipulation/ScalingManager.h +++ b/src/Core/Image/Manipulation/ScalingManager.h @@ -54,6 +54,8 @@ namespace degate unsigned int min_size; + ProjectType project_type; + private: unsigned long get_nearest_power_of_two(unsigned int value) @@ -78,15 +80,16 @@ namespace degate /** * Create a new ScalingManager object for an image. * @param img The background image. - * @param base_directory A directory where all files can be stored. You - * can use the directory of the master image for that. Make - * sure that the directory exist. if you compile libdegate - * with DEBUG=1 the existence is assert()ed. + * @param base_directory A path where all files can be stored. You + * can use the path of the master image for that. Make + * sure that the path exist. Not used if attached mode. + * @param project_type The type of the project (normal or attached). * @param min_size Create down scalings until the edge length * is becomes less than \p min_size. */ ScalingManager(std::shared_ptr img, std::string const& base_directory, + ProjectType project_type, int min_size = 1024) { assert(img != nullptr); @@ -94,6 +97,8 @@ namespace degate images[1] = img; this->min_size = min_size; + this->project_type = project_type; + assert(file_exists(base_directory)); } @@ -125,53 +130,72 @@ namespace degate /** * Create the scaled images. + * * Created prescaled images that have the same peristence state as the - * master image. The files are written into the directory, where the + * master image. The files are written into the path, where the * master image is stored. + * + * This have no other effect than creating virtually the base image in multi + * scale for the attached mode (real loading is dynamic). + * * @throw InvalidPathException This exception is thrown, if the - * \p directory (ctor param) doesn't exists. + * \p path (ctor param) doesn't exists. */ void create_scalings() { - if (!(file_exists(base_directory) && is_directory(base_directory))) - throw InvalidPathException("The directory for prescaled images must exist. but it is not there."); + // If normal mode, then check if directory exists + if (!(file_exists(base_directory) && is_directory(base_directory)) && project_type == ProjectType::Normal) + throw InvalidPathException("The path for prescaled images must exist. But it is not there."); std::shared_ptr last_img = images[1]; unsigned int w = last_img->get_width(); unsigned int h = last_img->get_height(); - for (int i = 2; ((h > min_size) || (w > min_size)) && - (i < (1 << 24)); // max 24 scaling levels + // Create scalings + for (int i = 2; ((h > min_size) || (w > min_size)) && (i < (1 << 24)); // max 24 scaling levels i *= 2) { w >>= 1; h >>= 1; - // create a new image - char dir_name[PATH_MAX]; - snprintf(dir_name, sizeof(dir_name), "scaling_%d.dimg", i); - std::string dir_path = join_pathes(images[1]->get_directory(), std::string(dir_name)); + std::string path; - debug(TM, "create scaled image in %s for scaling factor %d?", dir_path.c_str(), i); - if (!file_exists(dir_path)) + // Get the path to use + if (project_type == ProjectType::Normal) + { + // Create a new image directory name. + char dir_name[PATH_MAX]; + snprintf(dir_name, sizeof(dir_name), "scaling_%d.dimg", i); + + path = join_pathes(images[1]->get_path(), std::string(dir_name)); + } + else + path = images[1]->get_path(); + + // Create the scaling if needed + debug(TM, "create scaled image in %s for scaling factor %d?", path.c_str(), i); + if (!file_exists(path) && project_type == ProjectType::Normal) { debug(TM, "yes"); - create_directory(dir_path); + create_directory(path); - std::shared_ptr new_img(new ImageType(w, h, dir_path, - images[1]->is_persistent())); + // Load the base image + std::shared_ptr new_img(new ImageType(w, h, path, images[1]->is_persistent(), i)); + // Scale down scale_down_by_2(new_img, last_img); last_img = new_img; } else { debug(TM, "no"); - std::shared_ptr new_img(new ImageType(w, h, dir_path, - images[1]->is_persistent())); + + // Load the image + std::shared_ptr new_img(new ImageType(w, h, path, images[1]->is_persistent(), i)); last_img = new_img; } + images[i] = last_img; } } @@ -190,7 +214,6 @@ namespace degate (unsigned long)lrint(images.rbegin()->first)); typename image_map::iterator found = images.find(factor); assert(found != images.end()); - //debug(TM, "requested scaling is %f. nearest scaling is %d. found image with scaling %f", request_scaling, factor, found->first); return *found; } diff --git a/src/Core/Image/TileCache.h b/src/Core/Image/TileCache.h index a1f3be37..dc658c20 100644 --- a/src/Core/Image/TileCache.h +++ b/src/Core/Image/TileCache.h @@ -2,7 +2,7 @@ * This file is part of the IC reverse engineering tool Degate. * * Copyright 2008, 2009, 2010 by Martin Schobert - * Copyright 2019-2020 Dorian Bachelot + * Copyright 2021 Dorian Bachelot * * Degate is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,363 +22,156 @@ #ifndef __TILECACHE_H__ #define __TILECACHE_H__ -#include "Core/Utils/MemoryMap.h" -#include "Core/Utils/FileSystem.h" -#include "Core/Configuration.h" - -#include -#include -#include -#include -#include // for make_pair -#include -#include - -#include - -#include - -/** - * Minimum size (in Mb) of the cache. - */ -#define MINIMUM_CACHE_SIZE uint_fast64_t(256) - -static void get_clock(struct timespec* ts) -{ - assert(ts != nullptr); - - std::chrono::steady_clock::time_point tp = std::chrono::steady_clock::now(); - - auto secs = std::chrono::time_point_cast(tp); - auto ns = std::chrono::time_point_cast(tp) - std::chrono::time_point_cast(secs); - - ts->tv_sec = secs.time_since_epoch().count(); - ts->tv_nsec = static_cast(ns.count()); -} - -#define GET_CLOCK(dst_variable) \ - get_clock(&dst_variable); - - -// #define TILECACHE_DEBUG - -/** - * Overloaded comparison operator for timespec-structs. - * @return Returns true, if \p a is completely before \p b. Else - * false is returned. - */ -static bool operator<(struct timespec const& a, struct timespec const& b) -{ - if (a.tv_sec < b.tv_sec) return true; - else if (a.tv_sec == b.tv_sec && a.tv_nsec < b.tv_nsec) return true; - else return false; -} +#include "Core/Image/TileImage.h" +#include "Core/Image/TileCacheBase.h" +#include "Core/Image/GlobalTileCache.h" namespace degate { - class TileCacheBase - { - public: - virtual void cleanup_cache() = 0; - virtual void print() const = 0; - }; - - class GlobalTileCache : public SingletonBase + /** + * @class TileCache + * @brief Tile cache base class (interface). + */ + template + class TileCache : public TileCacheBase { - friend class SingletonBase; - - private: + friend class GlobalTileCache; - uint_fast64_t max_cache_memory; - uint_fast64_t allocated_memory; - - typedef std::pair cache_entry_t; - typedef std::map cache_t; - - cache_t cache; - - private: + public: - GlobalTileCache() : allocated_memory(0) + /** + * Create a new tile cache. + * + * @param path : the path to the image (can be to a file or a path for example). + * @param tile_width_exp : the width (and height) for image tiles. This + * value is specified as an exponent to the base 2. This means for + * example that if you want to use a width of 1024 pixel, you have + * to give a value of 10, because 2^10 is 1024. + * @param scale : the scale to apply when loading the image (e.g. scale = 2 + * will load the image with final size of width/2 and height/2). + * @see ScalingManager. + */ + inline TileCache(std::string path, unsigned int tile_width_exp, unsigned int scale) + : path(std::move(path)), + tile_width_exp(tile_width_exp), + scale(scale) { - if (Configuration::get_max_tile_cache_size() < MINIMUM_CACHE_SIZE) - max_cache_memory = MINIMUM_CACHE_SIZE * uint_fast64_t(1024) * uint_fast64_t(1024); - else - max_cache_memory = Configuration::get_max_tile_cache_size() * uint_fast64_t(1024) * uint_fast64_t(1024); } - void remove_oldest() + /** + * Release memory on destroy. + */ + inline ~TileCache() { - struct timespec now; - GET_CLOCK(now); - - TileCacheBase* oldest = nullptr; - - for (cache_t::iterator iter = cache.begin(); iter != cache.end(); ++iter) - { - cache_entry_t& entry = iter->second; - if (entry.first < now) - { - now.tv_sec = entry.first.tv_sec; - now.tv_nsec = entry.first.tv_nsec; - oldest = iter->first; - } - } - - if (oldest) - { -#ifdef TILECACHE_DEBUG - debug(TM, "Will call cleanup on %p", oldest); -#endif - oldest->cleanup_cache(); - } - else - { -#ifdef TILECACHE_DEBUG - debug(TM, "there is nothing to free."); - print_table(); -#endif - } + release_memory(); } - public: - - void print_table() const + /** + * Cleanup the cache by removing the oldest entry. + */ + inline void cleanup_cache() override { - std::cout << "Global Image Tile Cache:\n" - << "Used memory : " << allocated_memory << " bytes\n" - << "Max memory : " << max_cache_memory << " bytes\n\n" - << "Holder | Last access (sec,nsec) | Amount of memory\n" - << "-----------------+---------------------------+------------------------------------\n"; + if (TileCache::cache.size() == 0) return; - for (cache_t::const_iterator iter = cache.begin(); iter != cache.end(); ++iter) - { - cache_entry_t const& entry = iter->second; - std::cout << std::setw(16) << std::hex << static_cast(iter->first); - std::cout << " | "; - std::cout << std::setw(12) << entry.first.tv_sec; - std::cout << "."; - std::cout << std::setw(12) << entry.first.tv_nsec; - std::cout << " | "; - std::cout << entry.second / (1024 * 1024) << " M (" << entry.second << " bytes)\n"; - iter->first->print(); - } - std::cout << "\n"; - } + // Initialize a clock to store the oldest + struct timespec oldest_clock_val; + GET_CLOCK(oldest_clock_val); - bool request_cache_memory(TileCacheBase* requestor, uint_fast64_t amount) - { -#ifdef TILECACHE_DEBUG - debug(TM, "Local cache %p requests %d bytes.", requestor, amount); -#endif - while (allocated_memory + amount > max_cache_memory) - { -#ifdef TILECACHE_DEBUG - debug(TM, "Try to free memory"); -#endif - remove_oldest(); - } + auto oldest = TileCache::cache.begin(); - if (allocated_memory + amount <= max_cache_memory) + // Search for oldest entry + for (auto iter = TileCache::cache.begin(); + iter != TileCache::cache.end(); ++iter) { - struct timespec now; - GET_CLOCK(now); - - cache_t::iterator found = cache.find(requestor); - if (found == cache.end()) - { - cache[requestor] = std::make_pair(now, amount); - } - else + struct timespec clock_val = (*iter).second.second; + if (clock_val < oldest_clock_val) { - cache_entry_t& entry = found->second; - entry.first.tv_sec = now.tv_sec; - entry.first.tv_nsec = now.tv_nsec; - entry.second += amount; + oldest_clock_val.tv_sec = clock_val.tv_sec; + oldest_clock_val.tv_nsec = clock_val.tv_nsec; + oldest = iter; } - - allocated_memory += amount; -#ifdef TILECACHE_DEBUG - print_table(); -#endif - return true; } - debug(TM, "Can't free memory."); + assert(oldest != TileCache::cache.end()); - print_table(); - return false; - } - - void release_cache_memory(TileCacheBase* requestor, uint_fast64_t amount) - { -#ifdef TILECACHE_DEBUG - debug(TM, "Local cache %p releases %d bytes.", requestor, amount); -#endif + // Release memory + (*oldest).second.first.reset(); // explicit reset of smart pointer - cache_t::iterator found = cache.find(requestor); + // Clean the cache entry + TileCache::cache.erase(oldest); - if (found == cache.end()) - { - debug(TM, "Unknown memory should be released."); - print_table(); - assert(1==0); - } - else - { - cache_entry_t& entry = found->second; - - if (entry.second >= amount) - { - entry.second -= amount; - assert(allocated_memory >= amount); - if (allocated_memory >= amount) allocated_memory -= amount; - else - { - debug(TM, "More mem to release than available."); - print_table(); - assert(1==0); - } - } - else - { - print_table(); - assert(entry.second >= amount); // will break - } - - if (entry.second == 0) - { #ifdef TILECACHE_DEBUG - debug(TM, "Memory completely released. Remove entry from global cache."); + debug(TM, "local cache: %d entries after remove\n", TileCache::cache.size()); #endif - cache.erase(found); - } - } - } - inline uint_fast64_t get_max_cache_memory() const - { - return max_cache_memory; - } - - inline uint_fast64_t get_allocated_memory() const - { - return allocated_memory; + // Update the global tile cache (release the virtual memory) + GlobalTileCache& gtc = GlobalTileCache::get_instance(); + gtc.release_cache_memory(this, TileCache::get_image_size()); } - inline bool is_full(uint_fast64_t amount) const - { - return allocated_memory + amount > max_cache_memory; - } - }; - - - /** - * The TileCache class handles caching of image tiles. - * - * The implementation keeps track how old the cached tile is. - * If new tiles become loaded, old tiles are removed from the - * cache. You can control the numer of cached tiles via the - * constructor parameter \p _min_cache_tiles. The memory - * requirement is around - * \p _min_cache_tiles*sizeof(PixelPolicy::pixel_type)*(2^_tile_width_exp)^2 , - * where \p sizeof(PixelPolicy::pixel_type) is the size of a pixel. - */ - template - class TileCache : public TileCacheBase - { - friend class GlobalTileCache; - - private: - - typedef std::shared_ptr> MemoryMap_shptr; - typedef std::map> cache_type; - - const std::string directory; - const unsigned int tile_width_exp; - const bool persistent; - - cache_type cache; - - // Used for caching the working tile. - mutable MemoryMap_shptr current_tile; - mutable unsigned curr_tile_num_x; - mutable unsigned curr_tile_num_y; - - std::mutex mutex; - - - public: - /** - * Create a TileCache object. - * @param directory The directory where all the tiles are for a TileImage. - * @param tile_width_exp - * @param persistent + * Release all the memory. */ - TileCache(std::string const& directory, - unsigned int tile_width_exp, - bool persistent, - unsigned int min_cache_tiles = 4) : - directory(directory), - tile_width_exp(tile_width_exp), - persistent(persistent) + inline virtual void release_memory() { - } - - /** - * Destroy a TileCache object. - */ - ~TileCache() - { - release_memory(); - } - - void release_memory() - { - if (cache.size() > 0) + if (TileCache::cache.size() > 0) { std::lock_guard lock(mutex); - GlobalTileCache& gtc = GlobalTileCache::get_instance(); - gtc.release_cache_memory(this, cache.size() * get_image_size()); - current_tile.reset(); - cache.clear(); + // Release the global tile cache (by removing all the used virtual memory by this) + GlobalTileCache& gtc = GlobalTileCache::get_instance(); + gtc.release_cache_memory(this, TileCache::cache.size() * TileCache::get_image_size()); + + // Release the memory + TileCache::current_tile.reset(); + TileCache::cache.clear(); } } - void print() const override + /** + * Print this cache info. + */ + inline void print() const override { - for (typename cache_type::const_iterator iter = cache.begin(); - iter != cache.end(); ++iter) + for (typename TileCache::cache_type::const_iterator iter = TileCache::cache.begin(); + iter != TileCache::cache.end(); ++iter) { std::cout << "\t+ " - << directory << "/" - << (*iter).first << " " - << (*iter).second.second.tv_sec - << "/" - << (*iter).second.second.tv_nsec - << std::endl; + << TileCache::path << "/" + << (*iter).first << " " + << (*iter).second.second.tv_sec + << "/" + << (*iter).second.second.tv_nsec + << std::endl; } } - inline void cache_around(unsigned int min_x, - unsigned int max_x, - unsigned int min_y, - unsigned int max_y, - unsigned int max_size_x, - unsigned int max_size_y, - unsigned int radius) + /** + * Cache all tile around a rect (in a radius). + * + * @param min_x : lower left x coordinate of the rect. + * @param max_x : upper right x cooredinate of the rect. + * @param min_y : lower left y coordinate of the rect. + * @param max_y : upper right y coordinate of the rect. + * @param max_size_x : max possible x coordinate for the rect. + * @param max_size_y : max possible y coordinate for the rect. + * @param radius : radius around the rect to cache (in number of tile unit). + */ + inline virtual void cache_around(unsigned int min_x, + unsigned int max_x, + unsigned int min_y, + unsigned int max_y, + unsigned int max_size_x, + unsigned int max_size_y, + unsigned int radius) { - unsigned int tile_num_min_x = min_x >> tile_width_exp; - unsigned int tile_num_max_x = max_x >> tile_width_exp; - unsigned int tile_num_min_y = min_y >> tile_width_exp; - unsigned int tile_num_max_y = max_y >> tile_width_exp; + unsigned int tile_num_min_x = min_x >> TileCache::tile_width_exp; + unsigned int tile_num_max_x = max_x >> TileCache::tile_width_exp; + unsigned int tile_num_min_y = min_y >> TileCache::tile_width_exp; + unsigned int tile_num_max_y = max_y >> TileCache::tile_width_exp; - unsigned int tile_num_max_size_x = max_size_x >> tile_width_exp; - unsigned int tile_num_max_size_y = max_size_y >> tile_width_exp; + unsigned int tile_num_max_size_x = max_size_x >> TileCache::tile_width_exp; + unsigned int tile_num_max_size_y = max_size_y >> TileCache::tile_width_exp; unsigned int cache_min_x = radius > tile_num_min_x ? 0 : tile_num_min_x - radius; unsigned int cache_max_x = radius + tile_num_max_x > tile_num_max_size_x ? tile_num_max_size_x : tile_num_max_x + radius; @@ -386,9 +179,9 @@ namespace degate unsigned int cache_max_y = radius + tile_num_max_y > tile_num_max_size_y ? tile_num_max_size_y : tile_num_max_y + radius; if (static_cast(cache_max_x - cache_min_x) * - static_cast(cache_max_y - cache_min_y) * - static_cast(get_image_size()) * - static_cast(sizeof(typename PixelPolicy::pixel_type)) > GlobalTileCache::get_instance().get_max_cache_memory()) + static_cast(cache_max_y - cache_min_y) * + static_cast(TileCache::get_image_size()) * + static_cast(sizeof(typename PixelPolicy::pixel_type)) > GlobalTileCache::get_instance().get_max_cache_memory()) { debug(TM, "Cache too small to cache around"); @@ -400,58 +193,13 @@ namespace degate for (unsigned int x = cache_min_x; x <= cache_max_x; x++) { //if (y >= tile_num_min_y && y <= tile_num_max_y && x >= tile_num_min_x && x <= tile_num_max_x) - //continue; + //continue; load_tile(x, y); } } } - inline void load_tile(unsigned int x, unsigned int y, bool update_current = false) - { - std::lock_guard lock(mutex); - - // create a file name from tile number - char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "%d_%d.dat", x, y); - - // if filename/ object is not in cache, load the tile - typename cache_type::const_iterator iter = cache.find(filename); - - if (iter == cache.end()) - { - GlobalTileCache& gtc = GlobalTileCache::get_instance(); - - bool ok = gtc.request_cache_memory(this, get_image_size()); - assert(ok == true); - struct timespec now{}; - GET_CLOCK(now); - - cache[filename] = std::make_pair(load(filename), now); - - //debug(TM, "Cache size : %d/%d", gtc.get_allocated_memory(), gtc.get_max_cache_memory()); - - #ifdef TILECACHE_DEBUG - gtc.print_table(); - #endif - } - - // Get current time - struct timespec now{}; - GET_CLOCK(now); - - // Update entry - auto entry = cache[filename]; - entry.second = now; - - if (update_current) - { - current_tile = entry.first; - curr_tile_num_x = x; - curr_tile_num_y = y; - } - } - /** * Get a tile. If the tile is not in the cache, the tile is loaded. * @@ -465,54 +213,26 @@ namespace degate unsigned int tile_num_x = x >> tile_width_exp; unsigned int tile_num_y = y >> tile_width_exp; - if (!(current_tile != nullptr && - tile_num_x == curr_tile_num_x && - tile_num_y == curr_tile_num_y)) - { + // This is an optimisation, but don't fit well for attached mode + // TODO: When notifier for new loaded tile (attached mode) readd this + //if (!(current_tile != nullptr && tile_num_x == curr_tile_num_x && tile_num_y == curr_tile_num_y)) + //{ load_tile(tile_num_x, tile_num_y, true); - } + //} return current_tile; } - protected: - /** - * Remove the oldest entry from the cache. + * Load a new tile and update the cache. + * + * @param x : the x index of the tile (not the real coordinate). + * @param y : the y index of the tile (not the real coordinate). + * @param update_current : if true, will update the current_tile pointer, otherwise not. */ - void cleanup_cache() override - { - if (cache.size() == 0) return; - - struct timespec oldest_clock_val; - GET_CLOCK(oldest_clock_val); - - typename cache_type::iterator oldest = cache.begin(); - - for (typename cache_type::iterator iter = cache.begin(); - iter != cache.end(); ++iter) - { - struct timespec clock_val = (*iter).second.second; - if (clock_val < oldest_clock_val) - { - oldest_clock_val.tv_sec = clock_val.tv_sec; - oldest_clock_val.tv_nsec = clock_val.tv_nsec; - oldest = iter; - } - } - - assert(oldest != cache.end()); - (*oldest).second.first.reset(); // explicit reset of smart pointer - cache.erase(oldest); -#ifdef TILECACHE_DEBUG - debug(TM, "local cache: %d entries after remove\n", cache.size()); -#endif - GlobalTileCache& gtc = GlobalTileCache::get_instance(); - gtc.release_cache_memory(this, get_image_size()); - } - + virtual void load_tile(unsigned int x, unsigned int y, bool update_current = false) = 0; - private: + protected: /** * Get image size in bytes. @@ -522,24 +242,27 @@ namespace degate return sizeof(typename PixelPolicy::pixel_type) * (uint_fast64_t(1) << tile_width_exp) * (uint_fast64_t(1) << tile_width_exp); } - /** - * Load a tile from an image file. - * @param filename Just the name of the file to load. The filename is - * relative to the \p directory. - */ - std::shared_ptr> - load(std::string const& filename) const - { - //debug(TM, "directory: [%s] file: [%s]", directory.c_str(), filename.c_str()); - MemoryMap_shptr mem(new MemoryMap - (uint_fast64_t(1) << tile_width_exp, - uint_fast64_t(1) << tile_width_exp, - MAP_STORAGE_TYPE_PERSISTENT_FILE, - join_pathes(directory, filename))); - - return mem; - } - }; // end of class TileCache -} + protected: + const std::string path; + const unsigned int tile_width_exp; -#endif + // Cache types. + typedef std::shared_ptr> MemoryMap_shptr; + typedef std::map> + cache_type; + + cache_type cache; + + // Used for caching the working tile. + mutable MemoryMap_shptr current_tile; + mutable unsigned curr_tile_num_x = 0; + mutable unsigned curr_tile_num_y = 0; + + unsigned int scale; + + std::mutex mutex; + }; +} // namespace degate + +#endif //__TILECACHE_H__ diff --git a/src/Core/Image/TileCacheAttached.h b/src/Core/Image/TileCacheAttached.h new file mode 100644 index 00000000..725ff35c --- /dev/null +++ b/src/Core/Image/TileCacheAttached.h @@ -0,0 +1,259 @@ +/** + * This file is part of the IC reverse engineering tool Degate. + * + * Copyright 2008, 2009, 2010 by Martin Schobert + * Copyright 2019-2020 Dorian Bachelot + * + * Degate is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Degate is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with degate. If not, see . + * + */ + +#ifndef __TILECACHEATTACHED_H__ +#define __TILECACHEATTACHED_H__ + +#include "Core/Configuration.h" +#include "Core/Image/GlobalTileCache.h" +#include "Core/Utils/FileSystem.h" +#include "Core/Utils/MemoryMap.h" + +#include +#include +#include + +namespace degate +{ + /** + * @class TileCacheAttached + * @brief Tile cache for attached/linked images. + */ + template + class TileCacheAttached : public TileCache + { + friend class GlobalTileCache; + + public: + + /** + * Create a new Tile cache with attached/linked image. + * + * @param path : the path to the image. + * @param tile_width_exp : the width (and height) for image tiles. This + * value is specified as an exponent to the base 2. This means for + * example that if you want to use a width of 1024 pixel, you have + * to give a value of 10, because 2^10 is 1024. + * @param scale : the scale to apply when loading the image (e.g. scale = 2 + * will load the image with final size of width/2 and height/2). + * @see ScalingManager. + */ + inline TileCacheAttached(std::string path, unsigned int tile_width_exp, unsigned int scale = 1) + : TileCache(path, tile_width_exp, scale), + tile_size(1 << tile_width_exp), + path(path) + { + QImageReader reader(path.c_str()); + + // If the image is a multi-page/multi-res, we take the page with the biggest resolution. + if (reader.imageCount() > 1) + { + QSize best_size{0, 0}; + for (int i = 0; i < reader.imageCount(); i++) + { + if (best_size.width() < reader.size().width() || best_size.height() < reader.size().height()) + { + best_size = reader.size(); + best_image_number = reader.currentImageNumber(); + } + + reader.jumpToNextImage(); + } + + reader.jumpToImage(best_image_number); + } + + // Size + size = reader.size(); + if (!size.isValid()) + { + debug(TM, "can't read size of %s\n", path.c_str()); + return; + } + + // Scaled size conversion + auto h = static_cast(round(log(scale) / log(2))); + scaled_size = QSize{size.width() >> h, size.height() >> h}; + + // Create loading tile + loading_tile = std::make_shared>(tile_size, tile_size); + } + + /** + * Load a new tile and update the cache. + * + * @param x : the x index of the tile (not the real coordinate). + * @param y : the y index of the tile (not the real coordinate). + * @param update_current : if true, will update the current_tile pointer, otherwise not. + */ + inline void load_tile(unsigned int x, unsigned int y, bool update_current = false) + { + // Create a file name from tile number + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%d_%d.dat", x, y); + + // If filename/object is not in cache, load the tile + typename TileCache::cache_type::const_iterator iter = TileCache::cache.find(filename); + + // If the tile was not found in the cache, then load it + if (iter == TileCache::cache.end()) + { + GlobalTileCache& gtc = GlobalTileCache::get_instance(); + + // Allocate memory (global tile cache) + bool ok = gtc.request_cache_memory(this, TileCache::get_image_size()); + assert(ok == true); + struct timespec now{}; + GET_CLOCK(now); + + // If update_current, then update and set a black loading tile + if (update_current) + { + TileCache::cache[filename] = std::make_pair(loading_tile, now);; + TileCache::current_tile = loading_tile; + TileCache::curr_tile_num_x = x; + TileCache::curr_tile_num_y = y; + } + + // Run in another thread the loading phase of the new tile + QtConcurrent::run([=]() + { + auto data = std::make_pair(load(x, y), now); + TileCache::mutex.lock(); + TileCache::cache[filename] = data; + TileCache::mutex.unlock(); + }); + + #ifdef TILECACHE_DEBUG + gtc.print_table(); + #endif + + // Show the loading tile while waiting for the next update to try to load the real tile image + return; + } + + // Get current time + struct timespec now{}; + GET_CLOCK(now); + + // Update entry + auto entry = TileCache::cache[filename]; + entry.second = now; + + if (update_current) + { + TileCache::current_tile = entry.first; + TileCache::curr_tile_num_x = x; + TileCache::curr_tile_num_y = y; + } + } + + protected: + + /** + * Load a tile. + */ + inline + std::shared_ptr> + load(unsigned int x, unsigned int y) + { + debug(TM, "load(%d, %d)", x, y); + + // Prepare sizes + QSize reading_size{static_cast(tile_size), static_cast(tile_size)}; + QSize read_size{static_cast(x) * static_cast(tile_size), static_cast(y) * static_cast(tile_size)}; + + // Check width + if (reading_size.width() + read_size.width() > scaled_size.width()) + reading_size.setWidth(scaled_size.width() - read_size.width()); + + // Check height + if (reading_size.height() + read_size.height() > scaled_size.height()) + reading_size.setHeight(scaled_size.height() - read_size.height()); + + // Create reader + QImageReader current_reader(path.c_str()); + + // Create reading rect + QRect rect(read_size.width(), read_size.height(), reading_size.width(), reading_size.height()); + + // If the image is a multi-page/multi-res, we take the page with the biggest resolution + if (current_reader.imageCount() > 1) + current_reader.jumpToImage(best_image_number); + + // Scaled read + current_reader.setScaledSize(scaled_size); + current_reader.setScaledClipRect(rect); + + // Set reader reading rect + QImage img = current_reader.read(); + if (img.isNull()) + { + debug(TM, "can't read image file when loading a new tile\n"); + } + + // Convert to good format + if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_RGB32) + { + img = img.convertToFormat(QImage::Format_ARGB32); + } + + // Get data + const auto *rgb_data = reinterpret_cast(&img.constBits()[0]); + + // Create memory map + auto mem = std::make_shared>(tile_size, tile_size); + + // Prevent overflows + unsigned int max_x = tile_size > static_cast(reading_size.width()) ? + static_cast(reading_size.width()) : tile_size; + unsigned int max_y = tile_size > static_cast(reading_size.height()) ? + static_cast(reading_size.height()) : tile_size; + + // Fill data + QRgb rgb; + for (unsigned int y = 0; y < max_y; y++) + { + for (unsigned int x = 0; x < max_x; x++) + { + rgb = rgb_data[y * reading_size.width() + x]; + mem->set(x, y, MERGE_CHANNELS(qRed(rgb), qGreen(rgb), qBlue(rgb), qAlpha(rgb))); + } + } + + debug(TM, "finished load(%d, %d)", x, y); + + return mem; + } + + private: + + QSize size; + QSize scaled_size; + unsigned int tile_size; + std::string path; + int best_image_number = -1; + + std::shared_ptr> loading_tile; + }; +} // namespace degate + +#endif diff --git a/src/Core/Image/TileCacheBase.h b/src/Core/Image/TileCacheBase.h new file mode 100644 index 00000000..ea1d9bb5 --- /dev/null +++ b/src/Core/Image/TileCacheBase.h @@ -0,0 +1,74 @@ +/** + * This file is part of the IC reverse engineering tool Degate. + * + * Copyright 2008, 2009, 2010 by Martin Schobert + * Copyright 2021 Dorian Bachelot + * + * Degate is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Degate is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with degate. If not, see . + * + */ + +#ifndef __TILECACHEBASE_H__ +#define __TILECACHEBASE_H__ + +#include +#include + +/** + * Minimum size (in Mb) of the cache. + */ +#define MINIMUM_CACHE_SIZE uint_fast64_t(256) + +static void get_clock(struct timespec* ts) +{ + assert(ts != nullptr); + + std::chrono::steady_clock::time_point tp = std::chrono::steady_clock::now(); + + auto secs = std::chrono::time_point_cast(tp); + auto ns = std::chrono::time_point_cast(tp) - + std::chrono::time_point_cast(secs); + + ts->tv_sec = secs.time_since_epoch().count(); + ts->tv_nsec = static_cast(ns.count()); +} + +#define GET_CLOCK(dst_variable) get_clock(&dst_variable); + +/** + * Overloaded comparison operator for timespec-structs. + * @return Returns true, if \p a is completely before \p b. Else + * false is returned. + */ +static bool operator<(struct timespec const& a, struct timespec const& b) +{ + if (a.tv_sec < b.tv_sec) + return true; + else if (a.tv_sec == b.tv_sec && a.tv_nsec < b.tv_nsec) + return true; + else + return false; +} + +namespace degate +{ + class TileCacheBase + { + public: + virtual void cleanup_cache() = 0; + virtual void print() const = 0; + }; +} + +#endif diff --git a/src/Core/Image/TileCacheFactory.h b/src/Core/Image/TileCacheFactory.h new file mode 100644 index 00000000..d859f61b --- /dev/null +++ b/src/Core/Image/TileCacheFactory.h @@ -0,0 +1,94 @@ +/** + * This file is part of the IC reverse engineering tool Degate. + * + * Copyright 2008, 2009, 2010 by Martin Schobert + * Copyright 2021 Dorian Bachelot + * + * Degate is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Degate is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with degate. If not, see . + * + */ + +#ifndef __TILECACHEFACTORY_H__ +#define __TILECACHEFACTORY_H__ + +#include "Core/Image/TileCacheAttached.h" +#include "Core/Image/TileCacheStore.h" +#include "Core/Primitive/SingletonBase.h" + +namespace degate +{ + /** + * @class TileCacheFactory + * @brief Create appropriete tile cache regarding the type of the current loaded project. + */ + class TileCacheFactory : public SingletonBase + { + public: + + /** + * Generate a new appropriete tile cache. + * + * @param path : is either the image path or the path with saved image in Degate tile image format. + * @param tile_width_exp : the width (and height) for image tiles. This + * value is specified as an exponent to the base 2. This means for + * example that if you want to use a width of 1024 pixel, you have + * to give a value of 10, because 2^10 is 1024. + * @param scale : the scale to apply when loading the image (e.g. scale = 2 + * will load the image with final size of width/2 and height/2). + * @see ScalingManager. + */ + template + inline std::shared_ptr> generate(std::string path, + unsigned int tile_width_exp, + unsigned int scale = 1) + { + switch (project_type) + { + case ProjectType::Normal: + return std::make_shared>(path, tile_width_exp, scale); + break; + case ProjectType::Attached: + return std::make_shared>(path, tile_width_exp, scale); + break; + default: + return std::make_shared>(path, tile_width_exp); + break; + } + } + + /** + * Set the project type to use when creating new tile cache. + * + * If the project type change, make sure to destroy all existing + * tile cache and recreate them. + */ + inline void set_project_type(ProjectType project_type) + { + this->project_type = project_type; + } + + /** + * Get the current project type used to generate new tile cache. + */ + inline ProjectType get_project_type() + { + return project_type; + } + + private: + ProjectType project_type = ProjectType::Normal; + }; +} // namespace degate + +#endif //__TILECACHEFACTORY_H__ diff --git a/src/Core/Image/TileCacheStore.h b/src/Core/Image/TileCacheStore.h new file mode 100644 index 00000000..fa546983 --- /dev/null +++ b/src/Core/Image/TileCacheStore.h @@ -0,0 +1,150 @@ +/** + * This file is part of the IC reverse engineering tool Degate. + * + * Copyright 2008, 2009, 2010 by Martin Schobert + * Copyright 2019-2020 Dorian Bachelot + * + * Degate is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Degate is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with degate. If not, see . + * + */ + +#ifndef __TILECACHESTORE_H__ +#define __TILECACHESTORE_H__ + +#include "Core/Utils/MemoryMap.h" +#include "Core/Utils/FileSystem.h" +#include "Core/Configuration.h" +#include "Core/Image/TileCache.h" + +#include +#include +#include +#include +#include // for make_pair +#include +#include +#include + +namespace degate +{ + + /** + * The TileCacheStore class handles caching of image tiles. + * + * The implementation keeps track how old the cached tile is. + * If new tiles become loaded, old tiles are removed from the + * cache. You can control the numer of cached tiles via the + * constructor parameter \p _min_cache_tiles. The memory + * requirement is around + * \p _min_cache_tiles*sizeof(PixelPolicy::pixel_type)*(2^_tile_width_exp)^2 , + * where \p sizeof(PixelPolicy::pixel_type) is the size of a pixel. + */ + template + class TileCacheStore : public TileCache + { + + public: + + /** + * Create a TileCacheStore object. + * + * @param path : the path of the path where all the tiles are for a TileImage. + * @param tile_width_exp : the width (and height) for image tiles. This + * value is specified as an exponent to the base 2. This means for + * example that if you want to use a width of 1024 pixel, you have + * to give a value of 10, because 2^10 is 1024. + * @param scale : the scale to apply when loading the image (e.g. scale = 2 + * will load the image with final size of width/2 and height/2). + * @see ScalingManager. Not used here since images are already imported/scaled. + */ + TileCacheStore(std::string path, unsigned int tile_width_exp, unsigned int scale = 1) + : TileCache(path, tile_width_exp, scale) + { + } + + /** + * Load a new tile and update the cache. + * + * @param x : the x index of the tile (not the real coordinate). + * @param y : the y index of the tile (not the real coordinate). + * @param update_current : if true, will update the current_tile pointer, otherwise not. + */ + inline void load_tile(unsigned int x, unsigned int y, bool update_current = false) + { + std::lock_guard lock(TileCache::mutex); + + // create a file name from tile number + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%d_%d.dat", x, y); + + // if filename/object is not in cache, load the tile + typename TileCache::cache_type::const_iterator iter = TileCache::cache.find(filename); + + if (iter == TileCache::cache.end()) + { + GlobalTileCache& gtc = GlobalTileCache::get_instance(); + + bool ok = gtc.request_cache_memory(this, TileCache::get_image_size()); + assert(ok == true); + struct timespec now{}; + GET_CLOCK(now); + + TileCache::cache[filename] = std::make_pair(load(filename), now); + + //debug(TM, "Cache size : %d/%d", gtc.get_allocated_memory(), gtc.get_max_cache_memory()); + + #ifdef TILECACHE_DEBUG + gtc.print_table(); + #endif + } + + // Get current time + struct timespec now{}; + GET_CLOCK(now); + + // Update entry + auto entry = TileCache::cache[filename]; + entry.second = now; + + if (update_current) + { + TileCache::current_tile = entry.first; + TileCache::curr_tile_num_x = x; + TileCache::curr_tile_num_y = y; + } + } + + + private: + + /** + * Load a tile from an image file. + * @param filename Just the name of the file to load. The filename is + * relative to the \p path. + */ + std::shared_ptr> + load(std::string const& filename) const + { + std::shared_ptr> mem( + new MemoryMap(uint_fast64_t(1) << TileCache::tile_width_exp, + uint_fast64_t(1) << TileCache::tile_width_exp, + MAP_STORAGE_TYPE_PERSISTENT_FILE, + join_pathes(TileCache::path, filename))); + + return mem; + } + }; // end of class TileCacheStore +} + +#endif diff --git a/src/Core/Image/TileImage.h b/src/Core/Image/TileImage.h index e7eac4c4..406308ac 100644 --- a/src/Core/Image/TileImage.h +++ b/src/Core/Image/TileImage.h @@ -22,18 +22,19 @@ #ifndef __TILEIMAGE_H__ #define __TILEIMAGE_H__ +#include "Core/Utils/FileSystem.h" #include "Globals.h" #include "PixelPolicies.h" #include "StoragePolicies.h" -#include "Core/Utils/FileSystem.h" -#include "TileCache.h" +#include "TileCacheStore.h" +#include "Core/Image/TileCacheFactory.h" namespace degate { /** * Storage policy for image objects that consists of tiles. * - * This implementation uses a TileCache. + * This implementation uses a TileCacheStore. */ template class StoragePolicy_Tile : public StoragePolicy_Base @@ -52,10 +53,10 @@ namespace degate const unsigned int offset_bitmask; // The place where we store the image data. - const std::string directory; + const std::string path; // A helper class to load tiles. - mutable TileCache tile_cache; + std::shared_ptr> tile_cache; unsigned int tiles_number; @@ -69,7 +70,10 @@ namespace degate * Get the minimum width or height of an tile based image, that * it at least requested_size pixel width / height. * @param requested_size The minimum size. - * @param tile_width_exp The exponent (to base 2) that gives th + * @param tile_width_exp he width (and height) for image tiles. This + * value is specified as an exponent to the base 2. This means for + * example that if you want to use a width of 1024 pixel, you have + * to give a value of 10, because 2^10 is 1024. * @return */ unsigned int calc_real_size(unsigned int requested_size, @@ -93,9 +97,8 @@ namespace degate * * @param width The minimum width of the image. * @param height The minimum height of the image. - * @param directory A tile based image is stored in multiple - * files. This directory specifies the place where - * the files are stored. If the directory doen't exits, it is created. + * @param path Can be a path with all the tile image (store mode) + * or also directly an image path (attached mode). * @param persistent This boolean value indicates whether the image files * are removed on object destruction. * @param tile_width_exp The width (and height) for image tiles. This @@ -103,19 +106,22 @@ namespace degate * example that if you want to use a width of 1024 pixel, you have * to give a value of 10, because 2^10 is 1024. */ - StoragePolicy_Tile(unsigned int width, unsigned int height, - std::string const& directory, + StoragePolicy_Tile(unsigned int width, + unsigned int height, + std::string const& path, bool persistent = false, - unsigned int tile_width_exp = 10) : - persistent(persistent), - tile_width_exp(tile_width_exp), - offset_bitmask((1 << tile_width_exp) - 1), - directory(directory), - tile_cache(directory, tile_width_exp, persistent), - width(width), - height(height) + unsigned int scale = 1, + unsigned int tile_width_exp = 10) + : persistent(persistent), + tile_width_exp(tile_width_exp), + offset_bitmask((1 << tile_width_exp) - 1), + path(path), + tile_cache(TileCacheFactory::get_instance().generate(path, tile_width_exp, scale)), + width(width), + height(height) { - if (!file_exists(directory)) create_directory(directory); + if (!file_exists(path)) + create_directory(path); double temp_tile_size = 1 << tile_width_exp; tiles_number = static_cast(ceil(static_cast(width) / temp_tile_size)) * static_cast(ceil(static_cast(height) / temp_tile_size)); @@ -126,8 +132,10 @@ namespace degate */ virtual ~StoragePolicy_Tile() { - tile_cache.release_memory(); - if (persistent == false) remove_directory(directory); + tile_cache->release_memory(); + + if (persistent == false && is_directory(path)) + remove_directory(path); } inline unsigned int get_tiles_number() const @@ -148,18 +156,16 @@ namespace degate return tile_width_exp; } - /** - * Get the directory, where images are stored. + * Either where images are stored or the image path. */ - std::string get_directory() const { return directory; } + std::string get_path() const { return path; } /** * Check if the image is persistent. */ bool is_persistent() const { return persistent; } - inline typename PixelPolicy::pixel_type get_pixel(unsigned int x, unsigned int y) const; inline void set_pixel(unsigned int x, unsigned int y, typename PixelPolicy::pixel_type new_val); @@ -169,7 +175,7 @@ namespace degate */ void raw_copy(void* dst_buf, unsigned int src_x, unsigned int src_y) const { - MemoryMap_shptr mem = tile_cache.get_tile(src_x, src_y); + MemoryMap_shptr mem = tile_cache->get_tile(src_x, src_y); mem->raw_copy(dst_buf); } @@ -179,7 +185,7 @@ namespace degate */ void* data(unsigned int src_x, unsigned int src_y) { - return tile_cache.get_tile(src_x, src_y)->data(); + return tile_cache->get_tile(src_x, src_y)->data(); } /** @@ -198,7 +204,7 @@ namespace degate unsigned int max_y, unsigned int radius) { - tile_cache.cache_around(min_x, max_x, min_y, max_y, width, height, radius); + tile_cache->cache_around(min_x, max_x, min_y, max_y, width, height, radius); } /** @@ -206,7 +212,7 @@ namespace degate */ void release_memory() { - tile_cache.release_memory(); + tile_cache->release_memory(); } }; @@ -215,7 +221,7 @@ namespace degate StoragePolicy_Tile::get_pixel(unsigned int x, unsigned int y) const { - MemoryMap_shptr mem = tile_cache.get_tile(x, y); + MemoryMap_shptr mem = tile_cache->get_tile(x, y); return mem->get(x & offset_bitmask, y & offset_bitmask); } @@ -224,7 +230,7 @@ namespace degate StoragePolicy_Tile::set_pixel(unsigned int x, unsigned int y, typename PixelPolicy::pixel_type new_val) { - MemoryMap_shptr mem = tile_cache.get_tile(x, y); + MemoryMap_shptr mem = tile_cache->get_tile(x, y); mem->set(x & offset_bitmask, y & offset_bitmask, new_val); } } diff --git a/src/Core/LogicModel/Layer.cc b/src/Core/LogicModel/Layer.cc index 7656ed47..530075b6 100644 --- a/src/Core/LogicModel/Layer.cc +++ b/src/Core/LogicModel/Layer.cc @@ -24,10 +24,8 @@ #include "Core/LogicModel/Gate/Gate.h" #include "Core/LogicModel/Via/Via.h" - #include - using namespace degate; void Layer::add_object(std::shared_ptr o) @@ -71,22 +69,24 @@ std::shared_ptr Layer::get_object(object_id_t object_id) return (*iter).second; } -Layer::Layer(BoundingBox const& bbox, Layer::LAYER_TYPE layer_type) : +Layer::Layer(BoundingBox const& bbox, ProjectType project_type, Layer::LAYER_TYPE layer_type) : quadtree(bbox, 100), layer_type(layer_type), layer_pos(0), enabled(true), - layer_id(0) + layer_id(0), + project_type(project_type) { } -Layer::Layer(BoundingBox const& bbox, Layer::LAYER_TYPE layer_type, +Layer::Layer(BoundingBox const& bbox, ProjectType project_type, Layer::LAYER_TYPE layer_type, BackgroundImage_shptr img) : quadtree(bbox, 100), layer_type(layer_type), layer_pos(0), enabled(true), - layer_id(0) + layer_id(0), + project_type(project_type) { set_image(img); } @@ -101,7 +101,7 @@ Layer::~Layer() */ DeepCopyable_shptr Layer::clone_shallow() const { - auto clone = std::make_shared(quadtree.get_bounding_box(), layer_type); + auto clone = std::make_shared(quadtree.get_bounding_box(), project_type, layer_type); clone->layer_pos = layer_pos; clone->enabled = enabled; clone->description = description; @@ -224,9 +224,7 @@ Layer::qt_region_iterator Layer::region_end() void Layer::set_image(BackgroundImage_shptr img) { - scaling_manager = - std::make_shared> - (img, img->get_directory()); + scaling_manager = std::make_shared>(img, img->get_path(), project_type); scaling_manager->create_scalings(); } @@ -247,11 +245,10 @@ std::string Layer::get_image_filename() const throw DegateLogicException("There is no scaling manager."); else { - const ScalingManager::image_map_element p = - scaling_manager->get_image(1); + const ScalingManager::image_map_element p = scaling_manager->get_image(1); if (p.second != nullptr) - return p.second->get_directory(); + return p.second->get_path(); else throw DegateLogicException("The scaling manager failed to return an image pointer."); } diff --git a/src/Core/LogicModel/Layer.h b/src/Core/LogicModel/Layer.h index 40cfe89b..a1f6e399 100644 --- a/src/Core/LogicModel/Layer.h +++ b/src/Core/LogicModel/Layer.h @@ -81,6 +81,8 @@ namespace degate layer_id_t layer_id; + ProjectType project_type; + protected: /** @@ -117,13 +119,12 @@ namespace degate /** * Create a new logic model layer. */ - Layer(BoundingBox const& bbox, LAYER_TYPE layer_type = Layer::UNDEFINED); + Layer(BoundingBox const& bbox, ProjectType project_type, LAYER_TYPE layer_type = Layer::UNDEFINED); /** * Create a new logic model layer. */ - Layer(BoundingBox const& bbox, LAYER_TYPE layer_type, - BackgroundImage_shptr img); + Layer(BoundingBox const& bbox, ProjectType project_type, LAYER_TYPE layer_type, BackgroundImage_shptr img); /** * Destruct a layer. @@ -238,7 +239,7 @@ namespace degate BackgroundImage_shptr get_image(); /** - * Get the directory name for the image, that represents the + * Get the path name for the image, that represents the * background image of the layer. * @exception DegateLogicException If you did not set the background image, then this * exception is thrown. @@ -402,6 +403,11 @@ namespace degate * Check if the layer has a valid layer ID. */ virtual bool has_valid_layer_id() const { return layer_id != 0; } + + /** + * Get project type. + */ + inline ProjectType get_project_type() const { return project_type; }; }; } diff --git a/src/Core/LogicModel/LogicModel.cc b/src/Core/LogicModel/LogicModel.cc index e0caa692..c0ae1df3 100644 --- a/src/Core/LogicModel/LogicModel.cc +++ b/src/Core/LogicModel/LogicModel.cc @@ -109,10 +109,11 @@ object_id_t LogicModel::get_new_object_id() } -LogicModel::LogicModel(unsigned int width, unsigned int height, unsigned int layers) : +LogicModel::LogicModel(unsigned int width, unsigned int height, ProjectType project_type, unsigned int layers) : bounding_box(static_cast(width), static_cast(height)), main_module(new Module("main_module", "", true)), - object_id_counter(0) + object_id_counter(0), + project_type(project_type) { gate_library = std::make_shared(); @@ -680,7 +681,7 @@ void LogicModel::add_layer(layer_position_t pos, Layer_shptr new_layer) void LogicModel::add_layer(layer_position_t pos) { - Layer_shptr new_layer(new Layer(bounding_box)); + Layer_shptr new_layer(new Layer(bounding_box, project_type)); add_layer(pos, new_layer); } diff --git a/src/Core/LogicModel/LogicModel.h b/src/Core/LogicModel/LogicModel.h index c6c40afa..f0c63e86 100644 --- a/src/Core/LogicModel/LogicModel.h +++ b/src/Core/LogicModel/LogicModel.h @@ -112,6 +112,8 @@ namespace degate diameter_t port_diameter = 5; + ProjectType project_type; + private: /** @@ -215,7 +217,7 @@ namespace degate /** * The constructor for the logic model. */ - LogicModel(unsigned int width, unsigned int height, unsigned int layers = 0); + LogicModel(unsigned int width, unsigned int height, ProjectType project_type, unsigned int layers = 0); /** diff --git a/src/Core/LogicModel/LogicModelHelper.cc b/src/Core/LogicModel/LogicModelHelper.cc index 3391a0ff..165bbe4f 100644 --- a/src/Core/LogicModel/LogicModelHelper.cc +++ b/src/Core/LogicModel/LogicModelHelper.cc @@ -200,36 +200,6 @@ void degate::grab_template_images(LogicModel_shptr lmodel, } } - -void degate::load_background_image(Layer_shptr layer, - std::string const& project_dir, - std::string const& image_file) -{ - if (layer == nullptr) - throw InvalidPointerException("Error: you passed an invalid pointer to load_background_image()"); - - boost::format fmter("layer_%1%.dimg"); - fmter % layer->get_layer_id(); // was get_layer_pos() - - std::string dir(join_pathes(project_dir, fmter.str())); - - if (layer->has_background_image()) - layer->unset_image(); - - debug(TM, "Create background image in %s", dir.c_str()); - BackgroundImage_shptr bg_image(new BackgroundImage(layer->get_width(), - layer->get_height(), - dir)); - - debug(TM, "Load image %s", image_file.c_str()); - load_image(image_file, bg_image); - - debug(TM, "Set image to layer."); - layer->set_image(bg_image); - debug(TM, "Done."); -} - - void load_tile(const QRgb* rba_data, unsigned int tile_size, unsigned int tile_index, @@ -323,7 +293,7 @@ void create_scaled_background_image(const std::string& dir, const BackgroundImag QImage img = current_reader.read(); if (img.isNull()) { - debug(TM, "can't create %s (can't read image)\n", bg_image->get_directory().c_str()); + debug(TM, "can't create %s (can't read image)\n", bg_image->get_path().c_str()); } // Convert to good format @@ -384,7 +354,7 @@ void create_scaled_background_images(const BackgroundImage_shptr& bg_image, unsi // create a new image char dir_name[PATH_MAX]; snprintf(dir_name, sizeof(dir_name), "scaling_%d.dimg", i); - std::string dir_path = join_pathes(bg_image->get_directory(), std::string(dir_name)); + std::string dir_path = join_pathes(bg_image->get_path(), std::string(dir_name)); create_directory(dir_path); reader.device()->seek(0); diff --git a/src/Core/LogicModel/LogicModelHelper.h b/src/Core/LogicModel/LogicModelHelper.h index 9c3a34a5..11a570c3 100644 --- a/src/Core/LogicModel/LogicModelHelper.h +++ b/src/Core/LogicModel/LogicModelHelper.h @@ -322,19 +322,6 @@ namespace degate Layer_shptr layer, BoundingBox const& search_bbox); - - /** - * Load an image in a common image format as background image for a layer. - * If there is already a background image, it will be unset and removed from - * the project directory. - * @exception InvalidPointerException If you pass an invalid shared pointer for - * \p layer, then this exception is raised. - * - */ - void load_background_image(Layer_shptr layer, - std::string const& project_dir, - std::string const& image_file); - /** * Load a new background image (optimized version). * diff --git a/src/Core/LogicModel/LogicModelImporter.cc b/src/Core/LogicModel/LogicModelImporter.cc index fa5d2dac..e9a47c40 100644 --- a/src/Core/LogicModel/LogicModelImporter.cc +++ b/src/Core/LogicModel/LogicModelImporter.cc @@ -87,9 +87,9 @@ void LogicModelImporter::import_into(LogicModel_shptr lmodel, } } -LogicModel_shptr LogicModelImporter::import(std::string const& filename) +LogicModel_shptr LogicModelImporter::import(std::string const& filename, ProjectType project_type) { - LogicModel_shptr lmodel(new LogicModel(width, height)); + LogicModel_shptr lmodel(new LogicModel(width, height, project_type)); assert(lmodel != nullptr); import_into(lmodel, filename); diff --git a/src/Core/LogicModel/LogicModelImporter.h b/src/Core/LogicModel/LogicModelImporter.h index 5116fe87..f5258608 100644 --- a/src/Core/LogicModel/LogicModelImporter.h +++ b/src/Core/LogicModel/LogicModelImporter.h @@ -104,7 +104,7 @@ namespace degate /** * import a logic model. */ - LogicModel_shptr import(std::string const& filename); + LogicModel_shptr import(std::string const& filename, ProjectType project_type); /** diff --git a/src/Core/Project/Project.cc b/src/Core/Project/Project.cc index bdf212f2..0516c7a9 100644 --- a/src/Core/Project/Project.cc +++ b/src/Core/Project/Project.cc @@ -35,23 +35,29 @@ using namespace std; using namespace degate; -Project::Project(length_t width, length_t height) : +Project::Project(length_t width, length_t height, ProjectType project_type) : bounding_box(width, height), degate_version(DEGATE_VERSION), - logic_model(new LogicModel(width, height)), - port_color_manager(new PortColorManager()) + logic_model(new LogicModel(width, height, project_type)), + port_color_manager(new PortColorManager()), + project_type(project_type) { + TileCacheFactory::get_instance().set_project_type(project_type); + init_default_values(); } -Project::Project(length_t width, length_t height, std::string const& directory, unsigned int layers) : +Project::Project(length_t width, length_t height, std::string const& directory, ProjectType project_type, unsigned int layers) : bounding_box(width, height), degate_version(DEGATE_VERSION), directory(directory), - logic_model(new LogicModel(width, height, layers)), - port_color_manager(new PortColorManager()) + logic_model(new LogicModel(width, height, project_type, layers)), + port_color_manager(new PortColorManager()), + project_type(project_type) { + TileCacheFactory::get_instance().set_project_type(project_type); + init_default_values(); } @@ -379,3 +385,8 @@ RCBase::container_type& Project::get_rcv_blacklist() { return rcv_blacklist; } + +ProjectType Project::get_project_type() const +{ + return project_type; +} \ No newline at end of file diff --git a/src/Core/Project/Project.h b/src/Core/Project/Project.h index c9b355d9..435c18a7 100644 --- a/src/Core/Project/Project.h +++ b/src/Core/Project/Project.h @@ -51,6 +51,32 @@ namespace degate namespace degate { + /** + * Transform a string to ProjectType enum element. + */ + inline ProjectType to_project_type(std::string string_type) + { + if (string_type == "Normal") + return ProjectType::Normal; + else if (string_type == "Attached") + return ProjectType::Attached; + else + return ProjectType::Normal; + } + + /** + * Transform a ProjectType enum element to string. + */ + inline std::string from_project_type(ProjectType type) + { + switch (type) + { + case ProjectType::Normal: return "Normal"; + case ProjectType::Attached: return "Attached"; + default: return "Normal"; + } + } + struct ProjectSnapshot { boost::posix_time::ptime datetime; @@ -105,6 +131,9 @@ namespace degate RCBase::container_type rcv_blacklist; unsigned int font_size; + + ProjectType project_type; + private: void init_default_values(); @@ -115,13 +144,13 @@ namespace degate * Create a new and empty project. * It will create an empty logic model as well. */ - Project(length_t width, length_t height); + Project(length_t width, length_t height, ProjectType project_type = ProjectType::Normal); /** * Create a new and empty project. * It will create an empty logic model as well. */ - Project(length_t width, length_t height, std::string const& directory, unsigned int layers = 0); + Project(length_t width, length_t height, std::string const& directory, ProjectType project_type = ProjectType::Normal, unsigned int layers = 0); /** @@ -303,6 +332,11 @@ namespace degate * Get a list of blacklisted Rule Check violations. */ RCBase::container_type& get_rcv_blacklist(); + + /** + * Get the type of the project (attached or normal). + */ + ProjectType get_project_type() const; }; } diff --git a/src/Core/Project/ProjectExporter.cc b/src/Core/Project/ProjectExporter.cc index a2ae7e29..34d574f6 100644 --- a/src/Core/Project/ProjectExporter.cc +++ b/src/Core/Project/ProjectExporter.cc @@ -201,6 +201,7 @@ void ProjectExporter::set_project_node_attributes(QDomDocument& doc, prj_elem.setAttribute("template-dimension", QString::fromStdString(number_to_string(prj->get_template_dimension()))); prj_elem.setAttribute("font-size", QString::fromStdString(number_to_string(prj->get_font_size()))); + prj_elem.setAttribute("type", QString::fromStdString(from_project_type(prj->get_project_type()))); } @@ -231,9 +232,17 @@ void ProjectExporter::add_layers(QDomDocument& doc, layer_elem.setAttribute("enabled", QString::fromStdString(layer->is_enabled() ? "true" : "false")); if (layer->has_background_image()) - layer_elem.setAttribute("image-filename", - QString::fromStdString( - get_relative_path(layer->get_image_filename(), project_dir))); + { + // Regarding the project type, set just the filename or the full path + if (layer->get_project_type() == ProjectType::Normal) + { + layer_elem.setAttribute("image-filename", QString::fromStdString(get_relative_path(layer->get_image_filename(), project_dir))); + } + else + { + layer_elem.setAttribute("image-path", QString::fromStdString(layer->get_image_filename())); + } + } layers_elem.appendChild(layer_elem); } diff --git a/src/Core/Project/ProjectImporter.cc b/src/Core/Project/ProjectImporter.cc index 1448c646..f85d9425 100644 --- a/src/Core/Project/ProjectImporter.cc +++ b/src/Core/Project/ProjectImporter.cc @@ -141,7 +141,10 @@ Project_shptr ProjectImporter::import(std::string const& directory) int w = parse_number(root_elem, "width"); int h = parse_number(root_elem, "height"); - Project_shptr prj(new Project(w, h, get_basedir(filename))); + // parse type + ProjectType type = to_project_type(root_elem.attribute("type", QString::fromStdString(from_project_type(ProjectType::Normal))).toStdString()); + + Project_shptr prj(new Project(w, h, get_basedir(filename), type)); assert(prj->get_project_directory().length() != 0); parse_project_element(prj, root_elem); @@ -168,7 +171,15 @@ void ProjectImporter::parse_layers_element(QDomElement const& layers_elem, const if (!layer_elem.isNull()) { - const std::string image_filename(layer_elem.attribute("image-filename").toStdString()); + // Regarding the type of the project, get just the file name (Normal) + // or the full image base path (Attached) + // TODO: need a way to ask for new image path if wrong (like the image moved) + std::string image_path; + if (prj->get_project_type() == ProjectType::Normal) + image_path = layer_elem.attribute("image-filename").toStdString(); + else + image_path = layer_elem.attribute("image-path").toStdString(); + const std::string layer_type_str(layer_elem.attribute("type").toStdString()); const std::string layer_description(layer_elem.attribute("description").toStdString()); auto position = parse_number(layer_elem, "position"); @@ -177,11 +188,11 @@ void ProjectImporter::parse_layers_element(QDomElement const& layers_elem, const Layer::LAYER_TYPE layer_type = Layer::get_layer_type_from_string(layer_type_str); auto layer_id = parse_number(layer_elem, "id", 0); - Layer_shptr new_layer = std::make_shared(prj->get_bounding_box(), layer_type); + Layer_shptr new_layer = std::make_shared(prj->get_bounding_box(), prj->get_project_type(), layer_type); LogicModel_shptr lmodel = prj->get_logic_model(); debug(TM, "Parsed a layer entry for type %s. This is a %s layer. Background image is %s", - layer_type_str.c_str(), Layer::get_layer_type_as_string(layer_type).c_str(), image_filename.c_str()); + layer_type_str.c_str(), Layer::get_layer_type_as_string(layer_type).c_str(), image_path.c_str()); bool layer_enabled = true; if (!layer_enabled_str.empty()) @@ -193,7 +204,7 @@ void ProjectImporter::parse_layers_element(QDomElement const& layers_elem, const lmodel->add_layer(position, new_layer); - load_background_image(new_layer, image_filename, prj); + load_background_image(new_layer, image_path, prj); } } } @@ -205,6 +216,17 @@ void ProjectImporter::load_background_image(const Layer_shptr& layer, debug(TM, "try to load image [%s]", image_filename.c_str()); if (!image_filename.empty()) { + // If attached project mode, just create the background image (no loading) + // Require just the right path (to the base image full path) and project dimension + // And then, just have to set the layer base image (scaling default = 1) + if (prj->get_project_type() == ProjectType::Attached) + { + BackgroundImage_shptr bg_image = std::make_shared(layer->get_width(), layer->get_height(), image_filename); + layer->set_image(bg_image); + + return; + } + assert(prj->get_project_directory().length() != 0); std::string image_path_to_load = join_pathes(prj->get_project_directory(), image_filename); diff --git a/src/GUI/Dialog/NewProjectDialog.cc b/src/GUI/Dialog/NewProjectDialog.cc index dd670353..d8d79072 100644 --- a/src/GUI/Dialog/NewProjectDialog.cc +++ b/src/GUI/Dialog/NewProjectDialog.cc @@ -51,6 +51,10 @@ namespace degate project_path_label.setText(tr("Project directory path:")); project_path_button.setText(tr("Set project directory path")); + // Attached mode + attached_mode_label.setText(tr("Attached mode?")); + attached_mode_box.setChecked(false); + // Validate button validate_button.setText(tr("Ok")); @@ -67,6 +71,9 @@ namespace degate user_selected_directory = true; } + project_group_layout.addWidget(&attached_mode_label, 2, 0); + project_group_layout.addWidget(&attached_mode_box, 2, 1); + // Control layout control_layout.addWidget(&validate_button, 0, 1); control_layout.setColumnStretch(0, 1); @@ -126,7 +133,7 @@ namespace degate create_directory(project_directory); // Create the project - project = std::make_shared(size.width(), size.height(), project_directory, layers_edit_widget.get_layer_count()); + project = std::make_shared(size.width(), size.height(), project_directory, attached_mode_box.isChecked() ? ProjectType::Attached : ProjectType::Normal, layers_edit_widget.get_layer_count()); project->set_name(project_name_edit.text().toStdString()); // Create each layer diff --git a/src/GUI/Dialog/NewProjectDialog.h b/src/GUI/Dialog/NewProjectDialog.h index 3e834d4a..21cabdaa 100644 --- a/src/GUI/Dialog/NewProjectDialog.h +++ b/src/GUI/Dialog/NewProjectDialog.h @@ -90,6 +90,9 @@ namespace degate QLabel project_path_label; QPushButton project_path_button; + QLabel attached_mode_label; + QCheckBox attached_mode_box; + QLabel layers_edit_label; LayersEditWidget layers_edit_widget; diff --git a/src/GUI/MainWindow.cc b/src/GUI/MainWindow.cc index 95c1ea7a..a6f10901 100644 --- a/src/GUI/MainWindow.cc +++ b/src/GUI/MainWindow.cc @@ -45,7 +45,6 @@ namespace degate setWindowTitle("Degate"); setWindowIcon(QIcon(":/degate_logo.png")); - // Workspace workspace = new WorkspaceRenderer(this); @@ -665,6 +664,7 @@ namespace degate project.reset(); project = nullptr; + workspace->set_project(nullptr); workspace->update_screen(); @@ -685,7 +685,16 @@ namespace degate status_bar.showMessage(tr("Creating a new project...")); NewProjectDialog dialog(this); - auto res = dialog.exec(); + + int res; + try + { + res = dialog.exec(); + } + catch(const std::exception& e) + { + std::cerr << e.what() << '\n'; + } if (res == QDialog::Accepted) { @@ -700,7 +709,9 @@ namespace degate status_bar.showMessage(tr("Created a new project."), SECOND(DEFAULT_STATUS_MESSAGE_DURATION)); } else + { status_bar.showMessage(tr("New project creation operation cancelled."), SECOND(DEFAULT_STATUS_MESSAGE_DURATION)); + } } void MainWindow::on_menu_project_create_subproject() diff --git a/src/GUI/Widget/LayersEditWidget.cc b/src/GUI/Widget/LayersEditWidget.cc index 81eea98d..a0a38260 100644 --- a/src/GUI/Widget/LayersEditWidget.cc +++ b/src/GUI/Widget/LayersEditWidget.cc @@ -337,7 +337,7 @@ namespace degate if (text_id == '?') { // New layer - layer = std::make_shared(BoundingBox(project->get_logic_model()->get_width(), project->get_logic_model()->get_height())); + layer = std::make_shared(BoundingBox(project->get_logic_model()->get_width(), project->get_logic_model()->get_height()), project->get_project_type()); layer->set_layer_id(project->get_logic_model()->get_new_layer_id()); } else @@ -359,37 +359,51 @@ namespace degate // Image LayerBackgroundSelectionButton* background = dynamic_cast(layers.cellWidget(i, 4)); - try + + if (project->get_project_type() == ProjectType::Normal) { - if (background->has_new_image()) + // If Normal project type, then just load the new image background + // This can take a very long time + + try { - // Start progress dialog - ProgressDialog progress_dialog(this->parentWidget(), - tr("Importation and conversion of the new background image. " - "This operation can take a lot of time, but will be performed only once."), - nullptr); - - // Set the job to start the background loading. - progress_dialog.set_job([&layer, &background, this]() - { - load_new_background_image(layer, project->get_project_directory(), background->get_image_path()); - }); - - // Start the process - progress_dialog.exec(); - - if (progress_dialog.was_canceled()) - debug(TM, "The background image importation and conversion operation has been canceled."); + if (background->has_new_image()) + { + // Start progress dialog + ProgressDialog progress_dialog(this->parentWidget(), + tr("Importation and conversion of the new background image. " + "This operation can take a lot of time, but will be performed only once."), + nullptr); + + // Set the job to start the background loading. + progress_dialog.set_job([&layer, &background, this]() + { + load_new_background_image(layer, project->get_project_directory(), background->get_image_path()); + }); + + // Start the process + progress_dialog.exec(); + + if (progress_dialog.was_canceled()) + debug(TM, "The background image importation and conversion operation has been canceled."); + } + } + catch (std::exception& e) + { + QMessageBox::critical(this, + tr("Error"), + tr("Can't import the background image.") + "\n" + tr("Error:") + " " + + QString::fromStdString(e.what())); + + return; } } - catch (std::exception& e) + else { - QMessageBox::critical(this, - tr("Error"), - tr("Can't import the background image.") + "\n" + tr("Error:") + " " + - QString::fromStdString(e.what())); + // If attached project type, then just create the background image without loading - return; + BackgroundImage_shptr bg_image = std::make_shared(layer->get_width(), layer->get_height(), background->get_image_path()); + layer->set_image(bg_image); } diff --git a/src/GUI/Workspace/WorkspaceBackground.cc b/src/GUI/Workspace/WorkspaceBackground.cc index 511fcae8..6f204550 100644 --- a/src/GUI/Workspace/WorkspaceBackground.cc +++ b/src/GUI/Workspace/WorkspaceBackground.cc @@ -152,13 +152,17 @@ namespace degate assert(context->glGetError() == GL_NO_ERROR); - if (!future.isFinished()) - return; + // What follow was removed since the performance impact + // needs to be evaluated. Also, that don't fit well with + // the attached mode tile cache. - future.setFuture(QtConcurrent::run([this, min_x, max_x, min_y, max_y]() - { - background_image->cache(min_x, max_x, min_y, max_y, 1); - })); + //if (!future.isFinished()) + //return; + + //future.setFuture(QtConcurrent::run([this, min_x, max_x, min_y, max_y]() + //{ + //background_image->cache(min_x, max_x, min_y, max_y, 4); + //})); } void WorkspaceBackground::draw(const QMatrix4x4& projection) diff --git a/src/Globals.h b/src/Globals.h index e1682870..bc96301f 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -197,6 +197,15 @@ namespace degate RET_MATH_ERR = 5, RET_CANCEL = 6 }; + + /** + * Project type enum. Default is Normal. + */ + enum ProjectType + { + Normal, + Attached + }; } #endif diff --git a/tests/src/ImageTests.cc b/tests/src/ImageTests.cc index 6ac3648d..3a349efb 100644 --- a/tests/src/ImageTests.cc +++ b/tests/src/ImageTests.cc @@ -71,7 +71,7 @@ TileImage_RGBA_shptr read_image(std::shared_ptrget_height(), tile_size_exp)); - REQUIRE(img->get_directory().size() > 0); + REQUIRE(img->get_path().size() > 0); bool state = reader->get_image(img); @@ -86,7 +86,7 @@ TileImage_RGBA_shptr read_image(std::shared_ptrget_directory())); + REQUIRE(is_directory(img->get_path())); return img; } diff --git a/tests/src/LogicModelDOTExporterTests.cc b/tests/src/LogicModelDOTExporterTests.cc index cb8b3c37..02a1036e 100644 --- a/tests/src/LogicModelDOTExporterTests.cc +++ b/tests/src/LogicModelDOTExporterTests.cc @@ -47,7 +47,7 @@ TEST_CASE("Test export", "[LogicModelDOTExporter]") */ LogicModelImporter lm_importer(500, 500, glib); std::string filename("tests_files/test_project/lmodel.xml"); - LogicModel_shptr lmodel(lm_importer.import(filename)); + LogicModel_shptr lmodel(lm_importer.import(filename, ProjectType::Normal)); // If this fail, need to add the 'tests_files' folder beside the tests executable. REQUIRE(lmodel != nullptr); diff --git a/tests/src/LogicModelImporterTests.cc b/tests/src/LogicModelImporterTests.cc index 2ad129fb..6a568659 100644 --- a/tests/src/LogicModelImporterTests.cc +++ b/tests/src/LogicModelImporterTests.cc @@ -38,7 +38,7 @@ TEST_CASE("Test import", "[LogicModelImporter]") LogicModelImporter lm_importer(500, 500, glib); std::string filename("tests_files/test_project/lmodel.xml"); - LogicModel_shptr lmodel(lm_importer.import(filename)); + LogicModel_shptr lmodel(lm_importer.import(filename, ProjectType::Normal)); // If this fail, need to add the 'tests_files' folder beside the tests executable. REQUIRE(lmodel != nullptr); @@ -54,6 +54,6 @@ TEST_CASE("Test import", "[LogicModelImporter]") } // try to reuse the importer - LogicModel_shptr lmodel2(lm_importer.import(filename)); + LogicModel_shptr lmodel2(lm_importer.import(filename, ProjectType::Normal)); REQUIRE(lmodel2 != nullptr); } diff --git a/tests/src/LogicModelTests.cc b/tests/src/LogicModelTests.cc index 22eb32c9..26f0f1d0 100644 --- a/tests/src/LogicModelTests.cc +++ b/tests/src/LogicModelTests.cc @@ -54,7 +54,7 @@ TEST_CASE("Test shared ptr casts", "[LogicModel]") TEST_CASE("Test add layer", "[LogicModel]") { - LogicModel_shptr lmodel(new LogicModel(100, 100)); + LogicModel_shptr lmodel(new LogicModel(100, 100, ProjectType::Normal)); lmodel->add_layer(0); lmodel->add_layer(1); lmodel->add_layer(2); @@ -70,7 +70,7 @@ TEST_CASE("Test add layer", "[LogicModel]") TEST_CASE("Test add and retrieve placed logic model", "[LogicModel]") { - LogicModel_shptr lmodel(new LogicModel(100, 100)); + LogicModel_shptr lmodel(new LogicModel(100, 100, ProjectType::Normal)); REQUIRE(lmodel != nullptr); PlacedLogicModelObject_shptr plo(new Gate(0, 10, 0, 10)); @@ -86,7 +86,7 @@ TEST_CASE("Test add and retrieve placed logic model", "[LogicModel]") TEST_CASE("Test add and retrieve wire", "[LogicModel]") { - LogicModel_shptr lmodel(new LogicModel(100, 100)); + LogicModel_shptr lmodel(new LogicModel(100, 100, ProjectType::Normal)); Wire_shptr w(new Wire(20, 21, 30, 31, 5)); REQUIRE(w->has_valid_object_id() == false); diff --git a/tests/src/ScalingManagerTests.cc b/tests/src/ScalingManagerTests.cc index b6fd5e8b..fed7d74e 100644 --- a/tests/src/ScalingManagerTests.cc +++ b/tests/src/ScalingManagerTests.cc @@ -46,6 +46,6 @@ TEST_CASE("Test scaling manager", "[ScalingManager]") bool state = tiff_reader.get_image(img); REQUIRE(state == true); - ScalingManager sm(img, img->get_directory(), 256); + ScalingManager sm(img, img->get_path(), ProjectType::Normal, 256); sm.create_scalings(); } \ No newline at end of file From 63a5936d0865568eaf5f682b5b095c98eeb9246b Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Sun, 19 Dec 2021 16:02:34 +0100 Subject: [PATCH 02/19] Added a workspace notifier to force a refresh when a new tile has been loaded. The workspace notifier usages are not limited to this case. --- src/Core/Image/TileCacheAttached.h | 7 +- src/GUI/Workspace/WorkspaceBackground.cc | 8 +- src/GUI/Workspace/WorkspaceNotifier.cc | 81 ++++++++++++++++++ src/GUI/Workspace/WorkspaceNotifier.h | 102 +++++++++++++++++++++++ src/GUI/Workspace/WorkspaceRenderer.cc | 8 ++ 5 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 src/GUI/Workspace/WorkspaceNotifier.cc create mode 100644 src/GUI/Workspace/WorkspaceNotifier.h diff --git a/src/Core/Image/TileCacheAttached.h b/src/Core/Image/TileCacheAttached.h index 725ff35c..f9b89a3a 100644 --- a/src/Core/Image/TileCacheAttached.h +++ b/src/Core/Image/TileCacheAttached.h @@ -26,6 +26,7 @@ #include "Core/Image/GlobalTileCache.h" #include "Core/Utils/FileSystem.h" #include "Core/Utils/MemoryMap.h" +#include "GUI/Workspace/WorkspaceNotifier.h" #include #include @@ -139,6 +140,8 @@ namespace degate auto data = std::make_pair(load(x, y), now); TileCache::mutex.lock(); TileCache::cache[filename] = data; + WorkspaceNotifier::get_instance().notify(WorkspaceTarget::WorkspaceBackground, WorkspaceNotification::Update); + WorkspaceNotifier::get_instance().notify(WorkspaceTarget::Workspace, WorkspaceNotification::Draw); TileCache::mutex.unlock(); }); @@ -175,8 +178,6 @@ namespace degate std::shared_ptr> load(unsigned int x, unsigned int y) { - debug(TM, "load(%d, %d)", x, y); - // Prepare sizes QSize reading_size{static_cast(tile_size), static_cast(tile_size)}; QSize read_size{static_cast(x) * static_cast(tile_size), static_cast(y) * static_cast(tile_size)}; @@ -239,8 +240,6 @@ namespace degate } } - debug(TM, "finished load(%d, %d)", x, y); - return mem; } diff --git a/src/GUI/Workspace/WorkspaceBackground.cc b/src/GUI/Workspace/WorkspaceBackground.cc index 6f204550..886f23c1 100644 --- a/src/GUI/Workspace/WorkspaceBackground.cc +++ b/src/GUI/Workspace/WorkspaceBackground.cc @@ -20,6 +20,7 @@ */ #include "WorkspaceBackground.h" +#include "GUI/Workspace/WorkspaceNotifier.h" #include @@ -56,6 +57,7 @@ namespace degate WorkspaceBackground::~WorkspaceBackground() { + WorkspaceNotifier::get_instance().undefine(WorkspaceTarget::WorkspaceBackground); free_textures(); } @@ -97,9 +99,13 @@ namespace degate delete fshader; program->link(); + + WorkspaceNotifier::get_instance().define(WorkspaceTarget::WorkspaceBackground, WorkspaceNotification::Update, std::bind(&WorkspaceBackground::update, this)); } - void WorkspaceBackground::update() //TODO: this is slow, try to have a thread that do that work + // TODO: Full update on each call, should now what changed and update only what's needed. + // Like save elem and compare, and {min,max}* : compare to know if something need to update. + void WorkspaceBackground::update() { free_textures(); diff --git a/src/GUI/Workspace/WorkspaceNotifier.cc b/src/GUI/Workspace/WorkspaceNotifier.cc new file mode 100644 index 00000000..c7034609 --- /dev/null +++ b/src/GUI/Workspace/WorkspaceNotifier.cc @@ -0,0 +1,81 @@ +/** + * This file is part of the IC reverse engineering tool Degate. + * + * Copyright 2008, 2009, 2010 by Martin Schobert + * Copyright 2019-2021 Dorian Bachelot + * + * Degate is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Degate is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with degate. If not, see . + * + */ + +#include "GUI/Workspace/WorkspaceNotifier.h" + +namespace degate +{ + void WorkspaceNotifier::define(WorkspaceTarget target, WorkspaceNotification notification, std::function updater) + { + targets[target][notification] = updater; + } + + void WorkspaceNotifier::undefine(WorkspaceTarget target) + { + targets[target].clear(); + } + + void WorkspaceNotifier::notify(WorkspaceTarget target, WorkspaceNotification notification) + { + // Check if we are in the main thread (the only space to do UI stuff) + if (qApp->thread() != QThread::currentThread()) + { + // If not, we need to run the notify() function in the main thread + // We are here in any thead but the main thread + + // Create a single shot and instant timer that will run in the main thread + QTimer* timer = new QTimer(); + timer->moveToThread(qApp->thread()); + timer->setSingleShot(true); + + // Connect the timeout of the timer to the notify() function + QObject::connect(timer, &QTimer::timeout, [=]() + { + // Main thread + + // Call the notify() function with proper args + notify(target, notification); + + // Delete this time when possible + timer->deleteLater(); + }); + + // Invoke the start method that will run the previous lambda (and therefore the notify() function with proper args) + // This will run in the main thread + QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0)); + + return; + } + + // Just to be sure we cath a possible regression of the upper method (force running notify() in the main thread) + assert(qApp->thread() == QThread::currentThread()); + + // We then, in the main thread, just iterate over targets and notification and, finally, run the associated function + auto wtarget = targets.find(target); + if (wtarget != targets.end()) + { + auto notifier = wtarget->second; + auto wnotification = notifier.find(notification); + if (wnotification != notifier.end()) + wnotification->second(); + } + } +} \ No newline at end of file diff --git a/src/GUI/Workspace/WorkspaceNotifier.h b/src/GUI/Workspace/WorkspaceNotifier.h new file mode 100644 index 00000000..f0a6d43b --- /dev/null +++ b/src/GUI/Workspace/WorkspaceNotifier.h @@ -0,0 +1,102 @@ +/** + * This file is part of the IC reverse engineering tool Degate. + * + * Copyright 2008, 2009, 2010 by Martin Schobert + * Copyright 2019-2021 Dorian Bachelot + * + * Degate is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Degate is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with degate. If not, see . + * + */ + +#ifndef __WORKSPACENOTIFIER_H__ +#define __WORKSPACENOTIFIER_H__ + +#include "Core/Primitive/SingletonBase.h" + +#include +#include +#include + +#include + +namespace degate +{ + /** + * @enum WorkspaceTarget + * @brief Defines workspace targets that can be used with WorkspaceNotifier. + */ + enum class WorkspaceTarget + { + WorkspaceBackground, + Workspace + }; + + /** + * @enum WorkspaceNotification + * @brief Defines workspace notifications that can be used with WorkspaceNotifier. + */ + enum class WorkspaceNotification + { + Update, + Draw + }; + + /** + * @class WorkspaceNotifier + * @brief Singleton used to notify a workspace class (like for an update to perform). + * + * @see WorkspaceBackground for example. + */ + class WorkspaceNotifier : public SingletonBase + { + public: + + /** + * Define a new notification for a target. + * + * @param target : the target. + * @param notification : the notification. + * @param updater : the corresponding function. + * + * @see WorkspaceTarget. + * @see WorkspaceNotification. + */ + void define(WorkspaceTarget target, WorkspaceNotification notification, std::function updater); + + /** + * Undefine all notifications for a target. + * + * @param target : the target. + * + * @see WorkspaceTarget. + */ + void undefine(WorkspaceTarget target); + + /** + * Notify a target about something, like an update to perform (if defined). + * + * @param target : the target to notify. + * @param notification : the notification. + * + * @see WorkspaceTarget. + * @see WorkspaceNotification. + */ + void notify(WorkspaceTarget target, WorkspaceNotification notification); + + private: + std::map>> targets; + }; +} + +#endif \ No newline at end of file diff --git a/src/GUI/Workspace/WorkspaceRenderer.cc b/src/GUI/Workspace/WorkspaceRenderer.cc index 05c4e309..1021348c 100644 --- a/src/GUI/Workspace/WorkspaceRenderer.cc +++ b/src/GUI/Workspace/WorkspaceRenderer.cc @@ -23,6 +23,7 @@ #include "GUI/Dialog/GateEditDialog.h" #include "GUI/Dialog/AnnotationEditDialog.h" #include "GUI/Preferences/PreferencesHandler.h" +#include "GUI/Workspace/WorkspaceNotifier.h" namespace degate { @@ -414,6 +415,8 @@ namespace degate void WorkspaceRenderer::cleanup() { + WorkspaceNotifier::get_instance().undefine(WorkspaceTarget::Workspace); + makeCurrent(); // Delete opengl objects here @@ -456,6 +459,11 @@ namespace degate debug(TM, "GLSL version: %s", glFuncs->glGetString(GL_SHADING_LANGUAGE_VERSION)); connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &WorkspaceRenderer::cleanup); + + // Define the draw notification for the workspace (renderer), just a repaint + WorkspaceNotifier::get_instance().define(WorkspaceTarget::Workspace, WorkspaceNotification::Draw, [=](){ + this->repaint(); + }); } void WorkspaceRenderer::paintGL() From d88eea5488193b43ad6824b35a2befb978e7ec1d Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Sun, 19 Dec 2021 16:24:56 +0100 Subject: [PATCH 03/19] Centered the view when opening a new project. --- src/GUI/Workspace/WorkspaceRenderer.cc | 27 ++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/GUI/Workspace/WorkspaceRenderer.cc b/src/GUI/Workspace/WorkspaceRenderer.cc index 1021348c..9e90992d 100644 --- a/src/GUI/Workspace/WorkspaceRenderer.cc +++ b/src/GUI/Workspace/WorkspaceRenderer.cc @@ -230,9 +230,32 @@ namespace degate regular_grid.viewport_update(BoundingBox(viewport_min_x, viewport_max_x, viewport_min_y, viewport_max_y)); regular_grid.update(); - set_projection(1, width() / 2.0, height() / 2.0); + // Reset scale + scale = 1.0; - update_screen(); + // If project set, then center view and max zoom out + if (project != nullptr) + { + if (width() < height()) + { + set_projection(static_cast(project->get_width()) / static_cast(width()), + project->get_width() / 2.0, + project->get_height() / 2.0); + } + else + { + set_projection(static_cast(project->get_height()) / static_cast(height()), + project->get_width() / 2.0, + project->get_height() / 2.0); + } + } + else + { + // Otherwise, just center the view + center_view(QPointF{width() / 2.0, height() / 2.0}); + } + + update_screen(); } bool WorkspaceRenderer::has_area_selection() From 4896d3af001c611502cf1985759fbbc867293417 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Mon, 20 Dec 2021 17:19:11 +0100 Subject: [PATCH 04/19] Fixed bugs with the attached project type when loading images in degate's format. --- src/Core/Image/Image.h | 22 ++++- src/Core/Image/Manipulation/ScalingManager.h | 55 ++++++----- src/Core/Image/TileCache.h | 38 +++++++- src/Core/Image/TileCacheAttached.h | 98 +++++++++++++++----- src/Core/Image/TileCacheFactory.h | 13 ++- src/Core/Image/TileCacheStore.h | 11 ++- src/Core/Image/TileImage.h | 10 +- src/Core/Project/ProjectImporter.cc | 13 ++- src/GUI/Workspace/WorkspaceNotifier.h | 12 +++ 9 files changed, 212 insertions(+), 60 deletions(-) diff --git a/src/Core/Image/Image.h b/src/Core/Image/Image.h index 8699b0ca..586246d4 100644 --- a/src/Core/Image/Image.h +++ b/src/Core/Image/Image.h @@ -227,17 +227,24 @@ namespace degate * value is specified as an exponent to the base 2. This means for * example that if you want to use a width of 1024 pixel, you have * to give a value of 10, because 2^10 is 1024. + * @param loading_type : the loading type to use when loading a new tile. + * @param notification_list : the list of workspace notification(s) to notify + * after a new loading finished. This is done only if async loading type. */ Image(unsigned int width, unsigned int height, unsigned int scale = 1, - unsigned int tile_width_exp = 10) : + unsigned int tile_width_exp = 10, + TileLoadingType loading_type = TileLoadingType::Sync, + const WorkspaceNotificationList& notification_list = {}) : ImageBase(width, height), StoragePolicy_Tile(width, height, create_temp_directory(), false, scale, - tile_width_exp) + tile_width_exp, + loading_type, + notification_list) { } @@ -256,19 +263,26 @@ namespace degate * value is specified as an exponent to the base 2. This means for * example that if you want to use a width of 1024 pixel, you have * to give a value of 10, because 2^10 is 1024. + * @param loading_type : the loading type to use when loading a new tile. + * @param notification_list : the list of workspace notification(s) to notify + * after a new loading finished. This is done only if async loading type. */ Image(unsigned int width, unsigned int height, std::string const& path, bool persistent = true, unsigned int scale = 1, - unsigned int tile_width_exp = 10) : + unsigned int tile_width_exp = 10, + TileLoadingType loading_type = TileLoadingType::Sync, + const WorkspaceNotificationList& notification_list = {}) : ImageBase(width, height), StoragePolicy_Tile(width, height, path, persistent, scale, - tile_width_exp) + tile_width_exp, + loading_type, + notification_list) { } diff --git a/src/Core/Image/Manipulation/ScalingManager.h b/src/Core/Image/Manipulation/ScalingManager.h index 524aee83..6ee1b995 100644 --- a/src/Core/Image/Manipulation/ScalingManager.h +++ b/src/Core/Image/Manipulation/ScalingManager.h @@ -168,32 +168,45 @@ namespace degate snprintf(dir_name, sizeof(dir_name), "scaling_%d.dimg", i); path = join_pathes(images[1]->get_path(), std::string(dir_name)); - } - else - path = images[1]->get_path(); - - // Create the scaling if needed - debug(TM, "create scaled image in %s for scaling factor %d?", path.c_str(), i); - if (!file_exists(path) && project_type == ProjectType::Normal) - { - debug(TM, "yes"); - create_directory(path); - - // Load the base image - std::shared_ptr new_img(new ImageType(w, h, path, images[1]->is_persistent(), i)); - // Scale down - scale_down_by_2(new_img, last_img); - last_img = new_img; + // Check if need to create scaled images + debug(TM, "create scaled image in %s for scaling factor %d?", path.c_str(), i); + if (!file_exists(path)) + { + debug(TM, "yes"); + create_directory(path); + + // Load the base image + std::shared_ptr new_img = std::make_shared(w, h, path, images[1]->is_persistent(), i); + + // Scale down + scale_down_by_2(new_img, last_img); + last_img = new_img; + } + else + { + debug(TM, "no"); + + // Load the scaled image + last_img = std::make_shared(w, h, path, images[1]->is_persistent(), i); + } } else { - debug(TM, "no"); - - // Load the image - std::shared_ptr new_img(new ImageType(w, h, path, images[1]->is_persistent(), i)); + path = images[1]->get_path(); - last_img = new_img; + // If attached, load async and notify workspace + background + last_img = std::make_shared( + w, + h, + path, + images[1]->is_persistent(), + i, + 10, + TileLoadingType::Async, + WorkspaceNotificationList{ + {WorkspaceTarget::WorkspaceBackground, WorkspaceNotification::Update}, + {WorkspaceTarget::Workspace, WorkspaceNotification::Draw}}); } images[i] = last_img; diff --git a/src/Core/Image/TileCache.h b/src/Core/Image/TileCache.h index dc658c20..d9cbb02f 100644 --- a/src/Core/Image/TileCache.h +++ b/src/Core/Image/TileCache.h @@ -25,9 +25,20 @@ #include "Core/Image/TileImage.h" #include "Core/Image/TileCacheBase.h" #include "Core/Image/GlobalTileCache.h" +#include "GUI/Workspace/WorkspaceNotifier.h" namespace degate { + /** + * @enum TileLoadingType + * @brief Defines the different tile loading types. + */ + enum class TileLoadingType + { + Sync, + Async + }; + /** * @class TileCache * @brief Tile cache base class (interface). @@ -50,11 +61,20 @@ namespace degate * @param scale : the scale to apply when loading the image (e.g. scale = 2 * will load the image with final size of width/2 and height/2). * @see ScalingManager. + * @param loading_type : the loading type to use when loading a new tile. + * @param notification_list : the list of workspace notification(s) to notify + * after a new loading finished. This is done only if async loading type. */ - inline TileCache(std::string path, unsigned int tile_width_exp, unsigned int scale) + inline TileCache(std::string path, + unsigned int tile_width_exp, + unsigned int scale, + TileLoadingType loading_type, + const WorkspaceNotificationList& notification_list) : path(std::move(path)), tile_width_exp(tile_width_exp), - scale(scale) + scale(scale), + loading_type(loading_type), + notification_list(notification_list) { } @@ -237,11 +257,20 @@ namespace degate /** * Get image size in bytes. */ - uint_fast64_t get_image_size() const + inline uint_fast64_t get_image_size() const { return sizeof(typename PixelPolicy::pixel_type) * (uint_fast64_t(1) << tile_width_exp) * (uint_fast64_t(1) << tile_width_exp); } + /** + * Send all notifications regarding the notification list. + */ + inline void notify() + { + for (auto notification : notification_list) + WorkspaceNotifier::get_instance().notify(notification.first, notification.second); + } + protected: const std::string path; const unsigned int tile_width_exp; @@ -262,6 +291,9 @@ namespace degate unsigned int scale; std::mutex mutex; + + TileLoadingType loading_type; + WorkspaceNotificationVector notification_list; }; } // namespace degate diff --git a/src/Core/Image/TileCacheAttached.h b/src/Core/Image/TileCacheAttached.h index f9b89a3a..1894d920 100644 --- a/src/Core/Image/TileCacheAttached.h +++ b/src/Core/Image/TileCacheAttached.h @@ -26,7 +26,7 @@ #include "Core/Image/GlobalTileCache.h" #include "Core/Utils/FileSystem.h" #include "Core/Utils/MemoryMap.h" -#include "GUI/Workspace/WorkspaceNotifier.h" +#include "Core/Image/TileCache.h" #include #include @@ -56,13 +56,26 @@ namespace degate * @param scale : the scale to apply when loading the image (e.g. scale = 2 * will load the image with final size of width/2 and height/2). * @see ScalingManager. + * @param loading_type : the loading type to use when loading a new tile. + * @param notification_list : the list of workspace notification(s) to notify + * after a new loading finished. This is done only if async loading type. */ - inline TileCacheAttached(std::string path, unsigned int tile_width_exp, unsigned int scale = 1) - : TileCache(path, tile_width_exp, scale), + inline TileCacheAttached(std::string path, + unsigned int tile_width_exp, + unsigned int scale, + TileLoadingType loading_type, + const WorkspaceNotificationList& notification_list) + : TileCache(path, tile_width_exp, scale, loading_type, notification_list), tile_size(1 << tile_width_exp), path(path) { QImageReader reader(path.c_str()); + if (reader.canRead() == false) + { + debug(TM, "Degate image format: %s", path.c_str()); + degate_image_format = true; + return; + } // If the image is a multi-page/multi-res, we take the page with the biggest resolution. if (reader.imageCount() > 1) @@ -125,32 +138,51 @@ namespace degate struct timespec now{}; GET_CLOCK(now); - // If update_current, then update and set a black loading tile - if (update_current) + if (degate_image_format == false) { - TileCache::cache[filename] = std::make_pair(loading_tile, now);; - TileCache::current_tile = loading_tile; - TileCache::curr_tile_num_x = x; - TileCache::curr_tile_num_y = y; + // Check loading type + if (TileCache::loading_type == TileLoadingType::Async) + { + // If update_current, then update and set a black loading tile + if (update_current) + { + TileCache::cache[filename] = std::make_pair(loading_tile, now);; + TileCache::current_tile = loading_tile; + TileCache::curr_tile_num_x = x; + TileCache::curr_tile_num_y = y; + } + + // Run in another thread the loading phase of the new tile + QtConcurrent::run([=]() + { + // Convert to cache type + auto data = std::make_pair(load(x, y), now); + + // Register the new entry and send notifications + TileCache::mutex.lock(); + TileCache::cache[filename] = data; + TileCache::notify(); + TileCache::mutex.unlock(); + }); + + // Show the loading tile while waiting for the next update to try to load the real tile image + return; + } + else + { + // If sync + TileCache::cache[filename] = std::make_pair(load(x, y), now); + } } - - // Run in another thread the loading phase of the new tile - QtConcurrent::run([=]() + else { - auto data = std::make_pair(load(x, y), now); - TileCache::mutex.lock(); - TileCache::cache[filename] = data; - WorkspaceNotifier::get_instance().notify(WorkspaceTarget::WorkspaceBackground, WorkspaceNotification::Update); - WorkspaceNotifier::get_instance().notify(WorkspaceTarget::Workspace, WorkspaceNotification::Draw); - TileCache::mutex.unlock(); - }); + // Async loading not supported for degate image format (memory map) + TileCache::cache[filename] = std::make_pair(load_degate_image_format(filename), now); + } #ifdef TILECACHE_DEBUG gtc.print_table(); #endif - - // Show the loading tile while waiting for the next update to try to load the real tile image - return; } // Get current time @@ -243,6 +275,27 @@ namespace degate return mem; } + /** + * Load image in degate internal format. + * + * @param filename : just the name of the file to load. The filename is + * relative to the path. + */ + inline + std::shared_ptr> + load_degate_image_format(std::string const& filename) + { + std::shared_ptr> mem( + new MemoryMap( + uint_fast64_t(1) << TileCache::tile_width_exp, + uint_fast64_t(1) << TileCache::tile_width_exp, + MAP_STORAGE_TYPE_PERSISTENT_FILE, + join_pathes(TileCache::path, filename))); + + return mem; + } + + private: QSize size; @@ -250,6 +303,7 @@ namespace degate unsigned int tile_size; std::string path; int best_image_number = -1; + bool degate_image_format = false; std::shared_ptr> loading_tile; }; diff --git a/src/Core/Image/TileCacheFactory.h b/src/Core/Image/TileCacheFactory.h index d859f61b..bb798034 100644 --- a/src/Core/Image/TileCacheFactory.h +++ b/src/Core/Image/TileCacheFactory.h @@ -47,22 +47,27 @@ namespace degate * @param scale : the scale to apply when loading the image (e.g. scale = 2 * will load the image with final size of width/2 and height/2). * @see ScalingManager. + * @param loading_type : the loading type to use when loading a new tile. + * @param notification_list : the list of workspace notification(s) to notify + * after a new loading finished. This is done only if async loading type. */ template inline std::shared_ptr> generate(std::string path, unsigned int tile_width_exp, - unsigned int scale = 1) + unsigned int scale, + TileLoadingType loading_type, + const WorkspaceNotificationList& notification_list) { switch (project_type) { case ProjectType::Normal: - return std::make_shared>(path, tile_width_exp, scale); + return std::make_shared>(path, tile_width_exp, scale, loading_type, notification_list); break; case ProjectType::Attached: - return std::make_shared>(path, tile_width_exp, scale); + return std::make_shared>(path, tile_width_exp, scale, loading_type, notification_list); break; default: - return std::make_shared>(path, tile_width_exp); + return std::make_shared>(path, tile_width_exp, scale, loading_type, notification_list); break; } } diff --git a/src/Core/Image/TileCacheStore.h b/src/Core/Image/TileCacheStore.h index fa546983..7aa47f54 100644 --- a/src/Core/Image/TileCacheStore.h +++ b/src/Core/Image/TileCacheStore.h @@ -67,9 +67,16 @@ namespace degate * @param scale : the scale to apply when loading the image (e.g. scale = 2 * will load the image with final size of width/2 and height/2). * @see ScalingManager. Not used here since images are already imported/scaled. + * @param loading_type : the loading type to use when loading a new tile. + * @param notification_list : the list of workspace notification(s) to notify + * after a new loading finished. This is done only if async loading type. */ - TileCacheStore(std::string path, unsigned int tile_width_exp, unsigned int scale = 1) - : TileCache(path, tile_width_exp, scale) + TileCacheStore(std::string path, + unsigned int tile_width_exp, + unsigned int scale, + TileLoadingType loading_type, + const WorkspaceNotificationList& notification_list) + : TileCache(path, tile_width_exp, scale, loading_type, notification_list) { } diff --git a/src/Core/Image/TileImage.h b/src/Core/Image/TileImage.h index 406308ac..98b1780a 100644 --- a/src/Core/Image/TileImage.h +++ b/src/Core/Image/TileImage.h @@ -26,7 +26,6 @@ #include "Globals.h" #include "PixelPolicies.h" #include "StoragePolicies.h" -#include "TileCacheStore.h" #include "Core/Image/TileCacheFactory.h" namespace degate @@ -105,18 +104,23 @@ namespace degate * value is specified as an exponent to the base 2. This means for * example that if you want to use a width of 1024 pixel, you have * to give a value of 10, because 2^10 is 1024. + * @param loading_type : the loading type to use when loading a new tile. + * @param notification_list : the list of workspace notification(s) to notify + * after a new loading finished. This is done only if async loading type. */ StoragePolicy_Tile(unsigned int width, unsigned int height, std::string const& path, bool persistent = false, unsigned int scale = 1, - unsigned int tile_width_exp = 10) + unsigned int tile_width_exp = 10, + TileLoadingType loading_type = TileLoadingType::Sync, + const WorkspaceNotificationList& notification_list = {}) : persistent(persistent), tile_width_exp(tile_width_exp), offset_bitmask((1 << tile_width_exp) - 1), path(path), - tile_cache(TileCacheFactory::get_instance().generate(path, tile_width_exp, scale)), + tile_cache(TileCacheFactory::get_instance().generate(path, tile_width_exp, scale, loading_type, notification_list)), width(width), height(height) { diff --git a/src/Core/Project/ProjectImporter.cc b/src/Core/Project/ProjectImporter.cc index f85d9425..f458c58c 100644 --- a/src/Core/Project/ProjectImporter.cc +++ b/src/Core/Project/ProjectImporter.cc @@ -221,7 +221,18 @@ void ProjectImporter::load_background_image(const Layer_shptr& layer, // And then, just have to set the layer base image (scaling default = 1) if (prj->get_project_type() == ProjectType::Attached) { - BackgroundImage_shptr bg_image = std::make_shared(layer->get_width(), layer->get_height(), image_filename); + BackgroundImage_shptr bg_image = std::make_shared( + layer->get_width(), + layer->get_height(), + image_filename, + true, + 1, + 10, + TileLoadingType::Async, + WorkspaceNotificationList{{WorkspaceTarget::WorkspaceBackground, WorkspaceNotification::Update}, + {WorkspaceTarget::Workspace, WorkspaceNotification::Draw}}); + + // Set the image to the layer layer->set_image(bg_image); return; diff --git a/src/GUI/Workspace/WorkspaceNotifier.h b/src/GUI/Workspace/WorkspaceNotifier.h index f0a6d43b..be7cad73 100644 --- a/src/GUI/Workspace/WorkspaceNotifier.h +++ b/src/GUI/Workspace/WorkspaceNotifier.h @@ -29,6 +29,8 @@ #include #include +#include +#include namespace degate { @@ -52,6 +54,16 @@ namespace degate Draw }; + /** + * A type that define a workspace notification list. + */ + using WorkspaceNotificationVector = std::vector>; + + /** + * A type that define init values for a workspace notification list. + */ + using WorkspaceNotificationList = std::initializer_list>; + /** * @class WorkspaceNotifier * @brief Singleton used to notify a workspace class (like for an update to perform). From b62b8fc5bef966f5ac9c086f6247f6371513a93d Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Mon, 20 Dec 2021 18:19:30 +0100 Subject: [PATCH 05/19] Added a way to change the paths of background images for attached mode. --- src/Core/Project/ProjectImporter.cc | 51 +++++++++++++++++++++++++++++ src/GUI/MainWindow.cc | 13 +++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/Core/Project/ProjectImporter.cc b/src/Core/Project/ProjectImporter.cc index f458c58c..01166e3b 100644 --- a/src/Core/Project/ProjectImporter.cc +++ b/src/Core/Project/ProjectImporter.cc @@ -25,6 +25,9 @@ #include "Core/RuleCheck/RCVBlacklistImporter.h" #include "Core/LogicModel/LogicModelHelper.h" +#include +#include + #include #include #include @@ -221,6 +224,53 @@ void ProjectImporter::load_background_image(const Layer_shptr& layer, // And then, just have to set the layer base image (scaling default = 1) if (prj->get_project_type() == ProjectType::Attached) { + bool can_load = true; + + // Check if the image exists + if (!file_exists(image_filename)) + can_load = false; + + // Check if the image is readeable + QImageReader reader(image_filename.c_str()); + if (!reader.canRead()) + can_load = false; + + // If cannot load/read + if (!can_load) + { + if (qApp->thread() != QThread::currentThread()) + throw std::runtime_error("Can't find this image path for project with attached mode: '" + image_filename + "'."); + + // Convert old filename + auto old_filename = QString::fromStdString(image_filename); + + // Show error + auto title = QCoreApplication::translate("ProjectImporter", "An image path was not found."); + auto warning = QCoreApplication::translate("ProjectImporter", + "You are loading a project with attached mode, and a background " + "image path was not found, please select the new path for: ", + "... [old path]") + + "'" + old_filename + "'"; + auto res = QMessageBox::warning(nullptr, title, warning, QMessageBox::Ok | QMessageBox::Cancel); + + // If cancel, just throw + if (res == QMessageBox::Cancel) + throw std::runtime_error("Can't find this image path for project with attached mode: '" + image_filename + "'."); + + // Show file picked + auto text = + QCoreApplication::translate("ProjectImporter", "Select the new path for: ", "... [old path]") + + "'" + old_filename + "'"; + std::string filepath = QFileDialog::getOpenFileName(nullptr, text).toStdString(); + + // Re-enter this function + load_background_image(layer, filepath, prj); + + // Finish here + return; + } + + // Create the image BackgroundImage_shptr bg_image = std::make_shared( layer->get_width(), layer->get_height(), @@ -235,6 +285,7 @@ void ProjectImporter::load_background_image(const Layer_shptr& layer, // Set the image to the layer layer->set_image(bg_image); + // Finish here return; } diff --git a/src/GUI/MainWindow.cc b/src/GUI/MainWindow.cc index a6f10901..44c3411c 100644 --- a/src/GUI/MainWindow.cc +++ b/src/GUI/MainWindow.cc @@ -1485,6 +1485,8 @@ namespace degate try { std::shared_ptr imported_project = nullptr; + + // First try, in worker thread progress_dialog.set_job([&] { try @@ -1501,9 +1503,18 @@ namespace degate }); progress_dialog.exec(); + // Second try, in the main thread if (error) { - throw std::runtime_error(error_message.c_str()); + try + { + imported_project = project_importer.import_all(path); + } + catch (const std::exception& e) + { + // If fail, just throw and cancel project opening + throw std::runtime_error(error_message.c_str()); + } } if (imported_project == nullptr) From c455e1689648fab10cc733eb6930bbcd728f10e4 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Tue, 21 Dec 2021 18:36:56 +0100 Subject: [PATCH 06/19] Added a new exception handler in main. --- src/Main.cc | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Main.cc b/src/Main.cc index f09e795c..bbf6fb25 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -29,6 +29,44 @@ #include "Core/Version.h" #include "Core/Utils/CrashReport.h" +/** + * @class Degate + * @brief Implementation of QApplication. + * + * Useful to catch exceptions. + */ +class Degate final : public QApplication +{ +public: + + /** + * Constructor, @see QApplication. + * + * Only one QApplication can exist, accessible through QApplication::. + */ + Degate(int& argc, char** argv) : QApplication(argc, argv) + { + } + + /** + * Reimplement notify() to catch exceptions. + */ + virtual bool notify(QObject* receiver, QEvent* event) override + { + try + { + return QApplication::notify(receiver, event); + } + catch (const std::exception& e) + { + degate::crash_report(e.what()); + exit(EXIT_FAILURE); + } + + return false; + } +}; + int main(int argc, char* argv[]) { int ret = 0; @@ -43,25 +81,28 @@ int main(int argc, char* argv[]) QSurfaceFormat::setDefaultFormat(format); // Create Qt application - QApplication a(argc, argv); + Degate app(argc, argv); // Bind signals std::signal(SIGSEGV, [](int signal) { std::string error = "A SIGSEGV occurred (" + std::to_string(signal) + ")."; degate::crash_report(error); QApplication::closeAllWindows(); + exit(EXIT_FAILURE); }); std::signal(SIGFPE, [](int signal) { std::string error = "A SIGFPE occurred (" + std::to_string(signal) + ")."; degate::crash_report(error); QApplication::closeAllWindows(); + exit(EXIT_FAILURE); }); std::signal(SIGILL, [](int signal) { std::string error = "A SIGILL occurred (" + std::to_string(signal) + ")."; degate::crash_report(error); QApplication::closeAllWindows(); + exit(EXIT_FAILURE); }); try From fcc982871ce177dbe6a6c1f57ccf1ecbe999ac66 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Tue, 21 Dec 2021 18:40:34 +0100 Subject: [PATCH 07/19] Fixed a bug with layer edit widget. --- src/GUI/Widget/LayersEditWidget.cc | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/GUI/Widget/LayersEditWidget.cc b/src/GUI/Widget/LayersEditWidget.cc index a0a38260..e643a100 100644 --- a/src/GUI/Widget/LayersEditWidget.cc +++ b/src/GUI/Widget/LayersEditWidget.cc @@ -400,16 +400,28 @@ namespace degate } else { - // If attached project type, then just create the background image without loading - - BackgroundImage_shptr bg_image = std::make_shared(layer->get_width(), layer->get_height(), background->get_image_path()); - layer->set_image(bg_image); + if (background->has_new_image()) + { + debug(TM, "Background image path: %s", background->get_image_path()); + + // If attached project type, then just create the background image without loading + BackgroundImage_shptr bg_image = std::make_shared( + layer->get_width(), + layer->get_height(), + background->get_image_path(), + true, + 1, + 10, + TileLoadingType::Async, + WorkspaceNotificationList{{WorkspaceTarget::WorkspaceBackground, WorkspaceNotification::Update}, + {WorkspaceTarget::Workspace, WorkspaceNotification::Draw}}); + + // Set the image to the layer + layer->set_image(bg_image); + } } - - // Position layer->set_layer_pos(i); - layer_collection.push_back(layer); } From e1863ddecc343583475b15d2d47c03d4aad4c8b9 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Tue, 21 Dec 2021 18:42:03 +0100 Subject: [PATCH 08/19] Fixed scaling management for attached mode. --- src/Core/Image/Manipulation/ScalingManager.h | 31 ++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Core/Image/Manipulation/ScalingManager.h b/src/Core/Image/Manipulation/ScalingManager.h index 6ee1b995..cdbf6801 100644 --- a/src/Core/Image/Manipulation/ScalingManager.h +++ b/src/Core/Image/Manipulation/ScalingManager.h @@ -143,13 +143,32 @@ namespace degate */ void create_scalings() { + // Get base image + std::shared_ptr last_img = images[1]; + unsigned int w = 0; + unsigned int h = 0; + // If normal mode, then check if directory exists - if (!(file_exists(base_directory) && is_directory(base_directory)) && project_type == ProjectType::Normal) - throw InvalidPathException("The path for prescaled images must exist. But it is not there."); + if (project_type == ProjectType::Normal) + { + if (!(file_exists(base_directory) && is_directory(base_directory))) + throw InvalidPathException("The path for prescaled images must exist. But it is not there."); - std::shared_ptr last_img = images[1]; - unsigned int w = last_img->get_width(); - unsigned int h = last_img->get_height(); + w = last_img->get_width(); + h = last_img->get_height(); + } + else + { + QImageReader reader(last_img->get_path().c_str()); + + // Size + auto size = reader.size(); + if (!size.isValid()) + throw std::runtime_error("Can't read size of " + last_img->get_path()); + + w = size.width(); + h = size.height(); + } // Create scalings for (int i = 2; ((h > min_size) || (w > min_size)) && (i < (1 << 24)); // max 24 scaling levels @@ -193,6 +212,8 @@ namespace degate } else { + // If attached, pass the image path directly + // @see TileCache to understand path = images[1]->get_path(); // If attached, load async and notify workspace + background From 855e47050dd94a117826117e1089e8a11a21589d Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Tue, 21 Dec 2021 18:42:49 +0100 Subject: [PATCH 09/19] Massive clean of the attached mode and tile cache code. --- src/Core/Image/Image.h | 8 + src/Core/Image/TileCache.h | 378 +++++++++++++++++++++++++---- src/Core/Image/TileCacheAttached.h | 312 ------------------------ src/Core/Image/TileCacheFactory.h | 99 -------- src/Core/Image/TileCacheStore.h | 157 ------------ src/Core/Image/TileImage.h | 10 +- src/Core/Project/Project.cc | 4 - 7 files changed, 351 insertions(+), 617 deletions(-) delete mode 100644 src/Core/Image/TileCacheAttached.h delete mode 100644 src/Core/Image/TileCacheFactory.h delete mode 100644 src/Core/Image/TileCacheStore.h diff --git a/src/Core/Image/Image.h b/src/Core/Image/Image.h index 586246d4..8c3dd1cb 100644 --- a/src/Core/Image/Image.h +++ b/src/Core/Image/Image.h @@ -218,6 +218,10 @@ namespace degate /** * Constructor for temporary virtual images. * + * It supports two types of tile loading depending on the type of the image. + * This is the main point of difference between Attached and Normal project modes. + * @see TileCache. + * * @param width : the width of the image. * @param height : the height of the image. * @param scale : the scale to apply when loading the image (e.g. scale = 2 @@ -251,6 +255,10 @@ namespace degate /** * Constructor for persistent virtual images. * + * It supports two types of tile loading depending on the type of the image. + * This is the main point of difference between Attached and Normal project modes. + * @see TileCache. + * * @param width : the width of the image. * @param height : the height of the image. * @param path : the path of the image, can be a directory (store mode) or diff --git a/src/Core/Image/TileCache.h b/src/Core/Image/TileCache.h index d9cbb02f..b55bff33 100644 --- a/src/Core/Image/TileCache.h +++ b/src/Core/Image/TileCache.h @@ -26,6 +26,12 @@ #include "Core/Image/TileCacheBase.h" #include "Core/Image/GlobalTileCache.h" #include "GUI/Workspace/WorkspaceNotifier.h" +#include "Core/Utils/FileSystem.h" +#include "Core/Utils/MemoryMap.h" + +#include +#include +#include namespace degate { @@ -41,7 +47,12 @@ namespace degate /** * @class TileCache - * @brief Tile cache base class (interface). + * @brief Tile cache class. + * + * It supports two types of tile loading depending on the type of the image. + * If it's in Degate's internal format, then it will use memory mapping from + * file. Otherwise, it will dynamically load tiles in memory. + * This is the main point of difference between Attached and Normal project modes. */ template class TileCache : public TileCacheBase @@ -53,6 +64,11 @@ namespace degate /** * Create a new tile cache. * + * It supports two types of tile loading depending on the type of the image. + * If it's in Degate's internal format, then it will use memory mapping from + * file. Otherwise, it will dynamically load tiles in memory. + * This is the main point of difference between Attached and Normal project modes. + * * @param path : the path to the image (can be to a file or a path for example). * @param tile_width_exp : the width (and height) for image tiles. This * value is specified as an exponent to the base 2. This means for @@ -62,6 +78,7 @@ namespace degate * will load the image with final size of width/2 and height/2). * @see ScalingManager. * @param loading_type : the loading type to use when loading a new tile. + * If using Degate's image format, only sync is supported. * @param notification_list : the list of workspace notification(s) to notify * after a new loading finished. This is done only if async loading type. */ @@ -74,8 +91,53 @@ namespace degate tile_width_exp(tile_width_exp), scale(scale), loading_type(loading_type), - notification_list(notification_list) + notification_list(notification_list), + tile_size(1 << tile_width_exp) { + // Check if Degate's image format + QImageReader reader(this->path.c_str()); + if (reader.canRead() == false) + { + if (!is_directory(this->path)) + throw std::runtime_error("Unsupported image format for image: " + this->path); + + //debug(TM, "Degate image format: %s", this->path.c_str()); + degate_image_format = true; + return; + } + + // If the image is a multi-page/multi-res, we take the page with the biggest resolution. + if (reader.imageCount() > 1) + { + QSize best_size{0, 0}; + for (int i = 0; i < reader.imageCount(); i++) + { + if (best_size.width() < reader.size().width() || best_size.height() < reader.size().height()) + { + best_size = reader.size(); + best_image_number = reader.currentImageNumber(); + } + + reader.jumpToNextImage(); + } + + reader.jumpToImage(best_image_number); + } + + // Size + size = reader.size(); + if (!size.isValid()) + { + debug(TM, "Can't read size of %s.\n", this->path.c_str()); + return; + } + + // Scaled size conversion + auto h = static_cast(round(log(scale) / log(2))); + scaled_size = QSize{size.width() >> h, size.height() >> h}; + + // Create loading tile + loading_tile = std::make_shared>(tile_size, tile_size); } /** @@ -91,17 +153,17 @@ namespace degate */ inline void cleanup_cache() override { - if (TileCache::cache.size() == 0) return; + if (cache.size() == 0) return; // Initialize a clock to store the oldest struct timespec oldest_clock_val; GET_CLOCK(oldest_clock_val); - auto oldest = TileCache::cache.begin(); + auto oldest = cache.begin(); // Search for oldest entry - for (auto iter = TileCache::cache.begin(); - iter != TileCache::cache.end(); ++iter) + for (auto iter = cache.begin(); + iter != cache.end(); ++iter) { struct timespec clock_val = (*iter).second.second; if (clock_val < oldest_clock_val) @@ -112,39 +174,39 @@ namespace degate } } - assert(oldest != TileCache::cache.end()); + assert(oldest != cache.end()); // Release memory (*oldest).second.first.reset(); // explicit reset of smart pointer // Clean the cache entry - TileCache::cache.erase(oldest); + cache.erase(oldest); #ifdef TILECACHE_DEBUG - debug(TM, "local cache: %d entries after remove\n", TileCache::cache.size()); + debug(TM, "local cache: %d entries after remove\n", cache.size()); #endif // Update the global tile cache (release the virtual memory) GlobalTileCache& gtc = GlobalTileCache::get_instance(); - gtc.release_cache_memory(this, TileCache::get_image_size()); + gtc.release_cache_memory(this, get_image_size()); } /** * Release all the memory. */ - inline virtual void release_memory() + inline void release_memory() { - if (TileCache::cache.size() > 0) + if (cache.size() > 0) { std::lock_guard lock(mutex); // Release the global tile cache (by removing all the used virtual memory by this) GlobalTileCache& gtc = GlobalTileCache::get_instance(); - gtc.release_cache_memory(this, TileCache::cache.size() * TileCache::get_image_size()); + gtc.release_cache_memory(this, cache.size() * get_image_size()); // Release the memory - TileCache::current_tile.reset(); - TileCache::cache.clear(); + current_tile.reset(); + cache.clear(); } } @@ -153,11 +215,11 @@ namespace degate */ inline void print() const override { - for (typename TileCache::cache_type::const_iterator iter = TileCache::cache.begin(); - iter != TileCache::cache.end(); ++iter) + for (typename cache_type::const_iterator iter = cache.begin(); + iter != cache.end(); ++iter) { std::cout << "\t+ " - << TileCache::path << "/" + << path << "/" << (*iter).first << " " << (*iter).second.second.tv_sec << "/" @@ -177,21 +239,21 @@ namespace degate * @param max_size_y : max possible y coordinate for the rect. * @param radius : radius around the rect to cache (in number of tile unit). */ - inline virtual void cache_around(unsigned int min_x, - unsigned int max_x, - unsigned int min_y, - unsigned int max_y, - unsigned int max_size_x, - unsigned int max_size_y, - unsigned int radius) + inline void cache_around(unsigned int min_x, + unsigned int max_x, + unsigned int min_y, + unsigned int max_y, + unsigned int max_size_x, + unsigned int max_size_y, + unsigned int radius) { - unsigned int tile_num_min_x = min_x >> TileCache::tile_width_exp; - unsigned int tile_num_max_x = max_x >> TileCache::tile_width_exp; - unsigned int tile_num_min_y = min_y >> TileCache::tile_width_exp; - unsigned int tile_num_max_y = max_y >> TileCache::tile_width_exp; + unsigned int tile_num_min_x = min_x >> tile_width_exp; + unsigned int tile_num_max_x = max_x >> tile_width_exp; + unsigned int tile_num_min_y = min_y >> tile_width_exp; + unsigned int tile_num_max_y = max_y >> tile_width_exp; - unsigned int tile_num_max_size_x = max_size_x >> TileCache::tile_width_exp; - unsigned int tile_num_max_size_y = max_size_y >> TileCache::tile_width_exp; + unsigned int tile_num_max_size_x = max_size_x >> tile_width_exp; + unsigned int tile_num_max_size_y = max_size_y >> tile_width_exp; unsigned int cache_min_x = radius > tile_num_min_x ? 0 : tile_num_min_x - radius; unsigned int cache_max_x = radius + tile_num_max_x > tile_num_max_size_x ? tile_num_max_size_x : tile_num_max_x + radius; @@ -200,7 +262,7 @@ namespace degate if (static_cast(cache_max_x - cache_min_x) * static_cast(cache_max_y - cache_min_y) * - static_cast(TileCache::get_image_size()) * + static_cast(get_image_size()) * static_cast(sizeof(typename PixelPolicy::pixel_type)) > GlobalTileCache::get_instance().get_max_cache_memory()) { debug(TM, "Cache too small to cache around"); @@ -233,12 +295,10 @@ namespace degate unsigned int tile_num_x = x >> tile_width_exp; unsigned int tile_num_y = y >> tile_width_exp; - // This is an optimisation, but don't fit well for attached mode - // TODO: When notifier for new loaded tile (attached mode) readd this - //if (!(current_tile != nullptr && tile_num_x == curr_tile_num_x && tile_num_y == curr_tile_num_y)) - //{ + if (!(current_tile != nullptr && tile_num_x == curr_tile_num_x && tile_num_y == curr_tile_num_y) || current_tile_is_loading) + { load_tile(tile_num_x, tile_num_y, true); - //} + } return current_tile; } @@ -250,7 +310,103 @@ namespace degate * @param y : the y index of the tile (not the real coordinate). * @param update_current : if true, will update the current_tile pointer, otherwise not. */ - virtual void load_tile(unsigned int x, unsigned int y, bool update_current = false) = 0; + inline void load_tile(unsigned int x, unsigned int y, bool update_current = false) + { + // Check if tile is included in the base image + // Otherwise return loading tile + if (!is_included(x, y)) + { + if (update_current) + { + current_tile = loading_tile; + curr_tile_num_x = x; + curr_tile_num_y = y; + current_tile_is_loading = false; + } + + return; + } + + // Get time + struct timespec now{}; + GET_CLOCK(now); + + // Create a file name from tile number + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%d_%d.dat", x, y); + + // If filename/object is not in cache, load the tile + typename cache_type::const_iterator iter = cache.find(filename); + + // If the tile was not found in the cache, then load it + if (iter == cache.end()) + { + GlobalTileCache& gtc = GlobalTileCache::get_instance(); + + // Allocate memory (global tile cache) + bool ok = gtc.request_cache_memory(this, get_image_size()); + assert(ok == true); + + if (degate_image_format == false) + { + // Check loading type + if (loading_type == TileLoadingType::Async) + { + // If update_current, then update and set a black loading tile + if (update_current) + { + cache[filename] = std::make_pair(loading_tile, now);; + current_tile = loading_tile; + curr_tile_num_x = x; + curr_tile_num_y = y; + current_tile_is_loading = true; + } + + // Run in another thread the loading phase of the new tile + QtConcurrent::run([=]() + { + // Convert to cache type + auto data = std::make_pair(load(x, y), now); + + // Register the new entry and send notifications + mutex.lock(); + cache[filename] = data; + notify(); + mutex.unlock(); + }); + + // Show the loading tile while waiting for the next update to try to load the real tile image + return; + } + else + { + // If sync + cache[filename] = std::make_pair(load(x, y), now); + } + } + else + { + // Async loading not supported for degate image format (memory map) + cache[filename] = std::make_pair(load_degate_image_format(filename), now); + } + + #ifdef TILECACHE_DEBUG + gtc.print_table(); + #endif + } + + // Update entry + auto entry = cache[filename]; + entry.second = now; + + if (update_current) + { + current_tile = entry.first; + curr_tile_num_x = x; + curr_tile_num_y = y; + current_tile_is_loading = false; + } + } protected: @@ -271,7 +427,136 @@ namespace degate WorkspaceNotifier::get_instance().notify(notification.first, notification.second); } - protected: + /** + * Load a tile (attached mode/on degate's image format). + * + * @param tile_x : the tile first coordinate (first index). + * @param tile_y : the tile second coordinate (second index). + * + * @return Returns the new memory map (here, for attached mod, just memory). + */ + inline + std::shared_ptr> + load(unsigned int tile_x, unsigned int tile_y) + { + // Prepare sizes + QSize reading_size{static_cast(tile_size), static_cast(tile_size)}; + const QSize read_size{static_cast(tile_x) * static_cast(tile_size), static_cast(tile_y) * static_cast(tile_size)}; + + // Check width + if (reading_size.width() + read_size.width() > scaled_size.width()) + reading_size.setWidth(scaled_size.width() - read_size.width()); + + // Check height + if (reading_size.height() + read_size.height() > scaled_size.height()) + reading_size.setHeight(scaled_size.height() - read_size.height()); + + // Check overflow + if (reading_size.width() <= 0 || reading_size.height() <= 0) + return loading_tile; + + // Create reader + QImageReader current_reader(path.c_str()); + + // Create reading rect + QRect rect(read_size.width(), read_size.height(), reading_size.width(), reading_size.height()); + + // If the image is a multi-page/multi-res, we take the page with the biggest resolution + if (current_reader.imageCount() > 1) + current_reader.jumpToImage(best_image_number); + + // Scaled read + current_reader.setScaledSize(scaled_size); + current_reader.setScaledClipRect(rect); + + // Set reader reading rect + QImage img = current_reader.read(); + if (img.isNull()) + { + debug(TM, "can't read image file when loading a new tile\n"); + } + + // Convert to good format + if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_RGB32) + { + img = img.convertToFormat(QImage::Format_ARGB32); + } + + // Get data + const auto *rgb_data = reinterpret_cast(&img.constBits()[0]); + + // Create memory map + auto mem = std::make_shared>(tile_size, tile_size); + + // Prevent overflows + unsigned int max_x = tile_size > static_cast(reading_size.width()) ? + static_cast(reading_size.width()) : tile_size; + unsigned int max_y = tile_size > static_cast(reading_size.height()) ? + static_cast(reading_size.height()) : tile_size; + + // Fill data + QRgb rgb; + for (unsigned int y = 0; y < max_y; y++) + { + for (unsigned int x = 0; x < max_x; x++) + { + rgb = rgb_data[y * reading_size.width() + x]; + mem->set(x, y, MERGE_CHANNELS(qRed(rgb), qGreen(rgb), qBlue(rgb), qAlpha(rgb))); + } + } + + return mem; + } + + /** + * Load image in degate internal format. + * + * @param filename : just the name of the file to load. The filename is + * relative to the path. + * + * @return Returns a memory map (mapped to the corresponding tile file). + */ + inline + std::shared_ptr> + load_degate_image_format(std::string const& filename) + { + std::shared_ptr> mem( + new MemoryMap( + uint_fast64_t(1) << tile_width_exp, + uint_fast64_t(1) << tile_width_exp, + MAP_STORAGE_TYPE_PERSISTENT_FILE, + join_pathes(path, filename))); + + return mem; + } + + /** + * Check if the tile(x,y) is included in the base image (don't work for degate image format). + * + * @param tile_x : the tile first coordinate (first index). + * @param tile_y : the tile second coordinate (second index). + */ + inline bool is_included(unsigned int tile_x, unsigned int tile_y) + { + // For historic reasons, Degate's image format will take the project + // size as image size (even if the real size is less). Also, the + // project size is equal to the biggest image size, so no overflow + // possible. Therefore, always return true in this case. + if (degate_image_format) + return true; + + // Check width + if (static_cast(tile_x) * static_cast(tile_size) >= scaled_size.width()) + return false; + + // Check height + if (static_cast(tile_y) * static_cast(tile_size) >= scaled_size.height()) + return false; + + return true; + } + + private: const std::string path; const unsigned int tile_width_exp; @@ -284,9 +569,10 @@ namespace degate cache_type cache; // Used for caching the working tile. - mutable MemoryMap_shptr current_tile; - mutable unsigned curr_tile_num_x = 0; - mutable unsigned curr_tile_num_y = 0; + MemoryMap_shptr current_tile; + unsigned curr_tile_num_x = 0; + unsigned curr_tile_num_y = 0; + bool current_tile_is_loading = false; unsigned int scale; @@ -294,6 +580,14 @@ namespace degate TileLoadingType loading_type; WorkspaceNotificationVector notification_list; + + QSize size; + QSize scaled_size; + unsigned int tile_size; + int best_image_number = -1; + bool degate_image_format = false; + + std::shared_ptr> loading_tile; }; } // namespace degate diff --git a/src/Core/Image/TileCacheAttached.h b/src/Core/Image/TileCacheAttached.h deleted file mode 100644 index 1894d920..00000000 --- a/src/Core/Image/TileCacheAttached.h +++ /dev/null @@ -1,312 +0,0 @@ -/** - * This file is part of the IC reverse engineering tool Degate. - * - * Copyright 2008, 2009, 2010 by Martin Schobert - * Copyright 2019-2020 Dorian Bachelot - * - * Degate is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * Degate is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with degate. If not, see . - * - */ - -#ifndef __TILECACHEATTACHED_H__ -#define __TILECACHEATTACHED_H__ - -#include "Core/Configuration.h" -#include "Core/Image/GlobalTileCache.h" -#include "Core/Utils/FileSystem.h" -#include "Core/Utils/MemoryMap.h" -#include "Core/Image/TileCache.h" - -#include -#include -#include - -namespace degate -{ - /** - * @class TileCacheAttached - * @brief Tile cache for attached/linked images. - */ - template - class TileCacheAttached : public TileCache - { - friend class GlobalTileCache; - - public: - - /** - * Create a new Tile cache with attached/linked image. - * - * @param path : the path to the image. - * @param tile_width_exp : the width (and height) for image tiles. This - * value is specified as an exponent to the base 2. This means for - * example that if you want to use a width of 1024 pixel, you have - * to give a value of 10, because 2^10 is 1024. - * @param scale : the scale to apply when loading the image (e.g. scale = 2 - * will load the image with final size of width/2 and height/2). - * @see ScalingManager. - * @param loading_type : the loading type to use when loading a new tile. - * @param notification_list : the list of workspace notification(s) to notify - * after a new loading finished. This is done only if async loading type. - */ - inline TileCacheAttached(std::string path, - unsigned int tile_width_exp, - unsigned int scale, - TileLoadingType loading_type, - const WorkspaceNotificationList& notification_list) - : TileCache(path, tile_width_exp, scale, loading_type, notification_list), - tile_size(1 << tile_width_exp), - path(path) - { - QImageReader reader(path.c_str()); - if (reader.canRead() == false) - { - debug(TM, "Degate image format: %s", path.c_str()); - degate_image_format = true; - return; - } - - // If the image is a multi-page/multi-res, we take the page with the biggest resolution. - if (reader.imageCount() > 1) - { - QSize best_size{0, 0}; - for (int i = 0; i < reader.imageCount(); i++) - { - if (best_size.width() < reader.size().width() || best_size.height() < reader.size().height()) - { - best_size = reader.size(); - best_image_number = reader.currentImageNumber(); - } - - reader.jumpToNextImage(); - } - - reader.jumpToImage(best_image_number); - } - - // Size - size = reader.size(); - if (!size.isValid()) - { - debug(TM, "can't read size of %s\n", path.c_str()); - return; - } - - // Scaled size conversion - auto h = static_cast(round(log(scale) / log(2))); - scaled_size = QSize{size.width() >> h, size.height() >> h}; - - // Create loading tile - loading_tile = std::make_shared>(tile_size, tile_size); - } - - /** - * Load a new tile and update the cache. - * - * @param x : the x index of the tile (not the real coordinate). - * @param y : the y index of the tile (not the real coordinate). - * @param update_current : if true, will update the current_tile pointer, otherwise not. - */ - inline void load_tile(unsigned int x, unsigned int y, bool update_current = false) - { - // Create a file name from tile number - char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "%d_%d.dat", x, y); - - // If filename/object is not in cache, load the tile - typename TileCache::cache_type::const_iterator iter = TileCache::cache.find(filename); - - // If the tile was not found in the cache, then load it - if (iter == TileCache::cache.end()) - { - GlobalTileCache& gtc = GlobalTileCache::get_instance(); - - // Allocate memory (global tile cache) - bool ok = gtc.request_cache_memory(this, TileCache::get_image_size()); - assert(ok == true); - struct timespec now{}; - GET_CLOCK(now); - - if (degate_image_format == false) - { - // Check loading type - if (TileCache::loading_type == TileLoadingType::Async) - { - // If update_current, then update and set a black loading tile - if (update_current) - { - TileCache::cache[filename] = std::make_pair(loading_tile, now);; - TileCache::current_tile = loading_tile; - TileCache::curr_tile_num_x = x; - TileCache::curr_tile_num_y = y; - } - - // Run in another thread the loading phase of the new tile - QtConcurrent::run([=]() - { - // Convert to cache type - auto data = std::make_pair(load(x, y), now); - - // Register the new entry and send notifications - TileCache::mutex.lock(); - TileCache::cache[filename] = data; - TileCache::notify(); - TileCache::mutex.unlock(); - }); - - // Show the loading tile while waiting for the next update to try to load the real tile image - return; - } - else - { - // If sync - TileCache::cache[filename] = std::make_pair(load(x, y), now); - } - } - else - { - // Async loading not supported for degate image format (memory map) - TileCache::cache[filename] = std::make_pair(load_degate_image_format(filename), now); - } - - #ifdef TILECACHE_DEBUG - gtc.print_table(); - #endif - } - - // Get current time - struct timespec now{}; - GET_CLOCK(now); - - // Update entry - auto entry = TileCache::cache[filename]; - entry.second = now; - - if (update_current) - { - TileCache::current_tile = entry.first; - TileCache::curr_tile_num_x = x; - TileCache::curr_tile_num_y = y; - } - } - - protected: - - /** - * Load a tile. - */ - inline - std::shared_ptr> - load(unsigned int x, unsigned int y) - { - // Prepare sizes - QSize reading_size{static_cast(tile_size), static_cast(tile_size)}; - QSize read_size{static_cast(x) * static_cast(tile_size), static_cast(y) * static_cast(tile_size)}; - - // Check width - if (reading_size.width() + read_size.width() > scaled_size.width()) - reading_size.setWidth(scaled_size.width() - read_size.width()); - - // Check height - if (reading_size.height() + read_size.height() > scaled_size.height()) - reading_size.setHeight(scaled_size.height() - read_size.height()); - - // Create reader - QImageReader current_reader(path.c_str()); - - // Create reading rect - QRect rect(read_size.width(), read_size.height(), reading_size.width(), reading_size.height()); - - // If the image is a multi-page/multi-res, we take the page with the biggest resolution - if (current_reader.imageCount() > 1) - current_reader.jumpToImage(best_image_number); - - // Scaled read - current_reader.setScaledSize(scaled_size); - current_reader.setScaledClipRect(rect); - - // Set reader reading rect - QImage img = current_reader.read(); - if (img.isNull()) - { - debug(TM, "can't read image file when loading a new tile\n"); - } - - // Convert to good format - if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_RGB32) - { - img = img.convertToFormat(QImage::Format_ARGB32); - } - - // Get data - const auto *rgb_data = reinterpret_cast(&img.constBits()[0]); - - // Create memory map - auto mem = std::make_shared>(tile_size, tile_size); - - // Prevent overflows - unsigned int max_x = tile_size > static_cast(reading_size.width()) ? - static_cast(reading_size.width()) : tile_size; - unsigned int max_y = tile_size > static_cast(reading_size.height()) ? - static_cast(reading_size.height()) : tile_size; - - // Fill data - QRgb rgb; - for (unsigned int y = 0; y < max_y; y++) - { - for (unsigned int x = 0; x < max_x; x++) - { - rgb = rgb_data[y * reading_size.width() + x]; - mem->set(x, y, MERGE_CHANNELS(qRed(rgb), qGreen(rgb), qBlue(rgb), qAlpha(rgb))); - } - } - - return mem; - } - - /** - * Load image in degate internal format. - * - * @param filename : just the name of the file to load. The filename is - * relative to the path. - */ - inline - std::shared_ptr> - load_degate_image_format(std::string const& filename) - { - std::shared_ptr> mem( - new MemoryMap( - uint_fast64_t(1) << TileCache::tile_width_exp, - uint_fast64_t(1) << TileCache::tile_width_exp, - MAP_STORAGE_TYPE_PERSISTENT_FILE, - join_pathes(TileCache::path, filename))); - - return mem; - } - - - private: - - QSize size; - QSize scaled_size; - unsigned int tile_size; - std::string path; - int best_image_number = -1; - bool degate_image_format = false; - - std::shared_ptr> loading_tile; - }; -} // namespace degate - -#endif diff --git a/src/Core/Image/TileCacheFactory.h b/src/Core/Image/TileCacheFactory.h deleted file mode 100644 index bb798034..00000000 --- a/src/Core/Image/TileCacheFactory.h +++ /dev/null @@ -1,99 +0,0 @@ -/** - * This file is part of the IC reverse engineering tool Degate. - * - * Copyright 2008, 2009, 2010 by Martin Schobert - * Copyright 2021 Dorian Bachelot - * - * Degate is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * Degate is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with degate. If not, see . - * - */ - -#ifndef __TILECACHEFACTORY_H__ -#define __TILECACHEFACTORY_H__ - -#include "Core/Image/TileCacheAttached.h" -#include "Core/Image/TileCacheStore.h" -#include "Core/Primitive/SingletonBase.h" - -namespace degate -{ - /** - * @class TileCacheFactory - * @brief Create appropriete tile cache regarding the type of the current loaded project. - */ - class TileCacheFactory : public SingletonBase - { - public: - - /** - * Generate a new appropriete tile cache. - * - * @param path : is either the image path or the path with saved image in Degate tile image format. - * @param tile_width_exp : the width (and height) for image tiles. This - * value is specified as an exponent to the base 2. This means for - * example that if you want to use a width of 1024 pixel, you have - * to give a value of 10, because 2^10 is 1024. - * @param scale : the scale to apply when loading the image (e.g. scale = 2 - * will load the image with final size of width/2 and height/2). - * @see ScalingManager. - * @param loading_type : the loading type to use when loading a new tile. - * @param notification_list : the list of workspace notification(s) to notify - * after a new loading finished. This is done only if async loading type. - */ - template - inline std::shared_ptr> generate(std::string path, - unsigned int tile_width_exp, - unsigned int scale, - TileLoadingType loading_type, - const WorkspaceNotificationList& notification_list) - { - switch (project_type) - { - case ProjectType::Normal: - return std::make_shared>(path, tile_width_exp, scale, loading_type, notification_list); - break; - case ProjectType::Attached: - return std::make_shared>(path, tile_width_exp, scale, loading_type, notification_list); - break; - default: - return std::make_shared>(path, tile_width_exp, scale, loading_type, notification_list); - break; - } - } - - /** - * Set the project type to use when creating new tile cache. - * - * If the project type change, make sure to destroy all existing - * tile cache and recreate them. - */ - inline void set_project_type(ProjectType project_type) - { - this->project_type = project_type; - } - - /** - * Get the current project type used to generate new tile cache. - */ - inline ProjectType get_project_type() - { - return project_type; - } - - private: - ProjectType project_type = ProjectType::Normal; - }; -} // namespace degate - -#endif //__TILECACHEFACTORY_H__ diff --git a/src/Core/Image/TileCacheStore.h b/src/Core/Image/TileCacheStore.h deleted file mode 100644 index 7aa47f54..00000000 --- a/src/Core/Image/TileCacheStore.h +++ /dev/null @@ -1,157 +0,0 @@ -/** - * This file is part of the IC reverse engineering tool Degate. - * - * Copyright 2008, 2009, 2010 by Martin Schobert - * Copyright 2019-2020 Dorian Bachelot - * - * Degate is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * Degate is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with degate. If not, see . - * - */ - -#ifndef __TILECACHESTORE_H__ -#define __TILECACHESTORE_H__ - -#include "Core/Utils/MemoryMap.h" -#include "Core/Utils/FileSystem.h" -#include "Core/Configuration.h" -#include "Core/Image/TileCache.h" - -#include -#include -#include -#include -#include // for make_pair -#include -#include -#include - -namespace degate -{ - - /** - * The TileCacheStore class handles caching of image tiles. - * - * The implementation keeps track how old the cached tile is. - * If new tiles become loaded, old tiles are removed from the - * cache. You can control the numer of cached tiles via the - * constructor parameter \p _min_cache_tiles. The memory - * requirement is around - * \p _min_cache_tiles*sizeof(PixelPolicy::pixel_type)*(2^_tile_width_exp)^2 , - * where \p sizeof(PixelPolicy::pixel_type) is the size of a pixel. - */ - template - class TileCacheStore : public TileCache - { - - public: - - /** - * Create a TileCacheStore object. - * - * @param path : the path of the path where all the tiles are for a TileImage. - * @param tile_width_exp : the width (and height) for image tiles. This - * value is specified as an exponent to the base 2. This means for - * example that if you want to use a width of 1024 pixel, you have - * to give a value of 10, because 2^10 is 1024. - * @param scale : the scale to apply when loading the image (e.g. scale = 2 - * will load the image with final size of width/2 and height/2). - * @see ScalingManager. Not used here since images are already imported/scaled. - * @param loading_type : the loading type to use when loading a new tile. - * @param notification_list : the list of workspace notification(s) to notify - * after a new loading finished. This is done only if async loading type. - */ - TileCacheStore(std::string path, - unsigned int tile_width_exp, - unsigned int scale, - TileLoadingType loading_type, - const WorkspaceNotificationList& notification_list) - : TileCache(path, tile_width_exp, scale, loading_type, notification_list) - { - } - - /** - * Load a new tile and update the cache. - * - * @param x : the x index of the tile (not the real coordinate). - * @param y : the y index of the tile (not the real coordinate). - * @param update_current : if true, will update the current_tile pointer, otherwise not. - */ - inline void load_tile(unsigned int x, unsigned int y, bool update_current = false) - { - std::lock_guard lock(TileCache::mutex); - - // create a file name from tile number - char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "%d_%d.dat", x, y); - - // if filename/object is not in cache, load the tile - typename TileCache::cache_type::const_iterator iter = TileCache::cache.find(filename); - - if (iter == TileCache::cache.end()) - { - GlobalTileCache& gtc = GlobalTileCache::get_instance(); - - bool ok = gtc.request_cache_memory(this, TileCache::get_image_size()); - assert(ok == true); - struct timespec now{}; - GET_CLOCK(now); - - TileCache::cache[filename] = std::make_pair(load(filename), now); - - //debug(TM, "Cache size : %d/%d", gtc.get_allocated_memory(), gtc.get_max_cache_memory()); - - #ifdef TILECACHE_DEBUG - gtc.print_table(); - #endif - } - - // Get current time - struct timespec now{}; - GET_CLOCK(now); - - // Update entry - auto entry = TileCache::cache[filename]; - entry.second = now; - - if (update_current) - { - TileCache::current_tile = entry.first; - TileCache::curr_tile_num_x = x; - TileCache::curr_tile_num_y = y; - } - } - - - private: - - /** - * Load a tile from an image file. - * @param filename Just the name of the file to load. The filename is - * relative to the \p path. - */ - std::shared_ptr> - load(std::string const& filename) const - { - std::shared_ptr> mem( - new MemoryMap(uint_fast64_t(1) << TileCache::tile_width_exp, - uint_fast64_t(1) << TileCache::tile_width_exp, - MAP_STORAGE_TYPE_PERSISTENT_FILE, - join_pathes(TileCache::path, filename))); - - return mem; - } - }; // end of class TileCacheStore -} - -#endif diff --git a/src/Core/Image/TileImage.h b/src/Core/Image/TileImage.h index 98b1780a..cef1c225 100644 --- a/src/Core/Image/TileImage.h +++ b/src/Core/Image/TileImage.h @@ -23,17 +23,17 @@ #define __TILEIMAGE_H__ #include "Core/Utils/FileSystem.h" +#include "Core/Image/TileCache.h" #include "Globals.h" #include "PixelPolicies.h" #include "StoragePolicies.h" -#include "Core/Image/TileCacheFactory.h" namespace degate { /** * Storage policy for image objects that consists of tiles. * - * This implementation uses a TileCacheStore. + * This implementation uses a TileCache. */ template class StoragePolicy_Tile : public StoragePolicy_Base @@ -93,6 +93,10 @@ namespace degate * least the size specified via the width and height parameter. * Because the image is splitted into equisized tiles the constructed * image might be larger than the requested size. + * + * It supports two types of tile loading depending on the type of the image. + * This is the main point of difference between Attached and Normal project modes. + * @see TileCache. * * @param width The minimum width of the image. * @param height The minimum height of the image. @@ -120,7 +124,7 @@ namespace degate tile_width_exp(tile_width_exp), offset_bitmask((1 << tile_width_exp) - 1), path(path), - tile_cache(TileCacheFactory::get_instance().generate(path, tile_width_exp, scale, loading_type, notification_list)), + tile_cache(std::make_shared>(path, tile_width_exp, scale, loading_type, notification_list)), width(width), height(height) { diff --git a/src/Core/Project/Project.cc b/src/Core/Project/Project.cc index 0516c7a9..6cbb4f74 100644 --- a/src/Core/Project/Project.cc +++ b/src/Core/Project/Project.cc @@ -42,8 +42,6 @@ Project::Project(length_t width, length_t height, ProjectType project_type) : port_color_manager(new PortColorManager()), project_type(project_type) { - TileCacheFactory::get_instance().set_project_type(project_type); - init_default_values(); } @@ -56,8 +54,6 @@ Project::Project(length_t width, length_t height, std::string const& directory, port_color_manager(new PortColorManager()), project_type(project_type) { - TileCacheFactory::get_instance().set_project_type(project_type); - init_default_values(); } From c704d7960c819efa3389d062a6261e0306769842 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Wed, 22 Dec 2021 17:22:54 +0100 Subject: [PATCH 10/19] Fixed a bug in scaling manager regarding scaled sizes. --- src/Core/Image/Manipulation/ScalingManager.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Core/Image/Manipulation/ScalingManager.h b/src/Core/Image/Manipulation/ScalingManager.h index cdbf6801..40e154ac 100644 --- a/src/Core/Image/Manipulation/ScalingManager.h +++ b/src/Core/Image/Manipulation/ScalingManager.h @@ -170,13 +170,14 @@ namespace degate h = size.height(); } + // First scaling possibility + w >>= 1; + h >>= 1; + // Create scalings - for (int i = 2; ((h > min_size) || (w > min_size)) && (i < (1 << 24)); // max 24 scaling levels + for (int i = 2; ((h > min_size) && (w > min_size)) && (i < (1 << 24)); // max 24 scaling levels i *= 2) { - w >>= 1; - h >>= 1; - std::string path; // Get the path to use @@ -231,6 +232,9 @@ namespace degate } images[i] = last_img; + + w >>= 1; + h >>= 1; } } From d8832f9bee8b75fa9e2df909a742dbafdc7e2d46 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Wed, 22 Dec 2021 17:23:52 +0100 Subject: [PATCH 11/19] Fixed wrong exception propagation in the main window. --- .vscode/settings.json | 6 ++++++ src/GUI/MainWindow.cc | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..2085b94f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cmake.configureArgs": [ + "-DBOOST_ROOT=\"C:\\local\\boost_1_73_0\"", + "-DCMAKE_PREFIX_PATH=\"C:\\Qt\\5.15.1\\msvc2019_64\\lib\\cmake\\Qt5\"" + ] +} \ No newline at end of file diff --git a/src/GUI/MainWindow.cc b/src/GUI/MainWindow.cc index 44c3411c..74af670a 100644 --- a/src/GUI/MainWindow.cc +++ b/src/GUI/MainWindow.cc @@ -1513,7 +1513,7 @@ namespace degate catch (const std::exception& e) { // If fail, just throw and cancel project opening - throw std::runtime_error(error_message.c_str()); + throw std::runtime_error(e.what()); } } From 53875fa0c8d4c065bf3ae9eb5e12872daf35e7f3 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Wed, 22 Dec 2021 17:24:20 +0100 Subject: [PATCH 12/19] Removed a problematic debug line. --- src/GUI/Widget/LayersEditWidget.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/GUI/Widget/LayersEditWidget.cc b/src/GUI/Widget/LayersEditWidget.cc index e643a100..4da98e60 100644 --- a/src/GUI/Widget/LayersEditWidget.cc +++ b/src/GUI/Widget/LayersEditWidget.cc @@ -402,8 +402,6 @@ namespace degate { if (background->has_new_image()) { - debug(TM, "Background image path: %s", background->get_image_path()); - // If attached project type, then just create the background image without loading BackgroundImage_shptr bg_image = std::make_shared( layer->get_width(), From c78f99fab85d99f7bd2a876e7630bd035e6b441c Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Wed, 22 Dec 2021 17:38:54 +0100 Subject: [PATCH 13/19] Fixed an issue when a TileCache is destroyed before getting async result. --- src/Core/Image/TileCache.h | 92 ++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/src/Core/Image/TileCache.h b/src/Core/Image/TileCache.h index b55bff33..86d05363 100644 --- a/src/Core/Image/TileCache.h +++ b/src/Core/Image/TileCache.h @@ -55,7 +55,7 @@ namespace degate * This is the main point of difference between Attached and Normal project modes. */ template - class TileCache : public TileCacheBase + class TileCache : public TileCacheBase, public QObject { friend class GlobalTileCache; @@ -145,6 +145,14 @@ namespace degate */ inline ~TileCache() { + std::lock_guard lock(mtx); + + // Delete and clear watchers + for (auto* watcher : watchers) + delete watcher; + watchers.clear(); + + // Release memory release_memory(); } @@ -155,6 +163,8 @@ namespace degate { if (cache.size() == 0) return; + std::lock_guard lock(mtx); + // Initialize a clock to store the oldest struct timespec oldest_clock_val; GET_CLOCK(oldest_clock_val); @@ -198,7 +208,7 @@ namespace degate { if (cache.size() > 0) { - std::lock_guard lock(mutex); + std::lock_guard lock(mtx); // Release the global tile cache (by removing all the used virtual memory by this) GlobalTileCache& gtc = GlobalTileCache::get_instance(); @@ -312,6 +322,8 @@ namespace degate */ inline void load_tile(unsigned int x, unsigned int y, bool update_current = false) { + std::lock_guard lock(mtx); + // Check if tile is included in the base image // Otherwise return loading tile if (!is_included(x, y)) @@ -355,7 +367,7 @@ namespace degate // If update_current, then update and set a black loading tile if (update_current) { - cache[filename] = std::make_pair(loading_tile, now);; + cache[filename] = std::make_pair(loading_tile, now); current_tile = loading_tile; curr_tile_num_x = x; curr_tile_num_y = y; @@ -363,17 +375,7 @@ namespace degate } // Run in another thread the loading phase of the new tile - QtConcurrent::run([=]() - { - // Convert to cache type - auto data = std::make_pair(load(x, y), now); - - // Register the new entry and send notifications - mutex.lock(); - cache[filename] = data; - notify(); - mutex.unlock(); - }); + load_async(x, y, now, filename); // Show the loading tile while waiting for the next update to try to load the real tile image return; @@ -381,7 +383,14 @@ namespace degate else { // If sync - cache[filename] = std::make_pair(load(x, y), now); + auto temp = load(x, y, tile_size, scaled_size, path, best_image_number); + + // Prevent overflow + if (temp == nullptr) + temp = loading_tile; + + // Update cache + cache[filename] = std::make_pair(temp, now); } } else @@ -436,8 +445,13 @@ namespace degate * @return Returns the new memory map (here, for attached mod, just memory). */ inline - std::shared_ptr> - load(unsigned int tile_x, unsigned int tile_y) + static + std::shared_ptr> load(unsigned int tile_x, + unsigned int tile_y, + unsigned int tile_size, + QSize scaled_size, + std::string path, + int best_image_number) { // Prepare sizes QSize reading_size{static_cast(tile_size), static_cast(tile_size)}; @@ -453,7 +467,7 @@ namespace degate // Check overflow if (reading_size.width() <= 0 || reading_size.height() <= 0) - return loading_tile; + return nullptr; // Create reader QImageReader current_reader(path.c_str()); @@ -556,6 +570,44 @@ namespace degate return true; } + /** + * Run load() async, take into account this Tile Cache possible destruction before getting the result. + */ + inline void load_async(unsigned int x, unsigned int y, struct timespec now, std::string filename) + { + // Run the load() function (static) async + auto future = QtConcurrent::run([=](){ + return load(x, y, tile_size, scaled_size, path, best_image_number); + }); + + // Create a new watcher and add it to the list of watchers + watchers.push_back(new QFutureWatcher>>(this)); + auto* watcher = watchers.back(); + + // Called when loading finished and if the watcher object is still valid (not destroyed). + QObject::connect(watcher, &QFutureWatcher>>::finished, [=]() { + // Get the load result (if this lambda was called, then watcher is valid/not destroyed) + auto temp = watcher->future().result(); + + // Prevent overflow + if (temp == nullptr) + temp = loading_tile; + + // Convert to cache type + auto data = std::make_pair(temp, now); + + // Register the new entry and send notifications (if this lambda was called, then this is valid/not destroyed) + cache[filename] = data; + notify(); + + // Remove and delete watcher + watchers.erase(std::remove(watchers.begin(), watchers.end(), watcher), watchers.end()); + delete watcher; + }); + + watcher->setFuture(future); + } + private: const std::string path; const unsigned int tile_width_exp; @@ -576,7 +628,7 @@ namespace degate unsigned int scale; - std::mutex mutex; + std::mutex mtx; TileLoadingType loading_type; WorkspaceNotificationVector notification_list; @@ -588,6 +640,8 @@ namespace degate bool degate_image_format = false; std::shared_ptr> loading_tile; + + std::vector>>*> watchers; }; } // namespace degate From 6d918c611a9b75474329f18f13b44df8586b0698 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Wed, 22 Dec 2021 17:42:37 +0100 Subject: [PATCH 14/19] Removed .vscode folder (a previous commit mistake). --- .vscode/settings.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 2085b94f..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cmake.configureArgs": [ - "-DBOOST_ROOT=\"C:\\local\\boost_1_73_0\"", - "-DCMAKE_PREFIX_PATH=\"C:\\Qt\\5.15.1\\msvc2019_64\\lib\\cmake\\Qt5\"" - ] -} \ No newline at end of file From 9120ca74ee8ef2dd2168c69f981b9d26af1b4fc2 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Wed, 22 Dec 2021 17:49:34 +0100 Subject: [PATCH 15/19] Removed TileCache dependency on QObject. --- src/Core/Image/TileCache.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Image/TileCache.h b/src/Core/Image/TileCache.h index 86d05363..1db79bc5 100644 --- a/src/Core/Image/TileCache.h +++ b/src/Core/Image/TileCache.h @@ -55,7 +55,7 @@ namespace degate * This is the main point of difference between Attached and Normal project modes. */ template - class TileCache : public TileCacheBase, public QObject + class TileCache : public TileCacheBase { friend class GlobalTileCache; @@ -581,7 +581,7 @@ namespace degate }); // Create a new watcher and add it to the list of watchers - watchers.push_back(new QFutureWatcher>>(this)); + watchers.push_back(new QFutureWatcher>>(nullptr)); auto* watcher = watchers.back(); // Called when loading finished and if the watcher object is still valid (not destroyed). From a9650c46b464bd341fb3914261c959f4e363ca6e Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Thu, 20 Jan 2022 13:57:23 +0100 Subject: [PATCH 16/19] Improved the project mode selection with radio buttons. --- src/GUI/Dialog/NewProjectDialog.cc | 21 +++++++++++++++------ src/GUI/Dialog/NewProjectDialog.h | 16 +++++++++++----- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/GUI/Dialog/NewProjectDialog.cc b/src/GUI/Dialog/NewProjectDialog.cc index d8d79072..33f26529 100644 --- a/src/GUI/Dialog/NewProjectDialog.cc +++ b/src/GUI/Dialog/NewProjectDialog.cc @@ -51,9 +51,18 @@ namespace degate project_path_label.setText(tr("Project directory path:")); project_path_button.setText(tr("Set project directory path")); - // Attached mode - attached_mode_label.setText(tr("Attached mode?")); - attached_mode_box.setChecked(false); + // Project mode + project_mode_label.setText(tr("Project mode:")); + normal_project_mode_button.setChecked(true); + normal_project_mode_button.setText(tr("Normal")); + attached_project_mode_button.setText(tr("Attached")); + + // Project mode layout + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(&normal_project_mode_button); + vbox->addWidget(&attached_project_mode_button); + vbox->addStretch(); + project_mode_box.setLayout(vbox); // Validate button validate_button.setText(tr("Ok")); @@ -71,8 +80,8 @@ namespace degate user_selected_directory = true; } - project_group_layout.addWidget(&attached_mode_label, 2, 0); - project_group_layout.addWidget(&attached_mode_box, 2, 1); + project_group_layout.addWidget(&project_mode_label, 2, 0); + project_group_layout.addWidget(&project_mode_box, 2, 1); // Control layout control_layout.addWidget(&validate_button, 0, 1); @@ -133,7 +142,7 @@ namespace degate create_directory(project_directory); // Create the project - project = std::make_shared(size.width(), size.height(), project_directory, attached_mode_box.isChecked() ? ProjectType::Attached : ProjectType::Normal, layers_edit_widget.get_layer_count()); + project = std::make_shared(size.width(), size.height(), project_directory, attached_project_mode_button.isChecked() ? ProjectType::Attached : ProjectType::Normal, layers_edit_widget.get_layer_count()); project->set_name(project_name_edit.text().toStdString()); // Create each layer diff --git a/src/GUI/Dialog/NewProjectDialog.h b/src/GUI/Dialog/NewProjectDialog.h index 21cabdaa..4ef54ab0 100644 --- a/src/GUI/Dialog/NewProjectDialog.h +++ b/src/GUI/Dialog/NewProjectDialog.h @@ -33,7 +33,8 @@ #include #include #include -#include +#include +#include namespace degate { @@ -79,20 +80,25 @@ namespace degate private: QVBoxLayout layout; - // Content - + // Layout QGroupBox project_group; QGridLayout project_group_layout; + // Project name QLabel project_name_label; QLineEdit project_name_edit; + // Project path QLabel project_path_label; QPushButton project_path_button; - QLabel attached_mode_label; - QCheckBox attached_mode_box; + // Project mode + QGroupBox project_mode_box; + QLabel project_mode_label; + QRadioButton normal_project_mode_button; + QRadioButton attached_project_mode_button; + // Layers edit widget QLabel layers_edit_label; LayersEditWidget layers_edit_widget; From cfeb7bd774543dbf256c4d35f54753230541e4ff Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Thu, 20 Jan 2022 16:20:25 +0100 Subject: [PATCH 17/19] Added a max concurrent number of threads per tile load. --- src/Core/Configuration.cc | 11 +++++++++++ src/Core/Configuration.h | 5 +++++ src/GUI/MainWindow.cc | 2 ++ src/GUI/Preferences/PreferencesHandler.cc | 7 +++++++ src/GUI/Preferences/PreferencesHandler.h | 1 + .../PerformancesPreferencesPage.cc | 19 ++++++++++++++++--- .../PerformancesPreferencesPage.h | 2 ++ 7 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/Core/Configuration.cc b/src/Core/Configuration.cc index d66b7285..7c785747 100644 --- a/src/Core/Configuration.cc +++ b/src/Core/Configuration.cc @@ -31,4 +31,15 @@ Configuration::Configuration() = default; uint_fast64_t Configuration::get_max_tile_cache_size() { return static_cast(PREFERENCES_HANDLER.get_preferences().cache_size); +} + +unsigned int Configuration::get_max_concurrent_thread_count() +{ + const auto& pref = PREFERENCES_HANDLER.get_preferences(); + + // Check default value (0 = use ideal thread count) + if (pref.max_concurrent_thread_count == 0) + return QThread::idealThreadCount(); + + return pref.max_concurrent_thread_count; } \ No newline at end of file diff --git a/src/Core/Configuration.h b/src/Core/Configuration.h index b147bcdd..686fd431 100644 --- a/src/Core/Configuration.h +++ b/src/Core/Configuration.h @@ -45,6 +45,11 @@ namespace degate * @return Returns the maximum cache size (in Mb) from the preferences. */ static uint_fast64_t get_max_tile_cache_size(); + + /** + * Get the maximum number of threads allowed to run concurrently. + */ + static unsigned int get_max_concurrent_thread_count(); }; } // namespace degate diff --git a/src/GUI/MainWindow.cc b/src/GUI/MainWindow.cc index 74af670a..e0ffae07 100644 --- a/src/GUI/MainWindow.cc +++ b/src/GUI/MainWindow.cc @@ -405,6 +405,8 @@ namespace degate QObject::connect(&auto_save_timer, SIGNAL(timeout()), this, SLOT(auto_save())); + QThreadPool::globalInstance()->setMaxThreadCount(Configuration::get_max_concurrent_thread_count()); + // Workaround for a bug on Windows that occurs when using QOpenGLWidget + fullscreen mode. // See: https://doc.qt.io/qt-5/windows-issues.html#fullscreen-opengl-based-windows. #ifdef SYS_WINDOWS diff --git a/src/GUI/Preferences/PreferencesHandler.cc b/src/GUI/Preferences/PreferencesHandler.cc index 076d02f0..c88ae783 100644 --- a/src/GUI/Preferences/PreferencesHandler.cc +++ b/src/GUI/Preferences/PreferencesHandler.cc @@ -86,6 +86,9 @@ namespace degate // Image importer cache size preferences.image_importer_cache_size = settings.value("image_importer_cache_size", 256).toUInt(); + // Max concurrent thread count + preferences.max_concurrent_thread_count = settings.value("max_concurrent_thread_count", 0).toUInt(); + load_recent_projects(); } @@ -133,6 +136,7 @@ namespace degate settings.setValue("cache_size", preferences.cache_size); settings.setValue("image_importer_cache_size", preferences.image_importer_cache_size); + settings.setValue("max_concurrent_thread_count", preferences.max_concurrent_thread_count); } void PreferencesHandler::update(const Preferences& updated_preferences) @@ -161,6 +165,9 @@ namespace degate } preferences = updated_preferences; + + // Update max thread count on preference update + QThreadPool::globalInstance()->setMaxThreadCount(Configuration::get_max_concurrent_thread_count()); } void PreferencesHandler::update_language() diff --git a/src/GUI/Preferences/PreferencesHandler.h b/src/GUI/Preferences/PreferencesHandler.h index 90ad31a7..7ffd9459 100644 --- a/src/GUI/Preferences/PreferencesHandler.h +++ b/src/GUI/Preferences/PreferencesHandler.h @@ -74,6 +74,7 @@ namespace degate unsigned int cache_size; unsigned int image_importer_cache_size; + unsigned int max_concurrent_thread_count; }; diff --git a/src/GUI/Preferences/PreferencesPage/PerformancesPreferencesPage.cc b/src/GUI/Preferences/PreferencesPage/PerformancesPreferencesPage.cc index 90338156..b9480429 100644 --- a/src/GUI/Preferences/PreferencesPage/PerformancesPreferencesPage.cc +++ b/src/GUI/Preferences/PreferencesPage/PerformancesPreferencesPage.cc @@ -35,17 +35,29 @@ namespace degate // Layout creation ////////// + introduction_label.setText(tr("These are advanced preferences, please only modify them if you know what you are doing.")); + layout.addWidget(&introduction_label); + + // Cache category + auto general_layout = PreferencesPage::add_category(tr("General")); + + // Max concurrent thread count + PreferencesPage::add_widget(general_layout, tr("Max concurrent thread count (0 to use system defaults):"), &max_concurrent_thread_count_edit); + max_concurrent_thread_count_edit.setMinimum(0); + max_concurrent_thread_count_edit.setMaximum(std::numeric_limits::max()); + max_concurrent_thread_count_edit.setValue(PREFERENCES_HANDLER.get_preferences().max_concurrent_thread_count); + // Cache category - auto theme_layout = PreferencesPage::add_category(tr("Cache")); + auto cache_layout = PreferencesPage::add_category(tr("Cache")); // Cache size spinbox - PreferencesPage::add_widget(theme_layout, tr("Cache size (in Mb):"), &cache_size_edit); + PreferencesPage::add_widget(cache_layout, tr("Cache size (in Mb):"), &cache_size_edit); cache_size_edit.setMinimum(MINIMUM_CACHE_SIZE); cache_size_edit.setMaximum(std::numeric_limits::max()); cache_size_edit.setValue(PREFERENCES_HANDLER.get_preferences().cache_size); // Image importer cache size spinbox - PreferencesPage::add_widget(theme_layout, tr("Image importer cache size (in Mb):"), &image_importer_cache_size_edit); + PreferencesPage::add_widget(cache_layout, tr("Image importer cache size (in Mb):"), &image_importer_cache_size_edit); image_importer_cache_size_edit.setMinimum(MINIMUM_CACHE_SIZE); image_importer_cache_size_edit.setMaximum(std::numeric_limits::max()); image_importer_cache_size_edit.setValue(PREFERENCES_HANDLER.get_preferences().image_importer_cache_size); @@ -61,5 +73,6 @@ namespace degate preferences.cache_size = static_cast(cache_size_edit.value()); preferences.image_importer_cache_size = static_cast(image_importer_cache_size_edit.value()); + preferences.max_concurrent_thread_count = static_cast(max_concurrent_thread_count_edit.value()); } } diff --git a/src/GUI/Preferences/PreferencesPage/PerformancesPreferencesPage.h b/src/GUI/Preferences/PreferencesPage/PerformancesPreferencesPage.h index bcd731c9..bbf1e7d8 100644 --- a/src/GUI/Preferences/PreferencesPage/PerformancesPreferencesPage.h +++ b/src/GUI/Preferences/PreferencesPage/PerformancesPreferencesPage.h @@ -56,8 +56,10 @@ namespace degate void apply(Preferences& preferences) override; private: + QLabel introduction_label; QSpinBox cache_size_edit; QSpinBox image_importer_cache_size_edit; + QSpinBox max_concurrent_thread_count_edit; }; } From 86e7235bf4a976362f1fd6ca907cd284f00a51c6 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Thu, 20 Jan 2022 16:21:11 +0100 Subject: [PATCH 18/19] Fixed a crash on creating a new project with normal mode. --- src/Core/Image/TileCache.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Core/Image/TileCache.h b/src/Core/Image/TileCache.h index 1db79bc5..80e044d3 100644 --- a/src/Core/Image/TileCache.h +++ b/src/Core/Image/TileCache.h @@ -98,10 +98,6 @@ namespace degate QImageReader reader(this->path.c_str()); if (reader.canRead() == false) { - if (!is_directory(this->path)) - throw std::runtime_error("Unsupported image format for image: " + this->path); - - //debug(TM, "Degate image format: %s", this->path.c_str()); degate_image_format = true; return; } From 54ec0672c4ef2dae24ae1fb816a211ecb7af8355 Mon Sep 17 00:00:00 2001 From: DorianBDev Date: Thu, 24 Aug 2023 19:33:33 +0200 Subject: [PATCH 19/19] Added a warning notice on new attached project creation. --- .gitignore | 3 +- src/GUI/Dialog/NewProjectDialog.cc | 51 ++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 13ed1d03..338a6553 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build/* .idea/ .vs/ .vscode/ +.cache/ etc/installer/packages/Degate/data/* !etc/installer/packages/Degate/data/.keepme @@ -16,4 +17,4 @@ etc/installer/packages/Degate/meta/package.xml doc/build/* !doc/build/.keepme -doc/config/DoxyFile \ No newline at end of file +doc/config/DoxyFile diff --git a/src/GUI/Dialog/NewProjectDialog.cc b/src/GUI/Dialog/NewProjectDialog.cc index 33f26529..b6cc4bf6 100644 --- a/src/GUI/Dialog/NewProjectDialog.cc +++ b/src/GUI/Dialog/NewProjectDialog.cc @@ -21,14 +21,20 @@ #include "NewProjectDialog.h" -#include +#include "Globals.h" + #include +#include #include +#include namespace degate { - NewProjectDialog::NewProjectDialog(QWidget* parent, const std::string& project_name, const std::string& project_path) - : QDialog(parent), layers_edit_widget(this, nullptr) + NewProjectDialog::NewProjectDialog(QWidget* parent, + const std::string& project_name, + const std::string& project_path) + : QDialog(parent), + layers_edit_widget(this, nullptr) { setWindowFlags(Qt::Window); setWindowTitle(tr("New project creation")); @@ -55,10 +61,10 @@ namespace degate project_mode_label.setText(tr("Project mode:")); normal_project_mode_button.setChecked(true); normal_project_mode_button.setText(tr("Normal")); - attached_project_mode_button.setText(tr("Attached")); + attached_project_mode_button.setText(tr("[BETA] Attached")); // Project mode layout - QVBoxLayout *vbox = new QVBoxLayout; + QVBoxLayout* vbox = new QVBoxLayout; vbox->addWidget(&normal_project_mode_button); vbox->addWidget(&attached_project_mode_button); vbox->addStretch(); @@ -117,9 +123,13 @@ namespace degate QSize size = layers_edit_widget.get_max_size(); // Check values - if (layers_edit_widget.get_layer_count() == 0 || size.width() == 0 || size.height() == 0 || project_name_edit.text().length() < 1) + if (layers_edit_widget.get_layer_count() == 0 || size.width() == 0 || size.height() == 0 || + project_name_edit.text().length() < 1) { - QMessageBox::warning(this, tr("Invalid values"), tr("The values you entered are invalid. You need at least one layer with a valid image.")); + QMessageBox::warning( + this, + tr("Invalid values"), + tr("The values you entered are invalid. You need at least one layer with a valid image.")); return; } @@ -141,8 +151,28 @@ namespace degate if (!file_exists(project_directory)) create_directory(project_directory); + // Confirm project type + ProjectType project_type = ProjectType::Normal; + if (attached_project_mode_button.isChecked()) + { + auto reply = QMessageBox::question( + this, + tr("Attached project mode is in beta"), + tr("Attached project mode shouldn't be used for real projects for the moment. Crash or project " + "corruption could occur. Continue with attached project mode?"), + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::Yes) + { + project_type = ProjectType::Attached; + } + } + // Create the project - project = std::make_shared(size.width(), size.height(), project_directory, attached_project_mode_button.isChecked() ? ProjectType::Attached : ProjectType::Normal, layers_edit_widget.get_layer_count()); + project = std::make_shared(size.width(), + size.height(), + project_directory, + project_type, + layers_edit_widget.get_layer_count()); project->set_name(project_name_edit.text().toStdString()); // Create each layer @@ -154,7 +184,8 @@ namespace degate void NewProjectDialog::set_project_directory_path() { - QString dir = QFileDialog::getExistingDirectory(this, tr("Select the directory where the project will be created")); + QString dir = + QFileDialog::getExistingDirectory(this, tr("Select the directory where the project will be created")); if (dir.isNull()) reject(); @@ -163,4 +194,4 @@ namespace degate project_path_button.setText(dir + "/" + project_name_edit.text()); } -} +} // namespace degate