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