Reorganize cache code and call flushAndSubmit in main

This commit is contained in:
Yousef Khadadeh 2026-01-27 12:40:39 +03:00
parent f46d25b8e6
commit f6c4eeb9f3
6 changed files with 43 additions and 42 deletions

View file

@ -36,9 +36,6 @@
#include "Tools/EraserTool.hpp"
size_t DrawingProgram::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH = 33; // Around 30FPS
size_t DrawingProgram::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH = 400;
DrawingProgram::DrawingProgram(World& initWorld):
world(initWorld),
drawCache(*this),
@ -238,23 +235,6 @@ void DrawingProgram::modify_grid(const NetworkingObjects::NetObjWeakPtr<WorldGri
}
void DrawingProgram::update() {
if(world.main.window.lastFrameTime > std::chrono::milliseconds(MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH)) {
if(!badFrametimeTimePoint)
badFrametimeTimePoint = std::chrono::steady_clock::now();
else if(unorderedObjectsExistTimePoint && std::chrono::steady_clock::now() - badFrametimeTimePoint.value() >= std::chrono::milliseconds(MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH) && std::chrono::steady_clock::now() - unorderedObjectsExistTimePoint.value() >= std::chrono::milliseconds(MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH)) {
rebuild_cache();
unorderedObjectsExistTimePoint = std::nullopt;
badFrametimeTimePoint = std::nullopt;
}
}
else
badFrametimeTimePoint = std::nullopt;
if(!drawCache.unsorted_components_exist())
unorderedObjectsExistTimePoint = std::nullopt;
else if(!unorderedObjectsExistTimePoint)
unorderedObjectsExistTimePoint = std::chrono::steady_clock::now();
tool_temporary_switch_update();
controls.cursorHoveringOverCanvas = !world.main.toolbar.io->hoverObstructed;
@ -340,7 +320,7 @@ void DrawingProgram::update() {
update_downloading_dropped_files();
check_updateable_components();
if(drawCache.should_rebuild())
if(drawCache.check_rebuild_needed_from_framerate() || drawCache.should_rebuild())
rebuild_cache();
}

View file

@ -22,8 +22,6 @@ class World;
class DrawingProgram {
public:
static size_t MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH;
static size_t MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH;
DrawingProgram(World& initWorld);
void server_init_no_file();
void toolbar_gui();
@ -65,8 +63,6 @@ class DrawingProgram {
void check_updateable_components();
void update_downloading_dropped_files();
std::optional<std::chrono::steady_clock::time_point> badFrametimeTimePoint;
std::optional<std::chrono::steady_clock::time_point> unorderedObjectsExistTimePoint;
bool selection_action_menu(Vector2f popupPos);
void rebuild_cache();

View file

@ -13,8 +13,14 @@
size_t DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD = 1000;
size_t DrawingProgramCache::MAXIMUM_COMPONENTS_IN_SINGLE_NODE = 50;
size_t DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES = 40;
#ifdef __EMSCRIPTEN__
size_t DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES = 40; // Use less VRAM in web build
#else
size_t DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES = 96;
#endif
size_t DrawingProgramCache::CACHE_NODE_RESOLUTION = 2048;
size_t DrawingProgramCache::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH = 33; // Around 30FPS
size_t DrawingProgramCache::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH = 5000; // Should be a bit long to prevent objects that are being updated, like brush strokes, from constantly refreshing the cache.
std::unordered_map<std::shared_ptr<DrawingProgramCacheBVHNode>, DrawingProgramCache::NodeCache> DrawingProgramCache::nodeCacheMap;
DrawingProgramCache::WindowCache DrawingProgramCache::windowCache;
@ -64,8 +70,25 @@ bool DrawingProgramCache::should_rebuild() const {
return (unsortedComponents.size() >= MINIMUM_COMPONENTS_TO_START_REBUILD);
}
bool DrawingProgramCache::unsorted_components_exist() const {
return !unsortedComponents.empty();
bool DrawingProgramCache::check_rebuild_needed_from_framerate() {
if(drawP.world.main.window.lastFrameTime > std::chrono::milliseconds(MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH)) {
if(!badFrametimeTimePoint)
badFrametimeTimePoint = std::chrono::steady_clock::now();
else if(unorderedObjectsExistTimePoint && std::chrono::steady_clock::now() - badFrametimeTimePoint.value() >= std::chrono::milliseconds(MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH) && std::chrono::steady_clock::now() - unorderedObjectsExistTimePoint.value() >= std::chrono::milliseconds(MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH)) {
unorderedObjectsExistTimePoint = std::nullopt;
badFrametimeTimePoint = std::nullopt;
return true;
}
}
else
badFrametimeTimePoint = std::nullopt;
if(unsortedComponents.empty())
unorderedObjectsExistTimePoint = std::nullopt;
else if(!unorderedObjectsExistTimePoint)
unorderedObjectsExistTimePoint = std::chrono::steady_clock::now();
return false;
}
void DrawingProgramCache::build(const std::unordered_set<CanvasComponentContainer::ObjInfo*>& objsToExclude) {
@ -202,18 +225,13 @@ void DrawingProgramCache::build_bvh_node_coords_and_resolution(DrawingProgramCac
}
void DrawingProgramCache::refresh_all_draw_cache(const DrawData& drawData) {
std::deque<std::shared_ptr<DrawingProgramCacheBVHNode>> nodeFlatList; // We want to render children before parents, so that parents can make use of the cached children
traverse_bvh_run_function(drawData.cam.viewingAreaGenerousCollider, [&](std::shared_ptr<DrawingProgramCacheBVHNode> node) {
if(node && node->coords.inverseScale <= drawData.cam.c.inverseScale) {
auto it = nodeCacheMap.find(node);
if((it == nodeCacheMap.end()) || it->second.invalidBounds.has_value())
nodeFlatList.emplace_front(node);
refresh_draw_cache(node, drawData);
return false;
}
return true;
});
if(!nodeFlatList.empty())
refresh_draw_cache(nodeFlatList.front(), drawData);
}
void DrawingProgramCache::traverse_bvh_run_function(const SCollision::AABB<WorldScalar>& aabb, std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>& node)> f) {
@ -243,6 +261,8 @@ void DrawingProgramCache::refresh_draw_cache(const std::shared_ptr<DrawingProgra
auto bvhNodeCacheIt = nodeCacheMap.find(bvhNode);
if(bvhNodeCacheIt != nodeCacheMap.end()) {
nodeCache = bvhNodeCacheIt->second;
if(!nodeCache.invalidBounds.has_value()) // Node is cached already, and doesn't have invalid areas, so there's nothing to do
return;
nodeCacheMap.erase(bvhNode); // Ensure nodeCache is erased, so that the draw_components_to_canvas function doesnt use it while drawing
}
else {

View file

@ -19,6 +19,8 @@ class DrawingProgramCache {
static size_t MAXIMUM_COMPONENTS_IN_SINGLE_NODE;
static size_t MAXIMUM_DRAW_CACHE_SURFACES;
static size_t CACHE_NODE_RESOLUTION;
static size_t MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH;
static size_t MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH;
DrawingProgramCache(DrawingProgram& initDrawP);
void add_component(CanvasComponentContainer::ObjInfo* c);
@ -32,9 +34,9 @@ class DrawingProgramCache {
void node_loop_erase_if_components(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, std::function<bool(CanvasComponentContainer::ObjInfo* comp)> f);
void node_loop_components(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, std::function<void(CanvasComponentContainer::ObjInfo* comp)> f);
bool should_rebuild() const;
bool check_rebuild_needed_from_framerate();
void update_and_draw_cached_canvas(SkCanvas* canvas, const DrawData& drawData);
void draw_components_to_canvas(SkCanvas* canvas, const DrawData& drawData, const std::optional<SCollision::AABB<WorldScalar>>& drawBounds);
bool unsorted_components_exist() const;
void invalidate_cache_at_aabb(const SCollision::AABB<WorldScalar>& aabb);
void set_clear_window_cache();
@ -69,6 +71,9 @@ class DrawingProgramCache {
void draw_cache_image_to_canvas(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode);
void recursive_draw_layer_item_to_canvas(const DrawingProgramLayerListItem& layerListItem, SkCanvas* canvas, const DrawData& drawData, const std::optional<SCollision::AABB<WorldScalar>>& drawBounds, const std::vector<std::shared_ptr<DrawingProgramCacheBVHNode>>& nodesToDraw);
std::optional<std::chrono::steady_clock::time_point> badFrametimeTimePoint;
std::optional<std::chrono::steady_clock::time_point> unorderedObjectsExistTimePoint;
std::shared_ptr<DrawingProgramCacheBVHNode> bvhRoot;
std::vector<CanvasComponentContainer::ObjInfo*> unsortedComponents;
DrawingProgram& drawP;

View file

@ -285,8 +285,8 @@ nlohmann::json Toolbar::get_config_json() {
debugJson["maxCacheNodes"] = DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES;
debugJson["maxComponentsInNode"] = DrawingProgramCache::MAXIMUM_COMPONENTS_IN_SINGLE_NODE;
debugJson["componentCountToForceCacheRebuild"] = DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD;
debugJson["maximumFrameTimeToForceCacheRebuild"] = DrawingProgram::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH;
debugJson["millisecondMinimumTimeToCheckForCacheRebuild"] = DrawingProgram::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH;
debugJson["maximumFrameTimeToForceCacheRebuild"] = DrawingProgramCache::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH;
debugJson["millisecondMinimumTimeToCheckForCacheRebuild"] = DrawingProgramCache::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH;
toRet["debug"] = debugJson;
return toRet;
@ -354,8 +354,8 @@ void Toolbar::set_config_json(const nlohmann::json& j, VersionNumber version) {
try{j.at("debug").at("maxCacheNodes").get_to(DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES);} catch(...) {}
try{j.at("debug").at("maxComponentsInNode").get_to(DrawingProgramCache::MAXIMUM_COMPONENTS_IN_SINGLE_NODE);} catch(...) {}
try{j.at("debug").at("componentCountToForceCacheRebuild").get_to(DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD);} catch(...) {}
try{j.at("debug").at("maximumFrameTimeToForceCacheRebuild").get_to(DrawingProgram::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH);} catch(...) {}
try{j.at("debug").at("millisecondMinimumTimeToCheckForCacheRebuild").get_to(DrawingProgram::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH);} catch(...) {}
try{j.at("debug").at("maximumFrameTimeToForceCacheRebuild").get_to(DrawingProgramCache::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH);} catch(...) {}
try{j.at("debug").at("millisecondMinimumTimeToCheckForCacheRebuild").get_to(DrawingProgramCache::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH);} catch(...) {}
main.update_display_names();
}
@ -1860,8 +1860,8 @@ void Toolbar::options_menu() {
gui.text_label_light("Cache max VRAM consumption (MB): " + std::to_string(cacheVRAMConsumptionInMB));
gui.input_scalar_field<size_t>("max components in node", "Maximum components in single node", &DrawingProgramCache::MAXIMUM_COMPONENTS_IN_SINGLE_NODE, 2, 10000);
gui.input_scalar_field<size_t>("components to force cache rebuild", "Number of components to force cache rebuild", &DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD, 1, 1000000);
gui.input_scalar_field<size_t>("maximum frame time to force cache rebuild", "Maximum frame time to force cache rebuild (ms)", &DrawingProgram::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH, 1, 1000000);
gui.input_scalar_field<size_t>("minimum time to force cache rebuild", "Minimum time to check cache rebuild (ms)", &DrawingProgram::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH, 1, 1000000);
gui.input_scalar_field<size_t>("maximum frame time to force cache rebuild", "Maximum frame time to force cache rebuild (ms)", &DrawingProgramCache::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH, 1, 1000000);
gui.input_scalar_field<size_t>("minimum time to force cache rebuild", "Minimum time to check cache rebuild (ms)", &DrawingProgramCache::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH, 1, 1000000);
gui.pop_id();
break;
}

View file

@ -608,7 +608,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) {
mS.canvas->drawImage(mS.intermediateSurface->makeTemporaryImage(), 0, 0);
mS.ctx->flush();
mS.ctx->flushAndSubmit();
SDL_GL_SwapWindow(mS.window);
#endif