Calculate accurate brush stroke data in parallel before drawing

This commit is contained in:
Yousef Khadadeh 2026-02-07 18:49:56 +03:00
parent fd1f76520b
commit 89925763cd
15 changed files with 86 additions and 50 deletions

View file

@ -8,6 +8,7 @@
#include "../TimePoint.hpp"
#include <include/core/SkVertices.h>
#include <include/pathops/SkPathOps.h>
#include <memory>
#include "../DrawCollision.hpp"
#include "CanvasComponentContainer.hpp"
#include "Helpers/SCollision.hpp"
@ -49,7 +50,7 @@ std::optional<Vector4f> BrushStrokeCanvasComponent::get_stroke_color() const {
return d.color;
}
void BrushStrokeCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData) const {
void BrushStrokeCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const {
SkPaint paint;
paint.setColor4f(SkColor4f{d.color.x(), d.color.y(), d.color.z(), d.color.w()});
@ -73,10 +74,10 @@ void BrushStrokeCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData
//draw_collider(canvas, drawData, strokeObjects, paint2);
}
bool BrushStrokeCanvasComponent::accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords) const {
std::shared_ptr<void> BrushStrokeCanvasComponent::get_predraw_data_accurate(const DrawData& drawData, const CoordSpaceHelper& coords) const {
std::vector<BrushStrokeCanvasComponentPoint> points = smooth_points(0, d.points->size() - 1, DEFAULT_SMOOTHNESS);
std::vector<SkPath> pathsToDraw;
auto pathsToDraw = std::make_shared<std::vector<SkPath>>();
auto triangleFunc = [&](Vector2f a, Vector2f b, Vector2f c) {
SCollision::Triangle worldTri = {
@ -111,7 +112,7 @@ bool BrushStrokeCanvasComponent::accurate_draw(SkCanvas* canvas, const DrawData&
SkPathBuilder pathBuilder;
std::vector<SkPoint> triSkPoints = {convert_vec2<SkPoint>(c.to_space(tri[0])), convert_vec2<SkPoint>(c.to_space(tri[1])), convert_vec2<SkPoint>(c.to_space(tri[2]))};
pathBuilder.addPolygon({triSkPoints.data(), triSkPoints.size()}, true);
pathsToDraw.emplace_back(pathBuilder.detach());
pathsToDraw->emplace_back(pathBuilder.detach());
}
}
return false;
@ -119,19 +120,27 @@ bool BrushStrokeCanvasComponent::accurate_draw(SkCanvas* canvas, const DrawData&
create_triangles(triangleFunc, points, 0, nullptr);
if(d.color.w() == 1.0f) {
SkPaint paint;
paint.setColor4f(SkColor4f{d.color.x(), d.color.y(), d.color.z(), d.color.w()});
for(const SkPath& p : pathsToDraw)
canvas->drawPath(p, paint);
}
else {
canvas->saveLayerAlphaf(nullptr, d.color.w());
SkPaint paint;
paint.setColor4f(SkColor4f{d.color.x(), d.color.y(), d.color.z(), 1.0f});
for(const SkPath& p : pathsToDraw)
canvas->drawPath(p, paint);
canvas->restore();
return pathsToDraw;
}
bool BrushStrokeCanvasComponent::accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords, const std::shared_ptr<void>& predrawData) const {
auto pathsToDraw = std::static_pointer_cast<std::vector<SkPath>>(predrawData);
if(!pathsToDraw->empty()) {
if(d.color.w() == 1.0f) {
SkPaint paint;
paint.setColor4f(SkColor4f{d.color.x(), d.color.y(), d.color.z(), d.color.w()});
for(const SkPath& p : *pathsToDraw)
canvas->drawPath(p, paint);
}
else {
canvas->saveLayerAlphaf(nullptr, d.color.w());
SkPaint paint;
paint.setColor4f(SkColor4f{d.color.x(), d.color.y(), d.color.z(), 1.0f});
for(const SkPath& p : *pathsToDraw)
canvas->drawPath(p, paint);
canvas->restore();
}
}
return true;

View file

@ -34,8 +34,10 @@ class BrushStrokeCanvasComponent : public CanvasComponent {
virtual void set_data_from(const CanvasComponent& other) override;
private:
virtual void draw(SkCanvas* canvas, const DrawData& drawData) const override;
virtual bool accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords) const override;
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const override;
virtual bool accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords, const std::shared_ptr<void>& predrawData) const override;
virtual std::shared_ptr<void> get_predraw_data_accurate(const DrawData& drawData, const CoordSpaceHelper& coords) const override;
virtual void initialize_draw_data(DrawingProgram& drawP) override;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const override;
void create_collider();

View file

@ -37,8 +37,16 @@ std::optional<Vector4f> CanvasComponent::get_stroke_color() const {
return std::nullopt;
}
bool CanvasComponent::accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords) const {
bool CanvasComponent::accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords, const std::shared_ptr<void>& predrawData) const {
return false;
}
std::shared_ptr<void> CanvasComponent::get_predraw_data(const DrawData& drawData) const {
return nullptr;
}
std::shared_ptr<void> CanvasComponent::get_predraw_data_accurate(const DrawData& drawData, const CoordSpaceHelper& coords) const {
return nullptr;
}
CanvasComponent::~CanvasComponent() {}

View file

@ -30,9 +30,11 @@ class CanvasComponent {
friend class CanvasComponentContainer;
friend class CanvasComponentAllocator;
virtual bool accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords) const;
virtual bool accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords, const std::shared_ptr<void>& predrawData) const;
virtual std::shared_ptr<void> get_predraw_data(const DrawData& drawData) const;
virtual std::shared_ptr<void> get_predraw_data_accurate(const DrawData& drawData, const CoordSpaceHelper& coords) const;
virtual void draw(SkCanvas* canvas, const DrawData& drawData) const = 0;
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const = 0;
virtual void initialize_draw_data(DrawingProgram& drawP) = 0;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const = 0;

View file

@ -89,14 +89,14 @@ void CanvasComponentContainer::load_file(cereal::PortableBinaryInputArchive& a,
void CanvasComponentContainer::draw(SkCanvas* canvas, const DrawData& drawData) const {
if(should_draw(drawData))
draw_with_transform(canvas, drawData, calculate_draw_transform(drawData));
draw_with_predraw_data(canvas, drawData, calculate_predraw_data(drawData));
}
void CanvasComponentContainer::draw_with_transform(SkCanvas* canvas, const DrawData& drawData, const TransformDrawData& transformDrawData) const {
if(transformDrawData.scale < COMP_MAX_BEFORE_STOP_SCALING || !get_comp().accurate_draw(canvas, drawData, coords)) {
void CanvasComponentContainer::draw_with_predraw_data(SkCanvas* canvas, const DrawData& drawData, const PreDrawData& preDrawData) const {
if(preDrawData.transformData.scale < COMP_MAX_BEFORE_STOP_SCALING || !get_comp().accurate_draw(canvas, drawData, coords, preDrawData.extraData)) {
canvas->save();
canvas_do_transform(canvas, transformDrawData);
get_comp().draw(canvas, drawData);
canvas_do_transform(canvas, preDrawData.transformData);
get_comp().draw(canvas, drawData, preDrawData.extraData);
canvas->restore();
}
}
@ -151,7 +151,7 @@ bool CanvasComponentContainer::collides_with(const CoordSpaceHelper& camCoords,
}
}
void CanvasComponentContainer::canvas_do_transform(SkCanvas* canvas, const TransformDrawData& transformData) const {
void CanvasComponentContainer::canvas_do_transform(SkCanvas* canvas, const TransformData& transformData) const {
canvas->scale(transformData.scale, transformData.scale);
canvas->rotate(transformData.rotation);
canvas->translate(transformData.translation.x(), transformData.translation.y());
@ -161,8 +161,18 @@ bool CanvasComponentContainer::should_draw(const DrawData& drawData) const {
return (!drawData.clampDrawBetween || (drawData.clampDrawMinimum < coords.inverseScale)) && SCollision::collide(worldAABB.value(), drawData.cam.viewingAreaGenerousCollider);
}
CanvasComponentContainer::TransformDrawData CanvasComponentContainer::calculate_draw_transform(const DrawData& drawData) const {
TransformDrawData toRet;
CanvasComponentContainer::PreDrawData CanvasComponentContainer::calculate_predraw_data(const DrawData& drawData) const {
PreDrawData toRet;
toRet.transformData = calculate_draw_transform(drawData);
if(toRet.transformData.scale < COMP_MAX_BEFORE_STOP_SCALING)
toRet.extraData = get_comp().get_predraw_data(drawData);
else
toRet.extraData = get_comp().get_predraw_data_accurate(drawData, coords);
return toRet;
}
CanvasComponentContainer::TransformData CanvasComponentContainer::calculate_draw_transform(const DrawData& drawData) const {
TransformData toRet;
toRet.translation = -coords.to_space(drawData.cam.c.pos);
toRet.rotation = (coords.rotation - drawData.cam.c.rotation) * 180.0 / std::numbers::pi;
toRet.scale = std::min(static_cast<float>(coords.inverseScale / drawData.cam.c.inverseScale), COMP_MAX_BEFORE_STOP_SCALING);

View file

@ -34,12 +34,16 @@ class CanvasComponentContainer {
void scale_up(const WorldScalar& scaleUpAmount);
};
struct TransformDrawData {
struct TransformData {
Vector2f translation;
float rotation;
float scale;
};
std::optional<TransformDrawData> transformHolder; // Can be used to store transforms when calculating transforms in parallel
struct PreDrawData {
TransformData transformData;
std::shared_ptr<void> extraData;
};
std::optional<PreDrawData> preDrawDataHolder; // Can be used to store transforms when calculating transforms in parallel (and other predraw data)
CanvasComponentContainer();
CanvasComponentContainer(NetworkingObjects::NetObjManager& objMan, CanvasComponentType type);
@ -53,8 +57,9 @@ class CanvasComponentContainer {
CanvasComponent& get_comp() const;
SCollision::AABB<WorldScalar> get_world_bounds() const;
void draw(SkCanvas* canvas, const DrawData& drawData) const;
void draw_with_transform(SkCanvas* canvas, const DrawData& drawData, const TransformDrawData& transformDrawData) const;
TransformDrawData calculate_draw_transform(const DrawData& drawData) const;
void draw_with_predraw_data(SkCanvas* canvas, const DrawData& drawData, const PreDrawData& preDrawData) const;
PreDrawData calculate_predraw_data(const DrawData& drawData) const;
TransformData calculate_draw_transform(const DrawData& drawData) const;
void commit_update(DrawingProgram& drawP);
void commit_transform_dont_invalidate_cache(); // Must be thread safe
void commit_transform(DrawingProgram& drawP);
@ -78,7 +83,7 @@ class CanvasComponentContainer {
unsigned get_mipmap_level(const DrawData& drawData) const;
CanvasComponent* allocate_comp(CanvasComponentType type);
void canvas_do_transform(SkCanvas* canvas, const TransformDrawData& transformData) const;
void canvas_do_transform(SkCanvas* canvas, const TransformData& transformData) const;
void calculate_world_bounds();
std::optional<SCollision::AABB<WorldScalar>> worldAABB;

View file

@ -33,7 +33,7 @@ std::optional<Vector4f> EllipseCanvasComponent::get_stroke_color() const {
return d.strokeColor;
}
void EllipseCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData) const {
void EllipseCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const {
SkPaint p;
if(d.fillStrokeMode == 0 || d.fillStrokeMode == 2) {
p.setStyle(SkPaint::kFill_Style);

View file

@ -29,7 +29,7 @@ class EllipseCanvasComponent : public CanvasComponent {
private:
void create_draw_data();
void create_collider();
virtual void draw(SkCanvas* canvas, const DrawData& drawData) const override;
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const override;
virtual void initialize_draw_data(DrawingProgram& drawP) override;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const override;
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;

View file

@ -86,7 +86,7 @@ void ImageCanvasComponent::get_used_resources(std::unordered_set<NetworkingObjec
resourceSet.emplace(d.imageID);
}
void ImageCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData) const {
void ImageCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const {
ResourceDisplay* display = drawData.rMan->get_display_data(d.imageID);
if(d.editing) {
canvas->saveLayerAlphaf(nullptr, 1.0f);

View file

@ -35,7 +35,7 @@ class ImageCanvasComponent : public CanvasComponent {
bool contains_actual_image();
private:
virtual void draw(SkCanvas* canvas, const DrawData& drawData) const override;
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const override;
virtual void initialize_draw_data(DrawingProgram& drawP) override;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const override;
void create_collider();

View file

@ -41,7 +41,7 @@ std::unique_ptr<CanvasComponent> RectangleCanvasComponent::get_data_copy() const
return toRet;
}
void RectangleCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData) const {
void RectangleCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const {
if(d.fillStrokeMode == 0 || d.fillStrokeMode == 2) {
SkPaint p;
p.setStyle(SkPaint::kFill_Style);

View file

@ -29,7 +29,7 @@ class RectangleCanvasComponent : public CanvasComponent {
} d;
private:
virtual void draw(SkCanvas* canvas, const DrawData& drawData) const override;
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const override;
virtual void initialize_draw_data(DrawingProgram& drawP) override;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const override;
void create_draw_data();

View file

@ -79,7 +79,7 @@ void TextBoxCanvasComponent::init_text_box(DrawingProgram& drawP) {
textBox->set_width(d.p2.x() - d.p1.x() - TEXTBOX_PADDING * 2.0f);
}
void TextBoxCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData) const {
void TextBoxCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const {
SkRect clipR = SkRect::MakeLTRB(d.p1.x(), d.p1.y(), d.p2.x(), d.p2.y());
if(d.editing) {
SkPaint p;

View file

@ -30,7 +30,7 @@ class TextBoxCanvasComponent : public CanvasComponent {
private:
friend class TextBoxEditTool;
virtual void draw(SkCanvas* canvas, const DrawData& drawData) const override;
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const override;
virtual void initialize_draw_data(DrawingProgram& drawP) override;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const override;
void create_collider();

View file

@ -449,32 +449,32 @@ void DrawingProgramCache::recursive_draw_layer_item_to_canvas(const DrawingProgr
parallel_loop_container(nodesToDraw, [&](auto& node) {
std::for_each(node->components.begin(), node->components.end(), [&](auto& c) {
if(c->obj->parentLayer == &layerListItem && (!drawBounds.has_value() || SCollision::collide(drawBounds.value(), c->obj->get_world_bounds())) && c->obj->should_draw(drawData))
c->obj->transformHolder = c->obj->calculate_draw_transform(drawData);
c->obj->preDrawDataHolder = c->obj->calculate_predraw_data(drawData);
else
c->obj->transformHolder = std::nullopt;
c->obj->preDrawDataHolder = std::nullopt;
});
});
parallel_loop_container(unsortedComponents, [&](auto& c) {
if(c->obj->parentLayer == &layerListItem && (!drawBounds.has_value() || SCollision::collide(drawBounds.value(), c->obj->get_world_bounds())) && c->obj->should_draw(drawData))
c->obj->transformHolder = c->obj->calculate_draw_transform(drawData);
c->obj->preDrawDataHolder = c->obj->calculate_predraw_data(drawData);
else
c->obj->transformHolder = std::nullopt;
c->obj->preDrawDataHolder = std::nullopt;
});
for(auto& node : nodesToDraw) {
for(auto& c : node->components) {
if(c->obj->transformHolder.has_value())
if(c->obj->preDrawDataHolder.has_value())
compsToDraw.emplace_back(c);
}
}
for(auto& c : unsortedComponents) {
if(c->obj->transformHolder.has_value())
if(c->obj->preDrawDataHolder.has_value())
compsToDraw.emplace_back(c);
}
std::sort(compsToDraw.begin(), compsToDraw.end(), [](auto& a, auto& b) {
return a->pos < b->pos;
});
for(auto& c : compsToDraw)
c->obj->draw_with_transform(canvas, drawData, c->obj->transformHolder.value());
c->obj->draw_with_predraw_data(canvas, drawData, c->obj->preDrawDataHolder.value());
}
canvas->restore();
}