Cache system doesnt break when transforming objects

This commit is contained in:
Yousef Khadadeh 2025-07-21 11:59:19 +03:00
parent caea747f1e
commit eb00125f01
9 changed files with 101 additions and 95 deletions

View file

@ -106,9 +106,9 @@ void BrushTool::tool_update() {
controls.intermediateItem->d.hasRoundCaps = controls.hasRoundCaps;
controls.intermediateItem->coords = drawP.world.drawData.cam.c;
controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now();
controls.intermediateItem->temp_update(drawP);
uint64_t placement = drawP.components.client_list().size();
controls.intermediateID = drawP.components.client_insert(placement, controls.intermediateItem);
controls.intermediateItem->temp_update(drawP);
controls.intermediateItem->client_send_place(drawP, placement);
// NOTE: The data isnt finallized at this point, so a BVH isn't generated for an object that you undo while youre drawing (this isnt just for brush strokes)
// The fix for now is to generate a BVH for an object when you redo it

View file

@ -146,7 +146,7 @@ void DrawingProgram::parallel_loop_all_components(std::function<void(const std::
void DrawingProgram::check_all_collisions_base(const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, const SCollision::ColliderCollection<float>& checkAgainstCam) {
for(auto& c : components.client_list())
c->obj->globalCollisionCheck = false;
compCache.traverse_bvh_run_function(checkAgainstWorld.bounds, [&](DrawingProgramCache::BVHNode* node, const std::vector<CollabListType::ObjectInfoPtr>& comps) {
compCache.traverse_bvh_run_function(checkAgainstWorld.bounds, [&](const std::shared_ptr<DrawingProgramCache::BVHNode>& node, const std::vector<CollabListType::ObjectInfoPtr>& comps) {
for(auto& c : comps)
c->obj->globalCollisionCheck = c->obj->collides_with(world.drawData.cam.c, checkAgainstWorld, checkAgainstCam, colliderAllocated);
return true;
@ -447,8 +447,9 @@ void DrawingProgram::initialize_draw_data(cereal::PortableBinaryInputArchive& a)
auto newComp = DrawComponent::allocate_comp_type(t);
a(newComp->coords, *newComp);
components.init_emplace_back(id, newComp);
newComp->final_update_dont_invalidate_cache(*this);
newComp->final_update(*this);
}
compCache.force_rebuild();
}
void DrawingProgram::reset_selection() {
@ -607,9 +608,9 @@ void DrawingProgram::draw_components_to_canvas(SkCanvas* canvas, const DrawData&
for(auto& c : components.client_list())
c->obj->drawSetupData.shouldDraw = false;
std::vector<DrawingProgramCache::BVHNode*> cachedNodesToDraw;
std::vector<std::shared_ptr<DrawingProgramCache::BVHNode>> cachedNodesToDraw;
compCache.traverse_bvh_run_function(drawData.cam.viewingAreaGenerousCollider, [&](DrawingProgramCache::BVHNode* node, const std::vector<CollabListType::ObjectInfoPtr>& comps) {
compCache.traverse_bvh_run_function(drawData.cam.viewingAreaGenerousCollider, [&](const std::shared_ptr<DrawingProgramCache::BVHNode>& node, const std::vector<CollabListType::ObjectInfoPtr>& comps) {
if(!dontUseCache && node && node->drawCache && node->coords.inverseScale <= drawData.cam.c.inverseScale) {
cachedNodesToDraw.emplace_back(node);
return false;
@ -622,7 +623,7 @@ void DrawingProgram::draw_components_to_canvas(SkCanvas* canvas, const DrawData&
for(uint64_t i = 0; i < components.client_list().size(); i++) {
auto& c = components.client_list()[i];
c->obj->draw(canvas, drawData);
for(DrawingProgramCache::BVHNode* bvhNode : cachedNodesToDraw) {
for(const std::shared_ptr<DrawingProgramCache::BVHNode>& bvhNode : cachedNodesToDraw) {
auto& drawCache = bvhNode->drawCache.value();
if(drawCache.lastDrawnComponentPlacement == i) {
canvas->save();

View file

@ -15,25 +15,33 @@
DrawingProgramCache::DrawingProgramCache(DrawingProgram& initDrawP):
drawP(initDrawP)
{}
{
lastBvhBuildTime = std::chrono::steady_clock::now();
}
void DrawingProgramCache::build(std::vector<CollabListType::ObjectInfoPtr> components) {
bvhRoot = BVHNode();
bvhRoot = std::make_shared<BVHNode>();
unsortedComponents.clear();
nodesWithCachedSurfaces.clear();
std::erase_if(components, [&](auto& c) {
if(!c->obj->worldAABB || c->obj->updateDraw) {
unsortedComponents.emplace_back(c);
c->obj->parentBvhNode.reset();
return true;
}
return false;
});
build_bvh_node(bvhRoot, components);
lastBvhBuildTime = std::chrono::steady_clock::now();
}
void DrawingProgramCache::update() {
if(unsortedComponents.size() >= 750)
build(drawP.components.client_list());
if(unsortedComponents.size() >= 750 && (std::chrono::steady_clock::now() - lastBvhBuildTime) >= std::chrono::seconds(7))
force_rebuild();
}
void DrawingProgramCache::force_rebuild() {
build(drawP.components.client_list());
}
void DrawingProgramCache::add_component(const CollabListType::ObjectInfoPtr& c) {
@ -42,84 +50,73 @@ void DrawingProgramCache::add_component(const CollabListType::ObjectInfoPtr& c)
}
void DrawingProgramCache::erase_component(const CollabListType::ObjectInfoPtr& c) {
auto it = std::find(unsortedComponents.begin(), unsortedComponents.end(), c);
if(it != unsortedComponents.end())
unsortedComponents.erase(it);
else {
if(!c->obj->worldAABB.has_value())
return;
auto bvhNodePair = get_bvh_node_fully_containing_recursive(bvhRoot, c->obj.get());
if(bvhNodePair.first)
bvhNodePair.first->components.erase(bvhNodePair.second);
auto parentBvhNode = c->obj->parentBvhNode.lock();
if(parentBvhNode) {
std::erase(parentBvhNode->components, c);
c->obj->parentBvhNode.reset();
}
else
std::erase(unsortedComponents, c);
invalidate_cache_before_pos(c->pos);
}
std::pair<DrawingProgramCache::BVHNode*, std::vector<DrawingProgramCache::CollabListType::ObjectInfoPtr>::iterator> DrawingProgramCache::get_bvh_node_fully_containing_recursive(BVHNode& bvhNode, DrawComponent* c) {
std::pair<std::shared_ptr<DrawingProgramCache::BVHNode>, std::vector<DrawingProgramCache::CollabListType::ObjectInfoPtr>::iterator> DrawingProgramCache::get_bvh_node_fully_containing_recursive(const std::shared_ptr<BVHNode>& bvhNode, DrawComponent* c) {
SCollision::AABB<WorldScalar>& cAABB = c->worldAABB.value();
if(!bvhNode.bounds.fully_contains_aabb(cAABB))
return {nullptr, bvhNode.components.end()};
auto it = std::find_if(bvhNode.components.begin(), bvhNode.components.end(), [&](const auto& comp) {
if(!bvhNode->bounds.fully_contains_aabb(cAABB))
return {nullptr, bvhNode->components.end()};
auto it = std::find_if(bvhNode->components.begin(), bvhNode->components.end(), [&](const auto& comp) {
return comp->obj.get() == c;
});
if(it != bvhNode.components.end())
return {&bvhNode, it};
for(auto& p : bvhNode.children) {
if(it != bvhNode->components.end())
return {bvhNode, it};
for(auto& p : bvhNode->children) {
auto toRet = get_bvh_node_fully_containing_recursive(p, c);
if(toRet.first)
return toRet;
}
return {nullptr, bvhNode.components.end()};
return {nullptr, bvhNode->components.end()};
}
void DrawingProgramCache::traverse_bvh_run_function(const SCollision::AABB<WorldScalar>& aabb, std::function<bool(BVHNode* node, const std::vector<CollabListType::ObjectInfoPtr>& components)> f) {
void DrawingProgramCache::traverse_bvh_run_function(const SCollision::AABB<WorldScalar>& aabb, std::function<bool(const std::shared_ptr<BVHNode> node, const std::vector<CollabListType::ObjectInfoPtr>& components)> f) {
f(nullptr, unsortedComponents);
traverse_bvh_run_function_recursive(bvhRoot, aabb, f);
}
void DrawingProgramCache::traverse_bvh_run_function_recursive(BVHNode& bvhNode, const SCollision::AABB<WorldScalar>& aabb, std::function<bool(BVHNode* node, const std::vector<CollabListType::ObjectInfoPtr>& components)> f) {
if(SCollision::collide(aabb, bvhNode.bounds) && f(&bvhNode, bvhNode.components)) {
for(auto& p : bvhNode.children)
void DrawingProgramCache::traverse_bvh_run_function_recursive(const std::shared_ptr<BVHNode>& bvhNode, const SCollision::AABB<WorldScalar>& aabb, std::function<bool(const std::shared_ptr<BVHNode>& node, const std::vector<CollabListType::ObjectInfoPtr>& components)> f) {
if(bvhNode && SCollision::collide(aabb, bvhNode->bounds) && f(bvhNode, bvhNode->components)) {
for(auto& p : bvhNode->children)
traverse_bvh_run_function_recursive(p, aabb, f);
}
}
void DrawingProgramCache::preupdate_component(DrawComponent* c) {
auto it = std::find_if(unsortedComponents.begin(), unsortedComponents.end(), [&](const auto& comp) {
return comp->obj.get() == c;
});
if(it != unsortedComponents.end()) {
invalidate_cache_before_pos((*it)->pos);
return;
}
else {
if(!c->worldAABB.has_value())
return;
auto bvhNodePair = get_bvh_node_fully_containing_recursive(bvhRoot, c);
if(bvhNodePair.first) {
unsortedComponents.emplace_back(*bvhNodePair.second);
invalidate_cache_before_pos((*bvhNodePair.second)->pos);
bvhNodePair.first->components.erase(bvhNodePair.second);
}
void DrawingProgramCache::preupdate_component(const CollabListType::ObjectInfoPtr& c) {
auto parentBvhNode = c->obj->parentBvhNode.lock();
if(parentBvhNode) {
unsortedComponents.emplace_back(c);
std::erase(parentBvhNode->components, c);
c->obj->parentBvhNode.reset();
}
invalidate_cache_before_pos(c->pos);
}
void DrawingProgramCache::build_bvh_node(BVHNode& node, std::vector<CollabListType::ObjectInfoPtr> components) {
void DrawingProgramCache::build_bvh_node(const std::shared_ptr<BVHNode>& bvhNode, std::vector<CollabListType::ObjectInfoPtr> components) {
if(components.empty())
return;
node.bounds = components.front()->obj->worldAABB.value();
bvhNode->bounds = components.front()->obj->worldAABB.value();
for(auto& c : components)
node.bounds.include_aabb_in_bounds(c->obj->worldAABB.value());
bvhNode->bounds.include_aabb_in_bounds(c->obj->worldAABB.value());
build_bvh_node_coords_and_resolution(node);
build_bvh_node_coords_and_resolution(*bvhNode);
if(components.size() < 100) {
node.components = components;
bvhNode->components = components;
for(auto& c : bvhNode->components)
c->obj->parentBvhNode = bvhNode;
return;
}
WorldVec boundsCenter = node.bounds.center();
WorldVec boundsCenter = bvhNode->bounds.center();
std::array<std::vector<CollabListType::ObjectInfoPtr>, 4> parts;
@ -133,13 +130,15 @@ void DrawingProgramCache::build_bvh_node(BVHNode& node, std::vector<CollabListTy
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
node.components.emplace_back(c);
else {
bvhNode->components.emplace_back(c);
c->obj->parentBvhNode = bvhNode;
}
}
for(auto& p : parts) {
if(!p.empty())
build_bvh_node(node.children.emplace_back(), p);
build_bvh_node(bvhNode->children.emplace_back(std::make_shared<BVHNode>()), p);
}
}
@ -184,8 +183,8 @@ void DrawingProgramCache::invalidate_cache_before_pos(uint64_t placementToInvali
}
void DrawingProgramCache::refresh_all_draw_cache(SkCanvas* canvas, const DrawData& drawData) {
std::deque<BVHNode*> 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, [&](DrawingProgramCache::BVHNode* node, const std::vector<CollabListType::ObjectInfoPtr>& comps) {
std::deque<std::shared_ptr<BVHNode>> 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<DrawingProgramCache::BVHNode> node, const std::vector<CollabListType::ObjectInfoPtr>& comps) {
if(node && node->coords.inverseScale <= drawData.cam.c.inverseScale) {
if(!node->drawCache)
nodeFlatList.emplace_front(node);
@ -194,17 +193,17 @@ void DrawingProgramCache::refresh_all_draw_cache(SkCanvas* canvas, const DrawDat
return true;
});
if(!nodeFlatList.empty())
refresh_draw_cache(*nodeFlatList.front(), drawData);
refresh_draw_cache(nodeFlatList.front(), drawData);
}
void DrawingProgramCache::refresh_draw_cache(BVHNode& bvhNode, const DrawData& drawData) {
if(bvhNode.children.empty() && bvhNode.components.empty())
void DrawingProgramCache::refresh_draw_cache(const std::shared_ptr<BVHNode>& bvhNode, const DrawData& drawData) {
if(bvhNode->children.empty() && bvhNode->components.empty())
return;
bvhNode.drawCache = std::nullopt;
bvhNode->drawCache = std::nullopt;
BVHNode::DrawCacheData drawCache;
SkImageInfo imgInfo = SkImageInfo::MakeN32Premul(bvhNode.resolution.x(), bvhNode.resolution.y());
SkImageInfo imgInfo = SkImageInfo::MakeN32Premul(bvhNode->resolution.x(), bvhNode->resolution.y());
#ifdef USE_SKIA_BACKEND_GRAPHITE
drawCache.surface = SkSurfaces::RenderTarget(drawP.world.main.window.recorder(), imgInfo, skgpu::Mipmapped::kNo, drawP.world.main.window.defaultMSAASurfaceProps);
#elif USE_SKIA_BACKEND_GANESH
@ -219,25 +218,26 @@ void DrawingProgramCache::refresh_draw_cache(BVHNode& bvhNode, const DrawData& d
cacheCanvas->clear(SkColor4f{0, 0, 0, 0});
DrawData cacheDrawData = drawData;
cacheDrawData.cam.c = bvhNode.coords;
cacheDrawData.cam.c = bvhNode->coords;
cacheDrawData.cam.changed = nullptr;
cacheDrawData.cam.set_viewing_area(bvhNode.resolution.cast<float>());
cacheDrawData.cam.set_viewing_area(bvhNode->resolution.cast<float>());
cacheDrawData.refresh_draw_optimizing_values();
drawP.draw_components_to_canvas(cacheCanvas, cacheDrawData, false);
drawCache.lastDrawnComponentPlacement = drawP.components.client_list().size() - 1;
drawCache.lastRenderTime = std::chrono::steady_clock::now();
bvhNode.drawCache = drawCache; // Set the drawCache after rendering is done, so that the draw function doesnt assume we have this node cached
bvhNode->drawCache = drawCache; // Set the drawCache after rendering is done, so that the draw function doesnt assume we have this node cached
while(nodesWithCachedSurfaces.size() >= MAXIMUM_DRAW_CACHE_SURFACES) {
BVHNode* leastUsedNode = *nodesWithCachedSurfaces.begin();
for(BVHNode* n : nodesWithCachedSurfaces) {
std::shared_ptr<BVHNode> leastUsedNode = *nodesWithCachedSurfaces.begin();
for(const std::shared_ptr<BVHNode>& n : nodesWithCachedSurfaces) {
if(n->drawCache.value().lastRenderTime < leastUsedNode->drawCache.value().lastRenderTime)
leastUsedNode = n;
}
leastUsedNode->drawCache = std::nullopt;
nodesWithCachedSurfaces.erase(leastUsedNode);
}
nodesWithCachedSurfaces.emplace(&bvhNode);
nodesWithCachedSurfaces.emplace(bvhNode);
}

View file

@ -1,12 +1,14 @@
#pragma once
#include <vector>
#include "../CollabList.hpp"
#include "../DrawComponents/DrawComponent.hpp"
#include <include/core/SkSurface.h>
#include <unordered_set>
#include "../DrawData.hpp"
class DrawingProgram;
class DrawComponent;
class DrawingProgramCache {
public:
typedef CollabList<std::shared_ptr<DrawComponent>, ServerClientID> CollabListType;
@ -20,7 +22,7 @@ class DrawingProgramCache {
struct BVHNode {
SCollision::AABB<WorldScalar> bounds;
std::vector<CollabListType::ObjectInfoPtr> components;
std::vector<BVHNode> children;
std::vector<std::shared_ptr<BVHNode>> children;
CoordSpaceHelper coords;
Vector2i resolution;
@ -33,25 +35,28 @@ class DrawingProgramCache {
std::optional<DrawCacheData> drawCache;
};
std::unordered_set<BVHNode*> nodesWithCachedSurfaces;
std::unordered_set<std::shared_ptr<BVHNode>> nodesWithCachedSurfaces;
void traverse_bvh_run_function(const SCollision::AABB<WorldScalar>& aabb, std::function<bool(BVHNode* node, const std::vector<CollabListType::ObjectInfoPtr>& components)> f);
void traverse_bvh_run_function(const SCollision::AABB<WorldScalar>& aabb, std::function<bool(const std::shared_ptr<BVHNode> node, const std::vector<CollabListType::ObjectInfoPtr>& components)> f);
void force_rebuild();
// Run before actually updating the component
void preupdate_component(DrawComponent* c);
void preupdate_component(const CollabListType::ObjectInfoPtr& c);
void invalidate_cache_before_pos(uint64_t placementToInvalidateAt);
private:
void build_bvh_node_coords_and_resolution(BVHNode& node);
void refresh_draw_cache(BVHNode& bvhNode, const DrawData& drawData);
void refresh_draw_cache(const std::shared_ptr<BVHNode>& bvhNode, const DrawData& drawData);
void build(std::vector<CollabListType::ObjectInfoPtr> components);
void traverse_bvh_run_function_recursive(BVHNode& bvhNode, const SCollision::AABB<WorldScalar>& aabb, std::function<bool(BVHNode* node, const std::vector<CollabListType::ObjectInfoPtr>& components)> f);
std::pair<DrawingProgramCache::BVHNode*, std::vector<DrawingProgramCache::CollabListType::ObjectInfoPtr>::iterator> get_bvh_node_fully_containing_recursive(BVHNode& bvhNode, DrawComponent* c);
std::chrono::steady_clock::time_point lastBvhBuildTime;
void build_bvh_node(BVHNode& node, std::vector<CollabListType::ObjectInfoPtr> components);
BVHNode bvhRoot;
void traverse_bvh_run_function_recursive(const std::shared_ptr<BVHNode>& bvhNode, const SCollision::AABB<WorldScalar>& aabb, std::function<bool(const std::shared_ptr<BVHNode>& node, const std::vector<CollabListType::ObjectInfoPtr>& components)> f);
std::pair<std::shared_ptr<DrawingProgramCache::BVHNode>, std::vector<DrawingProgramCache::CollabListType::ObjectInfoPtr>::iterator> get_bvh_node_fully_containing_recursive(const std::shared_ptr<BVHNode>& bvhNode, DrawComponent* c);
void build_bvh_node(const std::shared_ptr<BVHNode>& bvhNode, std::vector<CollabListType::ObjectInfoPtr> components);
std::shared_ptr<BVHNode> bvhRoot;
std::vector<CollabListType::ObjectInfoPtr> unsortedComponents;
DrawingProgram& drawP;
};

View file

@ -13,7 +13,6 @@
EllipseDrawTool::EllipseDrawTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
controls.intermediateItem = std::make_shared<DrawEllipse>();
}
void EllipseDrawTool::gui_toolbox() {

View file

@ -10,9 +10,7 @@
RectDrawTool::RectDrawTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
controls.intermediateItem = std::make_shared<DrawRectangle>();
}
{}
void RectDrawTool::gui_toolbox() {
Toolbar& t = drawP.world.main.toolbar;
@ -114,9 +112,9 @@ void RectDrawTool::tool_update() {
controls.intermediateItem->d.fillStrokeMode = static_cast<uint8_t>(controls.fillStrokeMode);
controls.intermediateItem->coords = drawP.world.drawData.cam.c;
controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now();
controls.intermediateItem->temp_update(drawP);
uint64_t placement = drawP.components.client_list().size();
controls.intermediateID = drawP.components.client_insert(placement, controls.intermediateItem);
controls.intermediateItem->temp_update(drawP);
controls.intermediateItem->client_send_place(drawP, placement);
drawP.add_undo_place_component(placement, controls.intermediateItem);
controls.drawStage = 1;