2025-07-19 15:57:09 +03:00
|
|
|
#include "DrawingProgramCache.hpp"
|
2025-07-20 15:24:44 +03:00
|
|
|
#include "DrawingProgram.hpp"
|
|
|
|
|
#include "../World.hpp"
|
|
|
|
|
#include "../MainProgram.hpp"
|
|
|
|
|
#include <chrono>
|
|
|
|
|
|
|
|
|
|
#ifdef USE_SKIA_BACKEND_GRAPHITE
|
|
|
|
|
#include <include/gpu/graphite/Surface.h>
|
|
|
|
|
#elif USE_SKIA_BACKEND_GANESH
|
|
|
|
|
#include <include/gpu/ganesh/GrDirectContext.h>
|
|
|
|
|
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#define MAXIMUM_DRAW_CACHE_SURFACES 32
|
|
|
|
|
|
|
|
|
|
DrawingProgramCache::DrawingProgramCache(DrawingProgram& initDrawP):
|
|
|
|
|
drawP(initDrawP)
|
2025-07-21 11:59:19 +03:00
|
|
|
{
|
|
|
|
|
lastBvhBuildTime = std::chrono::steady_clock::now();
|
|
|
|
|
}
|
2025-07-19 15:57:09 +03:00
|
|
|
|
|
|
|
|
void DrawingProgramCache::build(std::vector<CollabListType::ObjectInfoPtr> components) {
|
2025-07-21 11:59:19 +03:00
|
|
|
bvhRoot = std::make_shared<BVHNode>();
|
2025-07-19 15:57:09 +03:00
|
|
|
unsortedComponents.clear();
|
2025-07-20 15:24:44 +03:00
|
|
|
nodesWithCachedSurfaces.clear();
|
2025-07-19 15:57:09 +03:00
|
|
|
std::erase_if(components, [&](auto& c) {
|
2025-07-21 17:48:46 +03:00
|
|
|
if(!c->obj->worldAABB) {
|
2025-07-19 15:57:09 +03:00
|
|
|
unsortedComponents.emplace_back(c);
|
2025-07-21 11:59:19 +03:00
|
|
|
c->obj->parentBvhNode.reset();
|
2025-07-19 15:57:09 +03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
build_bvh_node(bvhRoot, components);
|
2025-07-21 11:59:19 +03:00
|
|
|
lastBvhBuildTime = std::chrono::steady_clock::now();
|
2025-07-19 15:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
2025-07-20 15:24:44 +03:00
|
|
|
void DrawingProgramCache::update() {
|
2025-07-21 17:48:46 +03:00
|
|
|
if(unsortedComponents.size() >= 1000 && (std::chrono::steady_clock::now() - lastBvhBuildTime) >= std::chrono::seconds(5))
|
2025-07-21 11:59:19 +03:00
|
|
|
force_rebuild();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DrawingProgramCache::force_rebuild() {
|
|
|
|
|
build(drawP.components.client_list());
|
2025-07-19 15:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DrawingProgramCache::add_component(const CollabListType::ObjectInfoPtr& c) {
|
|
|
|
|
unsortedComponents.emplace_back(c);
|
2025-07-19 19:41:25 +03:00
|
|
|
invalidate_cache_before_pos(c->pos);
|
2025-07-19 15:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DrawingProgramCache::erase_component(const CollabListType::ObjectInfoPtr& c) {
|
2025-07-21 11:59:19 +03:00
|
|
|
auto parentBvhNode = c->obj->parentBvhNode.lock();
|
|
|
|
|
if(parentBvhNode) {
|
|
|
|
|
std::erase(parentBvhNode->components, c);
|
|
|
|
|
c->obj->parentBvhNode.reset();
|
2025-07-19 15:57:09 +03:00
|
|
|
}
|
2025-07-21 11:59:19 +03:00
|
|
|
else
|
|
|
|
|
std::erase(unsortedComponents, c);
|
2025-07-19 19:41:25 +03:00
|
|
|
invalidate_cache_before_pos(c->pos);
|
2025-07-19 15:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
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) {
|
2025-07-19 15:57:09 +03:00
|
|
|
SCollision::AABB<WorldScalar>& cAABB = c->worldAABB.value();
|
2025-07-21 11:59:19 +03:00
|
|
|
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) {
|
2025-07-19 15:57:09 +03:00
|
|
|
return comp->obj.get() == c;
|
|
|
|
|
});
|
2025-07-21 11:59:19 +03:00
|
|
|
if(it != bvhNode->components.end())
|
|
|
|
|
return {bvhNode, it};
|
|
|
|
|
for(auto& p : bvhNode->children) {
|
2025-07-19 15:57:09 +03:00
|
|
|
auto toRet = get_bvh_node_fully_containing_recursive(p, c);
|
|
|
|
|
if(toRet.first)
|
|
|
|
|
return toRet;
|
|
|
|
|
}
|
2025-07-21 11:59:19 +03:00
|
|
|
return {nullptr, bvhNode->components.end()};
|
2025-07-19 15:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
2025-07-21 16:54:48 +03:00
|
|
|
void DrawingProgramCache::traverse_bvh_collision_check(const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, const SCollision::ColliderCollection<float>& checkAgainstCam) {
|
|
|
|
|
for(auto& c : drawP.components.client_list())
|
|
|
|
|
c->obj->globalCollisionCheck = false;
|
|
|
|
|
for(auto& c : unsortedComponents)
|
|
|
|
|
c->obj->globalCollisionCheck = c->obj->collides_with(drawP.world.drawData.cam.c, checkAgainstWorld, checkAgainstCam, drawP.colliderAllocated);
|
|
|
|
|
traverse_bvh_collision_check_recursive(bvhRoot, checkAgainstWorld, checkAgainstCam);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-21 17:04:50 +03:00
|
|
|
void DrawingProgramCache::traverse_bvh_collision_check_recursive_tiny(const std::shared_ptr<BVHNode>& bvhNode) {
|
2025-07-21 16:54:48 +03:00
|
|
|
if(bvhNode) {
|
|
|
|
|
for(auto& c : bvhNode->components)
|
2025-07-21 17:04:50 +03:00
|
|
|
c->obj->globalCollisionCheck = true;
|
2025-07-21 16:54:48 +03:00
|
|
|
for(auto& p : bvhNode->children)
|
2025-07-21 17:04:50 +03:00
|
|
|
traverse_bvh_collision_check_recursive_tiny(p);
|
2025-07-21 16:54:48 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DrawingProgramCache::traverse_bvh_collision_check_recursive(const std::shared_ptr<BVHNode>& bvhNode, const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, const SCollision::ColliderCollection<float>& checkAgainstCam) {
|
|
|
|
|
if(bvhNode) {
|
2025-07-21 17:04:50 +03:00
|
|
|
if(SCollision::collide(checkAgainstWorld.bounds, bvhNode->bounds)) {
|
|
|
|
|
auto& camCoords = drawP.world.drawData.cam.c;
|
|
|
|
|
if((camCoords.inverseScale >> DRAWCOMP_COLLIDE_MIN_SHIFT_TINY) >= bvhNode->coords.inverseScale) {
|
|
|
|
|
if(SCollision::collide(checkAgainstCam, camCoords.to_space(bvhNode->bounds.min)))
|
|
|
|
|
traverse_bvh_collision_check_recursive_tiny(bvhNode);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
for(auto& c : bvhNode->components)
|
|
|
|
|
c->obj->globalCollisionCheck = c->obj->collides_with(drawP.world.drawData.cam.c, checkAgainstWorld, checkAgainstCam, drawP.colliderAllocated);
|
|
|
|
|
for(auto& p : bvhNode->children)
|
|
|
|
|
traverse_bvh_collision_check_recursive(p, checkAgainstWorld, checkAgainstCam);
|
|
|
|
|
}
|
2025-07-21 16:54:48 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
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) {
|
2025-07-19 15:57:09 +03:00
|
|
|
f(nullptr, unsortedComponents);
|
|
|
|
|
traverse_bvh_run_function_recursive(bvhRoot, aabb, f);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
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)
|
2025-07-19 15:57:09 +03:00
|
|
|
traverse_bvh_run_function_recursive(p, aabb, f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
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();
|
2025-07-19 15:57:09 +03:00
|
|
|
}
|
2025-07-21 11:59:19 +03:00
|
|
|
invalidate_cache_before_pos(c->pos);
|
2025-07-19 15:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
void DrawingProgramCache::build_bvh_node(const std::shared_ptr<BVHNode>& bvhNode, std::vector<CollabListType::ObjectInfoPtr> components) {
|
2025-07-19 15:57:09 +03:00
|
|
|
if(components.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
bvhNode->bounds = components.front()->obj->worldAABB.value();
|
2025-07-19 15:57:09 +03:00
|
|
|
for(auto& c : components)
|
2025-07-21 11:59:19 +03:00
|
|
|
bvhNode->bounds.include_aabb_in_bounds(c->obj->worldAABB.value());
|
2025-07-19 15:57:09 +03:00
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
build_bvh_node_coords_and_resolution(*bvhNode);
|
2025-07-20 15:24:44 +03:00
|
|
|
|
|
|
|
|
if(components.size() < 100) {
|
2025-07-21 11:59:19 +03:00
|
|
|
bvhNode->components = components;
|
|
|
|
|
for(auto& c : bvhNode->components)
|
|
|
|
|
c->obj->parentBvhNode = bvhNode;
|
2025-07-19 15:57:09 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
WorldVec boundsCenter = bvhNode->bounds.center();
|
2025-07-19 15:57:09 +03:00
|
|
|
|
|
|
|
|
std::array<std::vector<CollabListType::ObjectInfoPtr>, 4> parts;
|
|
|
|
|
|
|
|
|
|
for(auto& c : components) {
|
|
|
|
|
const auto& cAABB = c->obj->worldAABB.value();
|
|
|
|
|
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);
|
2025-07-21 11:59:19 +03:00
|
|
|
else {
|
|
|
|
|
bvhNode->components.emplace_back(c);
|
|
|
|
|
c->obj->parentBvhNode = bvhNode;
|
|
|
|
|
}
|
2025-07-19 15:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(auto& p : parts) {
|
|
|
|
|
if(!p.empty())
|
2025-07-21 11:59:19 +03:00
|
|
|
build_bvh_node(bvhNode->children.emplace_back(std::make_shared<BVHNode>()), p);
|
2025-07-19 15:57:09 +03:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-19 19:41:25 +03:00
|
|
|
|
2025-07-20 15:24:44 +03:00
|
|
|
void DrawingProgramCache::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
|
|
|
|
|
|
|
|
|
|
const int MAX_DIMENSION_RESOLUTION = 2000;
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-19 19:41:25 +03:00
|
|
|
void DrawingProgramCache::invalidate_cache_before_pos(uint64_t placementToInvalidateAt) {
|
|
|
|
|
// We could use worldAABB of component to invalidate specific areas. However, that comes with some issues:
|
|
|
|
|
// - The AABB couldve been changed from the last frame, so the current worldAABB doesnt actually represent what we should update
|
|
|
|
|
// - The worldAABB might not be calculated yet, or mightve been invalidated (worldAABB = nullopt)
|
|
|
|
|
// For now, we will ignore worldAABB and just clear cache by placement in list
|
|
|
|
|
|
|
|
|
|
std::erase_if(nodesWithCachedSurfaces, [&](auto& n) {
|
|
|
|
|
if(placementToInvalidateAt <= n->drawCache.value().lastDrawnComponentPlacement) {
|
|
|
|
|
n->drawCache = std::nullopt;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-07-20 15:24:44 +03:00
|
|
|
|
|
|
|
|
void DrawingProgramCache::refresh_all_draw_cache(SkCanvas* canvas, const DrawData& drawData) {
|
2025-07-21 11:59:19 +03:00
|
|
|
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) {
|
2025-07-20 15:24:44 +03:00
|
|
|
if(node && node->coords.inverseScale <= drawData.cam.c.inverseScale) {
|
|
|
|
|
if(!node->drawCache)
|
|
|
|
|
nodeFlatList.emplace_front(node);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
if(!nodeFlatList.empty())
|
2025-07-21 11:59:19 +03:00
|
|
|
refresh_draw_cache(nodeFlatList.front(), drawData);
|
2025-07-20 15:24:44 +03:00
|
|
|
}
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
void DrawingProgramCache::refresh_draw_cache(const std::shared_ptr<BVHNode>& bvhNode, const DrawData& drawData) {
|
|
|
|
|
if(bvhNode->children.empty() && bvhNode->components.empty())
|
2025-07-20 15:24:44 +03:00
|
|
|
return;
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
bvhNode->drawCache = std::nullopt;
|
2025-07-20 15:24:44 +03:00
|
|
|
BVHNode::DrawCacheData drawCache;
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
SkImageInfo imgInfo = SkImageInfo::MakeN32Premul(bvhNode->resolution.x(), bvhNode->resolution.y());
|
2025-07-20 15:24:44 +03:00
|
|
|
#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
|
|
|
|
|
drawCache.surface = SkSurfaces::RenderTarget(drawP.world.main.window.ctx.get(), skgpu::Budgeted::kNo, imgInfo, drawP.world.main.window.defaultMSAASampleCount, &drawP.world.main.window.defaultMSAASurfaceProps);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if(!drawCache.surface)
|
|
|
|
|
throw std::runtime_error("[DrawingProgramCache::refresh_draw_cache] Could not make cache surface");
|
|
|
|
|
|
|
|
|
|
SkCanvas* cacheCanvas = drawCache.surface->getCanvas();
|
|
|
|
|
|
|
|
|
|
cacheCanvas->clear(SkColor4f{0, 0, 0, 0});
|
|
|
|
|
|
|
|
|
|
DrawData cacheDrawData = drawData;
|
2025-07-21 11:59:19 +03:00
|
|
|
cacheDrawData.cam.c = bvhNode->coords;
|
2025-07-20 15:24:44 +03:00
|
|
|
cacheDrawData.cam.changed = nullptr;
|
2025-07-21 11:59:19 +03:00
|
|
|
cacheDrawData.cam.set_viewing_area(bvhNode->resolution.cast<float>());
|
2025-07-20 15:24:44 +03:00
|
|
|
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();
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
bvhNode->drawCache = drawCache; // Set the drawCache after rendering is done, so that the draw function doesnt assume we have this node cached
|
2025-07-20 15:24:44 +03:00
|
|
|
|
|
|
|
|
while(nodesWithCachedSurfaces.size() >= MAXIMUM_DRAW_CACHE_SURFACES) {
|
2025-07-21 11:59:19 +03:00
|
|
|
std::shared_ptr<BVHNode> leastUsedNode = *nodesWithCachedSurfaces.begin();
|
|
|
|
|
for(const std::shared_ptr<BVHNode>& n : nodesWithCachedSurfaces) {
|
2025-07-20 15:24:44 +03:00
|
|
|
if(n->drawCache.value().lastRenderTime < leastUsedNode->drawCache.value().lastRenderTime)
|
|
|
|
|
leastUsedNode = n;
|
|
|
|
|
}
|
2025-07-21 11:59:19 +03:00
|
|
|
leastUsedNode->drawCache = std::nullopt;
|
2025-07-20 15:24:44 +03:00
|
|
|
nodesWithCachedSurfaces.erase(leastUsedNode);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-21 11:59:19 +03:00
|
|
|
nodesWithCachedSurfaces.emplace(bvhNode);
|
2025-07-20 15:24:44 +03:00
|
|
|
}
|