diff --git a/.gitignore b/.gitignore index 775c2f2f..57705ddf 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 @@ -17,5 +18,4 @@ etc/installer/packages/Degate/meta/package.xml doc/build/* !doc/build/.keepme doc/config/DoxyFile - -.cache \ No newline at end of file +.cache diff --git a/src/Core/Configuration.cc b/src/Core/Configuration.cc index 07547c87..7c785747 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; @@ -35,3 +32,14 @@ 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 3bb2edc1..686fd431 100644 --- a/src/Core/Configuration.h +++ b/src/Core/Configuration.h @@ -25,26 +25,33 @@ #include "Globals.h" #include "Core/Primitive/SingletonBase.h" -namespace degate { - - class Configuration : public SingletonBase { - - friend class SingletonBase; - - private: +namespace degate +{ + /** + * @class Configuration + * @brief Singleton class to store important parameters needed everywhere. + */ + class Configuration : public SingletonBase + { - Configuration(); + friend class SingletonBase; - public: + private: + Configuration(); - /** - * 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(); + 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(); - }; + /** + * Get the maximum number of threads allowed to run concurrently. + */ + static unsigned int get_max_concurrent_thread_count(); + }; -} +} // 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..8c3dd1cb 100644 --- a/src/Core/Image/Image.h +++ b/src/Core/Image/Image.h @@ -217,31 +217,80 @@ 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 + * 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. + * @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 tile_width_exp = 10) : + unsigned int scale = 1, + 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, - tile_width_exp) + scale, + tile_width_exp, + loading_type, + notification_list) { } /** * 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 + * 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. + * @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& directory, + std::string const& path, bool persistent = true, - unsigned int tile_width_exp = 10) : + unsigned int scale = 1, + unsigned int tile_width_exp = 10, + TileLoadingType loading_type = TileLoadingType::Sync, + const WorkspaceNotificationList& notification_list = {}) : ImageBase(width, height), StoragePolicy_Tile(width, height, - directory, + path, persistent, - tile_width_exp) + scale, + tile_width_exp, + loading_type, + notification_list) { } diff --git a/src/Core/Image/Manipulation/ScalingManager.h b/src/Core/Image/Manipulation/ScalingManager.h index 97e05f67..40e154ac 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,54 +130,111 @@ 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."); - + // Get base image std::shared_ptr last_img = images[1]; - unsigned int w = last_img->get_width(); - unsigned int h = last_img->get_height(); + unsigned int w = 0; + unsigned int h = 0; - for (int i = 2; ((h > min_size) || (w > min_size)) && - (i < (1 << 24)); // max 24 scaling levels - i *= 2) + // If normal mode, then check if directory exists + if (project_type == ProjectType::Normal) { - w >>= 1; - h >>= 1; + if (!(file_exists(base_directory) && is_directory(base_directory))) + throw InvalidPathException("The path for prescaled images must exist. But it is not there."); - // 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)); + w = last_img->get_width(); + h = last_img->get_height(); + } + else + { + QImageReader reader(last_img->get_path().c_str()); - debug(TM, "create scaled image in %s for scaling factor %d?", dir_path.c_str(), i); - if (!file_exists(dir_path)) - { - debug(TM, "yes"); - create_directory(dir_path); + // 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(); + } - std::shared_ptr new_img(new ImageType(w, h, dir_path, - images[1]->is_persistent())); + // First scaling possibility + w >>= 1; + h >>= 1; - scale_down_by_2(new_img, last_img); - last_img = new_img; + // Create scalings + for (int i = 2; ((h > min_size) && (w > min_size)) && (i < (1 << 24)); // max 24 scaling levels + i *= 2) + { + std::string 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)); + + // 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"); - std::shared_ptr new_img(new ImageType(w, h, dir_path, - images[1]->is_persistent())); - - last_img = new_img; + // If attached, pass the image path directly + // @see TileCache to understand + path = images[1]->get_path(); + + // 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; + + w >>= 1; + h >>= 1; } } @@ -190,7 +252,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..80e044d3 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,348 +22,229 @@ #ifndef __TILECACHE_H__ #define __TILECACHE_H__ -#include "Core/Utils/MemoryMap.h" +#include "Core/Image/TileImage.h" +#include "Core/Image/TileCacheBase.h" +#include "Core/Image/GlobalTileCache.h" +#include "GUI/Workspace/WorkspaceNotifier.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 +#include "Core/Utils/MemoryMap.h" -/** - * 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 +#include +#include namespace degate { - class TileCacheBase + /** + * @enum TileLoadingType + * @brief Defines the different tile loading types. + */ + enum class TileLoadingType { - public: - virtual void cleanup_cache() = 0; - virtual void print() const = 0; + Sync, + Async }; - class GlobalTileCache : public SingletonBase + /** + * @class TileCache + * @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 { - 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: - - 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); - } - - void remove_oldest() - { - 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 - } - } + friend class GlobalTileCache; public: - 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 (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"; - } - - bool request_cache_memory(TileCacheBase* requestor, uint_fast64_t amount) + /** + * 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 + * 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. + * 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. + */ + 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), + loading_type(loading_type), + notification_list(notification_list), + tile_size(1 << tile_width_exp) { -#ifdef TILECACHE_DEBUG - debug(TM, "Local cache %p requests %d bytes.", requestor, amount); -#endif - while (allocated_memory + amount > max_cache_memory) + // Check if Degate's image format + QImageReader reader(this->path.c_str()); + if (reader.canRead() == false) { -#ifdef TILECACHE_DEBUG - debug(TM, "Try to free memory"); -#endif - remove_oldest(); + degate_image_format = true; + return; } - if (allocated_memory + amount <= max_cache_memory) + // If the image is a multi-page/multi-res, we take the page with the biggest resolution. + if (reader.imageCount() > 1) { - struct timespec now; - GET_CLOCK(now); - - cache_t::iterator found = cache.find(requestor); - if (found == cache.end()) + QSize best_size{0, 0}; + for (int i = 0; i < reader.imageCount(); i++) { - 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; + if (best_size.width() < reader.size().width() || best_size.height() < reader.size().height()) + { + best_size = reader.size(); + best_image_number = reader.currentImageNumber(); + } + + reader.jumpToNextImage(); } - allocated_memory += amount; -#ifdef TILECACHE_DEBUG - print_table(); -#endif - return true; + reader.jumpToImage(best_image_number); } - debug(TM, "Can't free memory."); - - 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 - - cache_t::iterator found = cache.find(requestor); - - if (found == cache.end()) + // Size + size = reader.size(); + if (!size.isValid()) { - debug(TM, "Unknown memory should be released."); - print_table(); - assert(1==0); + debug(TM, "Can't read size of %s.\n", this->path.c_str()); + return; } - 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 - } + // Scaled size conversion + auto h = static_cast(round(log(scale) / log(2))); + scaled_size = QSize{size.width() >> h, size.height() >> h}; - if (entry.second == 0) - { -#ifdef TILECACHE_DEBUG - debug(TM, "Memory completely released. Remove entry from global cache."); -#endif - cache.erase(found); - } - } + // Create loading tile + loading_tile = std::make_shared>(tile_size, tile_size); } - inline uint_fast64_t get_max_cache_memory() const + /** + * Release memory on destroy. + */ + inline ~TileCache() { - return max_cache_memory; - } + std::lock_guard lock(mtx); - inline uint_fast64_t get_allocated_memory() const - { - return allocated_memory; - } + // Delete and clear watchers + for (auto* watcher : watchers) + delete watcher; + watchers.clear(); - inline bool is_full(uint_fast64_t amount) const - { - return allocated_memory + amount > max_cache_memory; + // Release memory + release_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; + /** + * Cleanup the cache by removing the oldest entry. + */ + inline void cleanup_cache() override + { + if (cache.size() == 0) return; - private: + std::lock_guard lock(mtx); - typedef std::shared_ptr> MemoryMap_shptr; - typedef std::map> cache_type; + // Initialize a clock to store the oldest + struct timespec oldest_clock_val; + GET_CLOCK(oldest_clock_val); - const std::string directory; - const unsigned int tile_width_exp; - const bool persistent; + auto oldest = cache.begin(); - cache_type cache; + // Search for oldest entry + for (auto 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; + } + } - // Used for caching the working tile. - mutable MemoryMap_shptr current_tile; - mutable unsigned curr_tile_num_x; - mutable unsigned curr_tile_num_y; + assert(oldest != cache.end()); - std::mutex mutex; + // Release memory + (*oldest).second.first.reset(); // explicit reset of smart pointer + // Clean the cache entry + cache.erase(oldest); - public: +#ifdef TILECACHE_DEBUG + debug(TM, "local cache: %d entries after remove\n", cache.size()); +#endif - /** - * Create a TileCache object. - * @param directory The directory where all the tiles are for a TileImage. - * @param tile_width_exp - * @param persistent - */ - 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) - { + // Update the global tile cache (release the virtual memory) + GlobalTileCache& gtc = GlobalTileCache::get_instance(); + gtc.release_cache_memory(this, get_image_size()); } /** - * Destroy a TileCache object. + * Release all the memory. */ - ~TileCache() - { - release_memory(); - } - - void release_memory() + inline void release_memory() { if (cache.size() > 0) { - std::lock_guard lock(mutex); + std::lock_guard lock(mtx); - GlobalTileCache& gtc = GlobalTileCache::get_instance(); + // Release the global tile cache (by removing all the used virtual memory by this) + GlobalTileCache& gtc = GlobalTileCache::get_instance(); gtc.release_cache_memory(this, cache.size() * get_image_size()); + + // Release the memory current_tile.reset(); 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) { std::cout << "\t+ " - << directory << "/" - << (*iter).first << " " - << (*iter).second.second.tv_sec - << "/" - << (*iter).second.second.tv_nsec - << std::endl; + << path << "/" + << (*iter).first << " " + << (*iter).second.second.tv_sec + << "/" + << (*iter).second.second.tv_nsec + << std::endl; } } + /** + * 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 void cache_around(unsigned int min_x, unsigned int max_x, unsigned int min_y, @@ -386,9 +267,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(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,46 +281,125 @@ 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); } } } + /** + * Get a tile. If the tile is not in the cache, the tile is loaded. + * + * @param x Absolut pixel coordinate. + * @param y Absolut pixel coordinate. + * @return Returns a shared pointer to a MemoryMap object. + */ + std::shared_ptr> + inline get_tile(unsigned int x, unsigned int y) + { + 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) || current_tile_is_loading) + { + load_tile(tile_num_x, tile_num_y, true); + } + + return current_tile; + } + + /** + * 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(mutex); + std::lock_guard lock(mtx); + + // 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; + } - // create a file name from tile number + 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 + // 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(); + GlobalTileCache& gtc = GlobalTileCache::get_instance(); + // Allocate memory (global tile cache) 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); + 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 + 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; + } + else + { + // If sync + auto temp = load(x, y, tile_size, scaled_size, path, best_image_number); - //debug(TM, "Cache size : %d/%d", gtc.get_allocated_memory(), gtc.get_max_cache_memory()); + // Prevent overflow + if (temp == nullptr) + temp = loading_tile; + + // Update cache + cache[filename] = std::make_pair(temp, 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 } - // Get current time - struct timespec now{}; - GET_CLOCK(now); - // Update entry auto entry = cache[filename]; entry.second = now; @@ -449,97 +409,236 @@ namespace degate current_tile = entry.first; curr_tile_num_x = x; curr_tile_num_y = y; + current_tile_is_loading = false; } } + protected: + /** - * Get a tile. If the tile is not in the cache, the tile is loaded. - * - * @param x Absolut pixel coordinate. - * @param y Absolut pixel coordinate. - * @return Returns a shared pointer to a MemoryMap object. + * Get image size in bytes. */ - std::shared_ptr> - inline get_tile(unsigned int x, unsigned int y) + inline uint_fast64_t get_image_size() const { - 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)) - { - load_tile(tile_num_x, tile_num_y, true); - } - - return current_tile; + return sizeof(typename PixelPolicy::pixel_type) * (uint_fast64_t(1) << tile_width_exp) * (uint_fast64_t(1) << tile_width_exp); } - protected: + /** + * Send all notifications regarding the notification list. + */ + inline void notify() + { + for (auto notification : notification_list) + WorkspaceNotifier::get_instance().notify(notification.first, notification.second); + } /** - * Remove the oldest entry from the cache. + * 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). */ - void cleanup_cache() override + inline + 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) { - if (cache.size() == 0) return; + // 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)}; - struct timespec oldest_clock_val; - GET_CLOCK(oldest_clock_val); + // Check width + if (reading_size.width() + read_size.width() > scaled_size.width()) + reading_size.setWidth(scaled_size.width() - read_size.width()); - typename cache_type::iterator oldest = cache.begin(); + // Check height + if (reading_size.height() + read_size.height() > scaled_size.height()) + reading_size.setHeight(scaled_size.height() - read_size.height()); - for (typename cache_type::iterator iter = cache.begin(); - iter != cache.end(); ++iter) + // Check overflow + if (reading_size.width() <= 0 || reading_size.height() <= 0) + return nullptr; + + // 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()) { - struct timespec clock_val = (*iter).second.second; - if (clock_val < oldest_clock_val) + 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++) { - oldest_clock_val.tv_sec = clock_val.tv_sec; - oldest_clock_val.tv_nsec = clock_val.tv_nsec; - oldest = iter; + rgb = rgb_data[y * reading_size.width() + x]; + mem->set(x, y, MERGE_CHANNELS(qRed(rgb), qGreen(rgb), qBlue(rgb), qAlpha(rgb))); } } - 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()); + 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))); - private: + return mem; + } /** - * Get image size in bytes. + * 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). */ - uint_fast64_t get_image_size() const + inline bool is_included(unsigned int tile_x, unsigned int tile_y) { - return sizeof(typename PixelPolicy::pixel_type) * (uint_fast64_t(1) << tile_width_exp) * (uint_fast64_t(1) << tile_width_exp); + // 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; } /** - * 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. + * Run load() async, take into account this Tile Cache possible destruction before getting the result. */ - std::shared_ptr> - load(std::string const& filename) const + inline void load_async(unsigned int x, unsigned int y, struct timespec now, std::string filename) { - //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))); + // Run the load() function (static) async + auto future = QtConcurrent::run([=](){ + return load(x, y, tile_size, scaled_size, path, best_image_number); + }); - return mem; + // Create a new watcher and add it to the list of watchers + watchers.push_back(new QFutureWatcher>>(nullptr)); + 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); } - }; // end of class TileCache -} -#endif + private: + const std::string path; + const unsigned int tile_width_exp; + + // Cache types. + typedef std::shared_ptr> MemoryMap_shptr; + typedef std::map> + cache_type; + + cache_type cache; + + // Used for caching the working tile. + 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; + + std::mutex mtx; + + 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; + + std::vector>>*> watchers; + }; +} // namespace degate + +#endif //__TILECACHE_H__ 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/TileImage.h b/src/Core/Image/TileImage.h index e7eac4c4..cef1c225 100644 --- a/src/Core/Image/TileImage.h +++ b/src/Core/Image/TileImage.h @@ -22,11 +22,11 @@ #ifndef __TILEIMAGE_H__ #define __TILEIMAGE_H__ +#include "Core/Utils/FileSystem.h" +#include "Core/Image/TileCache.h" #include "Globals.h" #include "PixelPolicies.h" #include "StoragePolicies.h" -#include "Core/Utils/FileSystem.h" -#include "TileCache.h" namespace degate { @@ -52,10 +52,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 +69,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, @@ -90,32 +93,43 @@ 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. - * @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 * 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& 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, + 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(std::make_shared>(path, tile_width_exp, scale, loading_type, notification_list)), + 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 +140,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 +164,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 +183,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 +193,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 +212,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 +220,7 @@ namespace degate */ void release_memory() { - tile_cache.release_memory(); + tile_cache->release_memory(); } }; @@ -215,7 +229,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 +238,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..6cbb4f74 100644 --- a/src/Core/Project/Project.cc +++ b/src/Core/Project/Project.cc @@ -35,22 +35,24 @@ 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) { 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) { init_default_values(); } @@ -379,3 +381,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 64e859cb..72c64151 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..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 @@ -141,7 +144,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 +174,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 +191,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 +207,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 +219,76 @@ 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) + { + 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(), + 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); + + // Finish here + 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..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")); @@ -51,6 +57,19 @@ namespace degate project_path_label.setText(tr("Project directory path:")); project_path_button.setText(tr("Set project directory path")); + // 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("[BETA] 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")); @@ -67,6 +86,9 @@ namespace degate user_selected_directory = true; } + 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); control_layout.setColumnStretch(0, 1); @@ -101,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; } @@ -125,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, 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 @@ -138,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(); @@ -147,4 +194,4 @@ namespace degate project_path_button.setText(dir + "/" + project_name_edit.text()); } -} +} // namespace degate diff --git a/src/GUI/Dialog/NewProjectDialog.h b/src/GUI/Dialog/NewProjectDialog.h index 3e834d4a..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,17 +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; + // 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; diff --git a/src/GUI/MainWindow.cc b/src/GUI/MainWindow.cc index 3c8a5fb6..40c8f397 100644 --- a/src/GUI/MainWindow.cc +++ b/src/GUI/MainWindow.cc @@ -59,7 +59,6 @@ namespace degate setWindowTitle("Degate"); setWindowIcon(QIcon(":/degate_logo.png")); - // Workspace workspace = new WorkspaceRenderer(this); @@ -447,6 +446,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-6/windows-issues.html#fullscreen-opengl-based-windows. #ifdef SYS_WINDOWS @@ -708,6 +709,7 @@ namespace degate project.reset(); project = nullptr; + workspace->set_project(nullptr); workspace->update_screen(); @@ -728,7 +730,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) { @@ -1519,6 +1530,7 @@ namespace degate try { std::shared_ptr imported_project = nullptr; + // First try, in worker thread progress_dialog.set_job([&] { try { @@ -1534,9 +1546,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(e.what()); + } } if (imported_project == nullptr) 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; }; } diff --git a/src/GUI/Widget/LayersEditWidget.cc b/src/GUI/Widget/LayersEditWidget.cc index 81eea98d..4da98e60 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,43 +359,67 @@ 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())); - - return; + if (background->has_new_image()) + { + // 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); } diff --git a/src/GUI/Workspace/WorkspaceBackground.cc b/src/GUI/Workspace/WorkspaceBackground.cc index 511fcae8..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(); @@ -152,13 +158,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/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..be7cad73 --- /dev/null +++ b/src/GUI/Workspace/WorkspaceNotifier.h @@ -0,0 +1,114 @@ +/** + * 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 +#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 + }; + + /** + * 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). + * + * @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 e5a73522..89cc8a8a 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 { @@ -233,9 +234,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() @@ -419,6 +443,8 @@ namespace degate void WorkspaceRenderer::cleanup() { + WorkspaceNotifier::get_instance().undefine(WorkspaceTarget::Workspace); + makeCurrent(); // Delete opengl objects here @@ -461,6 +487,11 @@ namespace degate // Get and print GLSL version QOpenGLFunctions *glFuncs = QOpenGLContext::currentContext()->functions(); debug(TM, "GLSL version: %s", glFuncs->glGetString(GL_SHADING_LANGUAGE_VERSION)); + + // Define the draw notification for the workspace (renderer), just a repaint + WorkspaceNotifier::get_instance().define(WorkspaceTarget::Workspace, WorkspaceNotification::Draw, [=](){ + this->repaint(); + }); } void WorkspaceRenderer::paintGL() 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/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 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