Manually track render-objects for leaks
diff --git a/include/rive/renderer.hpp b/include/rive/renderer.hpp
index 5d23576..85559b9 100644
--- a/include/rive/renderer.hpp
+++ b/include/rive/renderer.hpp
@@ -31,7 +31,8 @@
const size_t m_Count;
public:
- RenderBuffer(size_t count) : m_Count(count) {}
+ RenderBuffer(size_t count);
+ ~RenderBuffer() override;
size_t count() const { return m_Count; }
};
@@ -53,10 +54,17 @@
* It is common that a shader may be created with a 'localMatrix'. If this is
* not null, then it is applied to the shader's domain before the Renderer's CTM.
*/
- class RenderShader : public RefCnt {};
+ class RenderShader : public RefCnt {
+ public:
+ RenderShader();
+ ~RenderShader() override;
+ };
class RenderPaint {
public:
+ RenderPaint();
+ virtual ~RenderPaint();
+
virtual void style(RenderPaintStyle style) = 0;
virtual void color(ColorInt value) = 0;
virtual void thickness(float value) = 0;
@@ -64,8 +72,6 @@
virtual void cap(StrokeCap value) = 0;
virtual void blendMode(BlendMode value) = 0;
virtual void shader(rcp<RenderShader>) = 0;
-
- virtual ~RenderPaint() {}
};
class RenderImage {
@@ -74,7 +80,9 @@
int m_Height = 0;
public:
- virtual ~RenderImage() {}
+ RenderImage();
+ virtual ~RenderImage();
+
int width() const { return m_Width; }
int height() const { return m_Height; }
@@ -85,6 +93,9 @@
class RenderPath : public CommandPath {
public:
+ RenderPath();
+ ~RenderPath() override;
+
RenderPath* renderPath() override { return this; }
void addPath(CommandPath* path, const Mat2D& transform) override {
addRenderPath(path->renderPath(), transform);
diff --git a/skia/viewer/src/main.cpp b/skia/viewer/src/main.cpp
index 16d1096..204e539 100644
--- a/skia/viewer/src/main.cpp
+++ b/skia/viewer/src/main.cpp
@@ -29,6 +29,8 @@
#include "imgui/backends/imgui_impl_glfw.h"
#include "imgui/backends/imgui_impl_opengl3.h"
+#include "../src/render_counter.hpp"
+
#include <cmath>
#include <stdio.h>
@@ -39,6 +41,14 @@
std::unique_ptr<rive::ArtboardInstance> artboardInstance;
std::unique_ptr<rive::Scene> currentScene;
+static void delete_file() {
+ currentScene = nullptr;
+ artboardInstance = nullptr;
+ currentFile = nullptr;
+
+ rive::RenderCounter::globalCounter().dump("After deleting file");
+}
+
// ImGui wants raw pointers to names, but our public API returns
// names as strings (by value), so we cache these names each time we
// load a file
@@ -129,6 +139,8 @@
if (currentScene) {
currentScene->inputCount();
}
+
+ rive::RenderCounter::globalCounter().dump("After loading file");
}
void initAnimation(int index) {
@@ -153,6 +165,8 @@
currentScene = artboardInstance->animationAt(index);
currentScene->inputCount();
}
+
+ rive::RenderCounter::globalCounter().dump("After loading file");
}
rive::Mat2D gInverseViewTransform;
@@ -416,6 +430,8 @@
glfwPollEvents();
}
+ delete_file();
+
// Cleanup Skia.
surface = nullptr;
context = nullptr;
diff --git a/src/render_counter.hpp b/src/render_counter.hpp
new file mode 100644
index 0000000..3481e5e
--- /dev/null
+++ b/src/render_counter.hpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifndef _RIVE_RENDERER_COUNTER_HPP_
+#define _RIVE_RENDERER_COUNTER_HPP_
+
+#include "rive/rive_types.hpp"
+
+namespace rive {
+
+enum RenderCounterType {
+ kBuffer,
+ kPath,
+ kPaint,
+ kShader,
+ kImage,
+
+ kLastCounterType = kImage,
+};
+
+struct RenderCounter {
+ int counts[RenderCounterType::kLastCounterType + 1] = {};
+
+ void update(RenderCounterType ct, int delta) {
+ assert(delta == 1 || delta == -1);
+ counts[ct] += delta;
+ }
+
+ void dump(const char label[] = nullptr) const;
+
+ static RenderCounter& globalCounter();
+};
+
+} // namespace rive
+
+#endif
diff --git a/src/renderer.cpp b/src/renderer.cpp
index 37c3a89..87111ee 100644
--- a/src/renderer.cpp
+++ b/src/renderer.cpp
@@ -67,3 +67,46 @@
const float c = std::cos(radians);
this->transform(Mat2D(c, s, -s, c, 0, 0));
}
+
+/////////////////////////////////////////
+
+#include "../src/render_counter.hpp"
+
+static RenderCounter gCounter;
+
+const char* gCounterNames[] = {
+ "buffer", "path", "paint", "shader", "image",
+};
+
+void RenderCounter::dump(const char label[]) const {
+ if (label == nullptr) {
+ label = "RenderCounters";
+ }
+ printf("%s:", label);
+ for (int i = 0; i <= kLastCounterType; ++i) {
+ printf(" [%s]:%d", gCounterNames[i], counts[i]);
+ }
+ printf("\n");
+}
+
+RenderCounter& RenderCounter::globalCounter() { return gCounter; }
+
+RenderBuffer::RenderBuffer(size_t count) : m_Count(count) {
+ gCounter.update(kBuffer, 1);
+}
+
+RenderBuffer::~RenderBuffer() {
+ gCounter.update(kBuffer, -1);
+}
+
+RenderShader::RenderShader() { gCounter.update(kShader, 1); }
+RenderShader::~RenderShader() { gCounter.update(kShader, -1); }
+
+RenderPaint::RenderPaint() { gCounter.update(kPaint, 1); }
+RenderPaint::~RenderPaint() { gCounter.update(kPaint, -1); }
+
+RenderImage::RenderImage() { gCounter.update(kImage, 1); }
+RenderImage::~RenderImage() { gCounter.update(kImage, -1); }
+
+RenderPath::RenderPath() { gCounter.update(kPath, 1); }
+RenderPath::~RenderPath() { gCounter.update(kPath, -1); }
diff --git a/test/file_test.cpp b/test/file_test.cpp
index 05d0e5e..b8cd48b 100644
--- a/test/file_test.cpp
+++ b/test/file_test.cpp
@@ -8,6 +8,7 @@
#include <cstdio>
TEST_CASE("file can be read", "[file]") {
+ RenderObjectLeakChecker checker;
auto file = ReadRiveFile("../../test/assets/two_artboards.riv");
// Default artboard should be named Two.
@@ -18,6 +19,7 @@
}
TEST_CASE("file with animation can be read", "[file]") {
+ RenderObjectLeakChecker checker;
auto file = ReadRiveFile("../../test/assets/juice.riv");
auto artboard = file->artboard();
@@ -42,6 +44,7 @@
}
TEST_CASE("artboards can be counted and accessed via index or name", "[file]") {
+ RenderObjectLeakChecker checker;
auto file = ReadRiveFile("../../test/assets/dependency_test.riv");
// The artboards caqn be counted
@@ -55,6 +58,7 @@
}
TEST_CASE("dependencies are as expected", "[file]") {
+ RenderObjectLeakChecker checker;
// ┌────┐
// │Blue│
// └────┘
@@ -112,6 +116,7 @@
}
TEST_CASE("long name in object is parsed correctly", "[file]") {
+ RenderObjectLeakChecker checker;
auto file = ReadRiveFile("../../test/assets/long_name.riv");
auto artboard = file->artboard();
diff --git a/test/image_asset_test.cpp b/test/image_asset_test.cpp
index e65d25f..ae0eb4b 100644
--- a/test/image_asset_test.cpp
+++ b/test/image_asset_test.cpp
@@ -12,6 +12,7 @@
#include <cstdio>
TEST_CASE("image assets loads correctly", "[assets]") {
+ RenderObjectLeakChecker checker;
auto file = ReadRiveFile("../../test/assets/walle.riv");
auto node = file->artboard()->find("walle");
@@ -42,6 +43,7 @@
}
TEST_CASE("out of band image assets loads correctly", "[assets]") {
+ RenderObjectLeakChecker checker;
rive::NoOpFactory gEmptyFactory;
std::string filename = "../../test/assets/out_of_band/walle.riv";
diff --git a/test/rive_file_reader.hpp b/test/rive_file_reader.hpp
index 4294290..1076359 100644
--- a/test/rive_file_reader.hpp
+++ b/test/rive_file_reader.hpp
@@ -4,6 +4,7 @@
#include <rive/file.hpp>
#include "rive_testing.hpp"
#include "no_op_factory.hpp"
+#include "../src/render_counter.hpp"
static inline std::unique_ptr<rive::File>
ReadRiveFile(const char path[],
@@ -32,4 +33,21 @@
return file;
}
+class RenderObjectLeakChecker {
+ rive::RenderCounter m_Before;
+
+public:
+ RenderObjectLeakChecker() : m_Before(rive::RenderCounter::globalCounter()) {}
+ ~RenderObjectLeakChecker() {
+ auto after = rive::RenderCounter::globalCounter();
+ for (int i = 0; i <= rive::RenderCounterType::kLastCounterType; ++i) {
+ if (after.counts[i] != m_Before.counts[i]) {
+ m_Before.dump("before");
+ after.dump("after");
+ REQUIRE(false);
+ }
+ }
+ }
+};
+
#endif