Started work on new cache system for layers

This commit is contained in:
Yousef Khadadeh 2025-12-26 19:52:36 +03:00
parent 783bb7dacb
commit c2abccefc2
14 changed files with 234 additions and 19 deletions

View file

@ -58,6 +58,7 @@ set(sources "src/main.cpp"
"src/RichText/TextBox.cpp"
"src/RichText/TextStyleModifier.cpp"
"src/RichText/ParagraphStyleData.cpp"
"src/DrawingProgram/DrawingProgramCacheNew.cpp"
"src/DrawingProgram/DrawingProgramCache.cpp"
"src/DrawingProgram/DrawingProgram.cpp"
"src/DrawingProgram/DrawingProgramSelection.cpp"

View file

@ -0,0 +1,141 @@
#include "DrawingProgramCacheNew.hpp"
#include "DrawingProgram.hpp"
std::unordered_set<std::shared_ptr<DrawingProgramCacheNew::BVHNode>> DrawingProgramCacheNew::nodesWithCachedSurfaces;
DrawingProgramCacheNew::DrawingProgramCacheNew(DrawingProgram& initDrawP):
drawP(initDrawP)
{}
void DrawingProgramCacheNew::add_component(const CanvasComponentContainer::ObjInfoSharedPtr& c) {
unsortedComponents.emplace_back(c);
invalidate_cache_at_aabb(c->obj->get_world_bounds());
}
void DrawingProgramCacheNew::erase_component(const CanvasComponentContainer::ObjInfoSharedPtr& c) {
auto it = objToBVHNode.find(c);
if(it != objToBVHNode.end())
objToBVHNode.erase(it);
else
std::erase(unsortedComponents, c);
invalidate_cache_at_aabb(c->obj->get_world_bounds());
}
void DrawingProgramCacheNew::invalidate_cache_at_aabb(const SCollision::AABB<WorldScalar>& aabb) {
std::for_each(nodesWithCachedSurfaces.begin(), nodesWithCachedSurfaces.end(), [&](auto& n) {
if(n->drawCache.value().attachedCache == this && SCollision::collide(aabb, n->bounds)) {
auto& dCache = n->drawCache.value();
if(dCache.invalidBounds.has_value()) {
auto& iBounds = dCache.invalidBounds.value();
iBounds.include_aabb_in_bounds(aabb);
}
else
dCache.invalidBounds = aabb;
}
});
}
bool DrawingProgramCacheNew::should_rebuild() const {
return (unsortedComponents.size() >= MINIMUM_COMPONENTS_TO_START_REBUILD);
}
void DrawingProgramCacheNew::build(const std::unordered_set<CanvasComponentContainer::ObjInfoSharedPtr>& objsToExclude) {
internal_build(drawP.layerMan.get_flattened_component_list(), objsToExclude);
}
void DrawingProgramCacheNew::internal_build(std::vector<CanvasComponentContainer::ObjInfoSharedPtr> componentsToBuild, const std::unordered_set<CanvasComponentContainer::ObjInfoSharedPtr>& objsToNotInclude) {
std::erase_if(componentsToBuild, [&objsToNotInclude](auto& c) {
return objsToNotInclude.contains(c);
});
bvhRoot = std::make_shared<BVHNode>();
unsortedComponents.clear();
clear_own_cached_surfaces();
build_bvh_node(bvhRoot, componentsToBuild);
}
void DrawingProgramCacheNew::clear_own_cached_surfaces() {
std::erase_if(nodesWithCachedSurfaces, [&](auto& n) {
if(n->drawCache.value().attachedCache == this) {
n->drawCache = std::nullopt;
return true;
}
return false;
});
}
void DrawingProgramCacheNew::preupdate_component(const CanvasComponentContainer::ObjInfoSharedPtr& c) {
auto it = objToBVHNode.find(c);
if(it != objToBVHNode.end()) {
unsortedComponents.emplace_back(c);
std::erase(it->second->components, c);
}
invalidate_cache_at_aabb(c->obj->get_world_bounds());
}
void DrawingProgramCacheNew::build_bvh_node(const std::shared_ptr<BVHNode>& bvhNode, const std::vector<CanvasComponentContainer::ObjInfoSharedPtr>& components) {
if(components.empty())
return;
bvhNode->bounds = components.front()->obj->get_world_bounds();
for(auto& c : components)
bvhNode->bounds.include_aabb_in_bounds(c->obj->get_world_bounds());
build_bvh_node_coords_and_resolution(*bvhNode);
if(components.size() < MAXIMUM_COMPONENTS_IN_SINGLE_NODE) {
bvhNode->components = components;
for(auto& c : bvhNode->components)
objToBVHNode.emplace(c, bvhNode);
return;
}
WorldVec boundsCenter = bvhNode->bounds.center();
std::array<std::vector<CanvasComponentContainer::ObjInfoSharedPtr>, 4> parts;
for(auto& c : components) {
const auto& cAABB = c->obj->get_world_bounds();
if(cAABB.min.x() < boundsCenter.x() && cAABB.max.x() < boundsCenter.x() && cAABB.min.y() < boundsCenter.y() && cAABB.max.y() < boundsCenter.y())
parts[0].emplace_back(c);
else if(cAABB.min.x() > boundsCenter.x() && cAABB.max.x() > boundsCenter.x() && cAABB.min.y() < boundsCenter.y() && cAABB.max.y() < boundsCenter.y())
parts[1].emplace_back(c);
else if(cAABB.min.x() < boundsCenter.x() && cAABB.max.x() < boundsCenter.x() && cAABB.min.y() > boundsCenter.y() && cAABB.max.y() > boundsCenter.y())
parts[2].emplace_back(c);
else if(cAABB.min.x() > boundsCenter.x() && cAABB.max.x() > boundsCenter.x() && cAABB.min.y() > boundsCenter.y() && cAABB.max.y() > boundsCenter.y())
parts[3].emplace_back(c);
else {
bvhNode->components.emplace_back(c);
objToBVHNode.emplace(c, bvhNode);
}
}
for(auto& p : parts) {
if(!p.empty())
build_bvh_node(bvhNode->children.emplace_back(std::make_shared<BVHNode>()), p);
}
}
void DrawingProgramCacheNew::build_bvh_node_coords_and_resolution(BVHNode& node) {
// These are part of the draw cache data, but we're putting it out here, because:
// - We dont have to recalculate it every time we refresh the cache
// - Some of the data here is needed to determine whether to generate the cache in the first place
constexpr int MAX_DIMENSION_RESOLUTION = 2048;
WorldVec cacheBoundDim = node.bounds.dim();
if(cacheBoundDim.x() > cacheBoundDim.y()) {
node.resolution.x() = MAX_DIMENSION_RESOLUTION;
node.resolution.y() = MAX_DIMENSION_RESOLUTION * static_cast<double>(cacheBoundDim.y() / cacheBoundDim.x());
}
else {
node.resolution.y() = MAX_DIMENSION_RESOLUTION;
node.resolution.x() = MAX_DIMENSION_RESOLUTION * static_cast<double>(cacheBoundDim.x() / cacheBoundDim.y());
}
node.coords.rotation = 0.0;
node.coords.pos = node.bounds.min;
// Not necessary, but we choose the number that had less operations done on it for more accuracy
if(node.resolution.x() > node.resolution.y())
node.coords.inverseScale = cacheBoundDim.x().divide_double(node.resolution.x());
else
node.coords.inverseScale = cacheBoundDim.y().divide_double(node.resolution.y());
}

View file

@ -0,0 +1,46 @@
#pragma once
#include "../CanvasComponents/CanvasComponentContainer.hpp"
#include <unordered_map>
class DrawingProgramCacheNew {
public:
static constexpr size_t MINIMUM_COMPONENTS_TO_START_REBUILD = 1000;
static constexpr size_t MAXIMUM_COMPONENTS_IN_SINGLE_NODE = 50;
static constexpr size_t MAXIMUM_DRAW_CACHE_SURFACES = 40;
DrawingProgramCacheNew(DrawingProgram& initDrawP);
void add_component(const CanvasComponentContainer::ObjInfoSharedPtr& c);
void erase_component(const CanvasComponentContainer::ObjInfoSharedPtr& c);
void clear_own_cached_surfaces();
void preupdate_component(const CanvasComponentContainer::ObjInfoSharedPtr& c);
void build(const std::unordered_set<CanvasComponentContainer::ObjInfoSharedPtr>& objsToExclude);
bool should_rebuild() const;
private:
struct BVHNode {
SCollision::AABB<WorldScalar> bounds;
std::vector<CanvasComponentContainer::ObjInfoSharedPtr> components;
std::vector<std::shared_ptr<BVHNode>> children;
CoordSpaceHelper coords;
Vector2i resolution;
struct DrawCacheData {
sk_sp<SkSurface> surface;
std::chrono::steady_clock::time_point lastRenderTime;
DrawingProgramCacheNew* attachedCache;
std::optional<SCollision::AABB<WorldScalar>> invalidBounds;
};
std::optional<DrawCacheData> drawCache;
};
static std::unordered_set<std::shared_ptr<BVHNode>> nodesWithCachedSurfaces;
void internal_build(std::vector<CanvasComponentContainer::ObjInfoSharedPtr> componentsToBuild, const std::unordered_set<CanvasComponentContainer::ObjInfoSharedPtr>& objsToNotInclude);
void invalidate_cache_at_aabb(const SCollision::AABB<WorldScalar>& aabb);
void build_bvh_node(const std::shared_ptr<BVHNode>& bvhNode, const std::vector<CanvasComponentContainer::ObjInfoSharedPtr>& components);
void build_bvh_node_coords_and_resolution(BVHNode& node);
std::unordered_map<CanvasComponentContainer::ObjInfoSharedPtr, std::shared_ptr<BVHNode>> objToBVHNode;
std::shared_ptr<BVHNode> bvhRoot;
std::vector<CanvasComponentContainer::ObjInfoSharedPtr> unsortedComponents;
DrawingProgram& drawP;
};

View file

@ -3,20 +3,24 @@
#include "../DrawingProgram.hpp"
#include <Helpers/Parallel.hpp>
void DrawingProgramLayer::draw(SkCanvas* canvas, const DrawData& drawData) {
void DrawingProgramLayer::draw(SkCanvas* canvas, const DrawData& drawData) const {
for(auto& p : components->get_data())
p->obj->draw(canvas, drawData);
}
void DrawingProgramLayer::set_component_list_callbacks(DrawingProgramLayerManager& layerMan) {
void DrawingProgramLayer::set_component_list_callbacks(DrawingProgramLayerManager& layerMan) const {
components->set_insert_callback([&](const CanvasComponentContainer::ObjInfoSharedPtr& c) {
c->obj->set_owner_obj_info(c);
c->obj->commit_update(layerMan.drawP); // Run commit update on insert so that world bounds are calculated
});
}
void DrawingProgramLayer::commit_update_dont_invalidate_cache(DrawingProgramLayerManager& layerMan) {
void DrawingProgramLayer::commit_update_dont_invalidate_cache(DrawingProgramLayerManager& layerMan) const {
parallel_loop_container(components->get_data(), [&layerMan](auto& comp) {
comp->obj->commit_update_dont_invalidate_cache(layerMan.drawP);
});
}
void DrawingProgramLayer::get_flattened_component_list(std::vector<CanvasComponentContainer::ObjInfoSharedPtr>& objList) const {
objList.insert(objList.begin(), components->get_data().begin(), components->get_data().end());
}

View file

@ -7,9 +7,10 @@ class DrawingProgramLayerManager;
class DrawingProgramLayer {
public:
void draw(SkCanvas* canvas, const DrawData& drawData);
void set_component_list_callbacks(DrawingProgramLayerManager& layerMan);
void commit_update_dont_invalidate_cache(DrawingProgramLayerManager& layerMan);
void draw(SkCanvas* canvas, const DrawData& drawData) const;
void set_component_list_callbacks(DrawingProgramLayerManager& layerMan) const;
void commit_update_dont_invalidate_cache(DrawingProgramLayerManager& layerMan) const;
void get_flattened_component_list(std::vector<CanvasComponentContainer::ObjInfoSharedPtr>& objList) const;
CanvasComponentContainer::NetListOwnerPtr components;
};

View file

@ -3,12 +3,12 @@
#include "DrawingProgramLayer.hpp"
#include <Helpers/Parallel.hpp>
void DrawingProgramLayerFolder::draw(SkCanvas* canvas, const DrawData& drawData) {
void DrawingProgramLayerFolder::draw(SkCanvas* canvas, const DrawData& drawData) const {
for(auto& p : folderList->get_data() | std::views::reverse)
p->obj->draw(canvas, drawData);
}
void DrawingProgramLayerFolder::set_component_list_callbacks(DrawingProgramLayerManager& layerMan) {
void DrawingProgramLayerFolder::set_component_list_callbacks(DrawingProgramLayerManager& layerMan) const {
folderList->set_insert_callback([&](auto& c) {
c->obj->set_component_list_callbacks(layerMan);
});
@ -16,7 +16,12 @@ void DrawingProgramLayerFolder::set_component_list_callbacks(DrawingProgramLayer
listItem->obj->set_component_list_callbacks(layerMan);
}
void DrawingProgramLayerFolder::commit_update_dont_invalidate_cache(DrawingProgramLayerManager& layerMan) {
void DrawingProgramLayerFolder::commit_update_dont_invalidate_cache(DrawingProgramLayerManager& layerMan) const {
for(auto& listItem : folderList->get_data())
listItem->obj->commit_update_dont_invalidate_cache(layerMan);
}
void DrawingProgramLayerFolder::get_flattened_component_list(std::vector<CanvasComponentContainer::ObjInfoSharedPtr>& objList) const {
for(auto& listItem : folderList->get_data())
listItem->obj->get_flattened_component_list(objList);
}

View file

@ -2,15 +2,17 @@
#include <Helpers/NetworkingObjects/NetObjOrderedList.hpp>
#include <include/core/SkCanvas.h>
#include "../../DrawData.hpp"
#include "../../CanvasComponents/CanvasComponentContainer.hpp"
class DrawingProgramLayerListItem;
class DrawingProgramLayerManager;
class DrawingProgramLayerFolder {
public:
void draw(SkCanvas* canvas, const DrawData& drawData);
void set_component_list_callbacks(DrawingProgramLayerManager& layerMan);
void commit_update_dont_invalidate_cache(DrawingProgramLayerManager& layerMan);
void draw(SkCanvas* canvas, const DrawData& drawData) const;
void set_component_list_callbacks(DrawingProgramLayerManager& layerMan) const;
void commit_update_dont_invalidate_cache(DrawingProgramLayerManager& layerMan) const;
void get_flattened_component_list(std::vector<CanvasComponentContainer::ObjInfoSharedPtr>& objList) const;
NetworkingObjects::NetObjOwnerPtr<NetworkingObjects::NetObjOrderedList<DrawingProgramLayerListItem>> folderList;
bool isFolderOpen = false;

View file

@ -46,6 +46,13 @@ void DrawingProgramLayerListItem::commit_update_dont_invalidate_cache(DrawingPro
layerData->commit_update_dont_invalidate_cache(layerMan);
}
void DrawingProgramLayerListItem::get_flattened_component_list(std::vector<CanvasComponentContainer::ObjInfoSharedPtr>& objList) const {
if(folderData)
folderData->get_flattened_component_list(objList);
else
layerData->get_flattened_component_list(objList);
}
bool DrawingProgramLayerListItem::is_folder() const {
return folderData != nullptr;
}

View file

@ -5,6 +5,7 @@
#include "DrawingProgramLayerFolder.hpp"
#include "DrawingProgramLayer.hpp"
#include "SerializedBlendMode.hpp"
#include "../../CanvasComponents/CanvasComponentContainer.hpp"
class DrawingProgramLayerFolder;
class DrawingProgramLayer;
@ -23,9 +24,9 @@ class DrawingProgramLayerListItem {
void set_component_list_callbacks(DrawingProgramLayerManager &layerMan);
void commit_update_dont_invalidate_cache(DrawingProgramLayerManager& layerMan);
void draw(SkCanvas* canvas, const DrawData& drawData) const;
void set_name(NetworkingObjects::DelayUpdateSerializedClassManager& delayedNetObjMan, const std::string& newName) const;
const std::string& get_name() const;
void get_flattened_component_list(std::vector<CanvasComponentContainer::ObjInfoSharedPtr>& objList) const;
void set_alpha(NetworkingObjects::DelayUpdateSerializedClassManager& delayedNetObjMan, float newAlpha) const;
float get_alpha() const;

View file

@ -32,3 +32,9 @@ void DrawingProgramLayerManager::draw(SkCanvas* canvas, const DrawData& drawData
if(layerTreeRoot)
layerTreeRoot->draw(canvas, drawData);
}
std::vector<CanvasComponentContainer::ObjInfoSharedPtr> DrawingProgramLayerManager::get_flattened_component_list() const {
std::vector<CanvasComponentContainer::ObjInfoSharedPtr> toRet;
layerTreeRoot->get_flattened_component_list(toRet);
return toRet;
}

View file

@ -25,5 +25,6 @@ class DrawingProgramLayerManager {
return nullptr;
return editLayerPtr->get_layer().components->push_back_and_send_create(editLayerPtr->get_layer().components, newObj);
}
std::vector<CanvasComponentContainer::ObjInfoSharedPtr> get_flattened_component_list() const;
DrawingProgramLayerManagerGUI listGUI;
};

View file

@ -49,7 +49,7 @@ void EllipseDrawTool::switch_tool(DrawingProgramToolType newTool) {
void EllipseDrawTool::tool_update() {
switch(drawStage) {
case 0: {
if(drawP.controls.leftClick) {
if(drawP.controls.leftClick && drawP.layerMan.is_a_layer_being_edited()) {
CanvasComponentContainer* newContainer = new CanvasComponentContainer(drawP.world.netObjMan, CanvasComponentType::ELLIPSE);
EllipseCanvasComponent& newEllipse = static_cast<EllipseCanvasComponent&>(newContainer->get_comp());
@ -63,7 +63,7 @@ void EllipseDrawTool::tool_update() {
newEllipse.d.fillStrokeMode = static_cast<uint8_t>(fillStrokeMode);
newContainer->coords = drawP.world.drawData.cam.c;
objInfoBeingEdited = drawP.components->push_back_and_send_create(drawP.components, newContainer);
objInfoBeingEdited = drawP.layerMan.add_component_to_layer_being_edited(newContainer);
drawStage = 1;
}
break;

View file

@ -48,7 +48,7 @@ void RectDrawTool::switch_tool(DrawingProgramToolType newTool) {
void RectDrawTool::tool_update() {
switch(drawStage) {
case 0: {
if(drawP.controls.leftClick) {
if(drawP.controls.leftClick && drawP.layerMan.is_a_layer_being_edited()) {
CanvasComponentContainer* newContainer = new CanvasComponentContainer(drawP.world.netObjMan, CanvasComponentType::RECTANGLE);
RectangleCanvasComponent& newRectangle = static_cast<RectangleCanvasComponent&>(newContainer->get_comp());
@ -62,7 +62,7 @@ void RectDrawTool::tool_update() {
newRectangle.d.p2 = ensure_points_have_distance(newRectangle.d.p1, newRectangle.d.p2, MINIMUM_DISTANCE_BETWEEN_BOUNDS);
newRectangle.d.fillStrokeMode = static_cast<uint8_t>(fillStrokeMode);
newContainer->coords = drawP.world.drawData.cam.c;
objInfoBeingEdited = drawP.components->push_back_and_send_create(drawP.components, newContainer);
objInfoBeingEdited = drawP.layerMan.add_component_to_layer_being_edited(newContainer);
drawStage = 1;
}
break;

View file

@ -47,7 +47,7 @@ void TextBoxTool::switch_tool(DrawingProgramToolType newTool) {
void TextBoxTool::tool_update() {
switch(drawStage) {
case 0: {
if(drawP.controls.leftClick) {
if(drawP.controls.leftClick && drawP.layerMan.is_a_layer_being_edited()) {
startAt = drawP.world.main.input.mouse.pos;
endAt = startAt;
@ -58,7 +58,7 @@ void TextBoxTool::tool_update() {
newTextBox.d.p2 = ensure_points_have_distance(newTextBox.d.p1, newTextBox.d.p2, MINIMUM_DISTANCE_BETWEEN_BOUNDS);
newTextBox.d.editing = true;
newContainer->coords = drawP.world.drawData.cam.c;
objInfoBeingEdited = drawP.components->push_back_and_send_create(drawP.components, newContainer);
objInfoBeingEdited = drawP.layerMan.add_component_to_layer_being_edited(newContainer);
drawStage = 1;
}
break;