From da06a452d3deda40f746ffa81981271240e3b7ab Mon Sep 17 00:00:00 2001
From: namark <namark@disroot.org>
Date: Mon, 27 Jul 2020 04:54:00 +0400
Subject: [PATCH] nanovg framebuffers.

---
 common/simple_vg.cpp  | 106 +++++++++++++++++++++++++++++++++++++++---
 common/simple_vg.h    |  60 ++++++++++++++++++++++--
 common/sketchbook.hpp |  78 ++++++++++++++++++++++++++++---
 3 files changed, 228 insertions(+), 16 deletions(-)

diff --git a/common/simple_vg.cpp b/common/simple_vg.cpp
index 047cb8a..64192ac 100644
--- a/common/simple_vg.cpp
+++ b/common/simple_vg.cpp
@@ -1,5 +1,6 @@
 #include "simple_vg.h"
 #include "simple/support/enum.hpp"
+#include "simple/support/algorithm.hpp"
 
 using namespace simple::vg;
 
@@ -46,12 +47,55 @@ frame canvas::begin_frame(float2 size, float pixelRatio) noexcept
 	return frame(raw.get(), size, pixelRatio);
 }
 
-frame::frame(NVGcontext* context, float2 size, float pixelRatio) noexcept :
+frame canvas::begin_frame(framebuffer& fb) const noexcept
+{
+	return frame(raw.get(), fb);
+}
+
+framebuffer::framebuffer(int2 size, enum flags flags) noexcept :
+	flags(flags),
 	size(size),
-	pixelRatio(pixelRatio),
-	context(context)
+	raw(nullptr)
+{}
+framebuffer::framebuffer(const canvas& canvas, int2 size, enum flags flags) :
+	framebuffer(size, flags)
 {
-	nvgBeginFrame(context, size.x(), size.y(), pixelRatio);
+	create(canvas);
+}
+
+bool framebuffer::create(const canvas& canvas)
+{
+	if(raw)
+		return false;
+
+	raw = decltype(raw)(
+		nvgluCreateFramebuffer(
+			canvas.raw.get(),
+			size.x(), size.y(),
+			support::to_integer(flags)
+		)
+	);
+	return true;
+}
+
+simple::vg::paint framebuffer::paint(simple::support::range<int2> range, float opacity, float angle) const
+{
+	auto size = range.upper() - range.lower();
+	return nvgImagePattern(nullptr,
+		range.lower().x(), range.lower().y(),
+		size.x(), size.y(),
+		angle, raw->image, opacity
+	);
+}
+
+simple::vg::paint framebuffer::paint(float opacity, float angle) const
+{
+	return paint({int2::zero(), size}, opacity, angle);
+}
+
+void framebuffer::deleter::operator()(NVGLUframebuffer* raw) const noexcept
+{
+	nvgluDeleteFramebuffer(raw);
 }
 
 paint::paint(NVGpaint raw) noexcept : raw(raw) {}
@@ -67,9 +111,45 @@ paint paint::radial_gradient(float2 center, rangef radius, support::range<rgba_v
 	));
 }
 
+paint paint::radial_gradient(range2f bounds, rangef radius, support::range<rgba_vector> colors) noexcept
+{
+	auto size = (bounds.upper() - bounds.lower())/2;
+	return radial_gradient(
+		bounds.lower() + size,
+		radius * size.x(),
+		colors
+	);
+}
+
+frame::frame(NVGcontext* context, float2 size, float pixelRatio) noexcept :
+	size(size),
+	pixelRatio(pixelRatio),
+	buffer(nullptr),
+	context(context)
+{
+	nvgBeginFrame(context, size.x(), size.y(), pixelRatio);
+}
+
+frame::frame(NVGcontext* context, const framebuffer& fb) noexcept :
+	size(fb.size),
+	pixelRatio(1),
+	buffer(&fb),
+	context(context)
+{
+	nvgluBindFramebuffer(buffer->raw.get());
+	glClearColor(0,0,0,0);
+	glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+	glViewport(
+		0,0,
+		buffer->size.x(), buffer->size.y()
+	);
+	nvgBeginFrame(context, size.x(), size.y(), pixelRatio);
+}
+
 frame::frame(frame&& other) noexcept :
 	size(other.size),
 	pixelRatio(other.pixelRatio),
+	buffer(other.buffer),
 	context(other.context)
 {
 	other.context = nullptr;
@@ -79,6 +159,8 @@ frame::~frame() noexcept
 {
 	if(context)
 		nvgEndFrame(context);
+	if(buffer)
+		nvgluBindFramebuffer(nullptr);
 }
 
 sketch frame::begin_sketch() noexcept
@@ -133,8 +215,20 @@ sketch& sketch::rectangle(const range2f& bounds) noexcept
 
 sketch& sketch::line(float2 from, float2 to) noexcept
 {
-	nvgMoveTo(context, from.x(), from.y());
-	nvgLineTo(context, to.x(), to.y());
+	move(from);
+	vertex(to);
+	return *this;
+}
+
+sketch& sketch::move(float2 to) noexcept
+{
+	nvgMoveTo(context, to.x(), to.y());
+	return *this;
+}
+
+sketch& sketch::vertex(float2 v) noexcept
+{
+	nvgLineTo(context, v.x(), v.y());
 	return *this;
 }
 
diff --git a/common/simple_vg.h b/common/simple_vg.h
index 41eb6be..885df64 100644
--- a/common/simple_vg.h
+++ b/common/simple_vg.h
@@ -11,6 +11,7 @@
 namespace simple::vg
 {
 	using float2 = geom::vector<float, 2>;
+	using int2 = geom::vector<int, 2>;
 	using graphical::rgb_vector;
 	using graphical::rgba_vector;
 	using graphical::rgb_pixel;
@@ -20,6 +21,10 @@ namespace simple::vg
 	using rect2f = geom::segment<float2>;
 	using anchored_rect2f = geom::anchored_segment<float2>;
 
+	class frame;
+	class framebuffer;
+	class sketch;
+
 	class paint
 	{
 		NVGpaint raw;
@@ -28,13 +33,13 @@ namespace simple::vg
 		public:
 		paint() = delete;
 		static paint radial_gradient(float2 center, rangef radius, support::range<rgba_vector>) noexcept;
+		static paint radial_gradient(range2f range, rangef radius, support::range<rgba_vector>) noexcept;
 		static paint linear_gradient() noexcept; // TODO
 		static paint range_gradient() noexcept; // TODO
 		friend class sketch;
+		friend class framebuffer;
 	};
 
-	class frame;
-
 	class canvas
 	{
 		public:
@@ -53,6 +58,7 @@ namespace simple::vg
 		canvas& clear(const rgba_pixel& color) noexcept;
 
 		frame begin_frame(float2 size, float pixelRatio = 1) noexcept;
+		frame begin_frame(framebuffer&) const noexcept;
 
 		private:
 
@@ -62,13 +68,51 @@ namespace simple::vg
 		};
 
 		std::unique_ptr<NVGcontext, deleter> raw;
+
+		friend class framebuffer;
 	};
 	using ::operator |;
 	using ::operator &;
 	using ::operator &&;
 
-	class sketch;
+	class framebuffer
+	{
+		public:
+		enum class flags :
+			std::underlying_type_t<NVGimageFlags>
+		{
+			none = 0,
+			mipmap = NVG_IMAGE_GENERATE_MIPMAPS,
+			repeat_x = NVG_IMAGE_REPEATX,
+			repeat_y = NVG_IMAGE_REPEATY,
+			flip_y = NVG_IMAGE_FLIPY,
+			premultiplied = NVG_IMAGE_PREMULTIPLIED,
+			nearest = NVG_IMAGE_NEAREST
+		};
+
+		const flags flags;
+		const int2 size;
+
+		framebuffer(int2 size, enum flags = flags::none) noexcept;
+		framebuffer(const canvas&, int2 size, enum flags = flags::none);
 
+		bool create(const canvas&);
+
+		vg::paint paint(support::range<int2>, float opacity = 1, float angle = 0) const;
+		vg::paint paint(float opacity = 1, float angle = 0) const;
+		private:
+
+		struct deleter
+		{
+			void operator()(NVGLUframebuffer*) const noexcept;
+		};
+
+		std::unique_ptr<NVGLUframebuffer, deleter> raw;
+
+		friend class frame;
+	};
+
+	// TODO: prevent two frames with same context from coexisting
 	class frame
 	{
 		public:
@@ -78,9 +122,11 @@ namespace simple::vg
 			frame(frame&&) noexcept;
 			const float2 size;
 			const float pixelRatio;
+			const framebuffer * const buffer;
 		private:
 			NVGcontext* context;
 			frame(NVGcontext*, float2 size, float pixelRatio = 1) noexcept;
+			frame(NVGcontext*, const framebuffer&) noexcept;
 			friend class canvas;
 	};
 
@@ -106,6 +152,11 @@ namespace simple::vg
 			sketch& line(float2 from, float2 to) noexcept;
 			sketch& arc(float2 center, rangef angle, float radius) noexcept;
 			sketch& arc(float2 center, float2 radius, float tau_factor, float anchor = 0) noexcept; // TODO
+			sketch& move(float2) noexcept;
+			sketch& vertex(float2) noexcept;
+
+			sketch& scale(float2) noexcept;
+			sketch& reset_matrix() noexcept;
 
 			sketch& fill(const paint&) noexcept;
 			sketch& fill(const rgba_vector&) noexcept;
@@ -134,4 +185,7 @@ namespace simple::vg
 template<> struct simple::support::define_enum_flags_operators<simple::vg::canvas::flags>
 	: std::true_type {};
 
+template<> struct simple::support::define_enum_flags_operators<enum simple::vg::framebuffer::flags>
+	: std::true_type {};
+
 #endif /* end of include guard */
diff --git a/common/sketchbook.hpp b/common/sketchbook.hpp
index eb93813..48e6bcb 100644
--- a/common/sketchbook.hpp
+++ b/common/sketchbook.hpp
@@ -8,6 +8,7 @@
 #include <vector>
 #include <array>
 #include <queue>
+#include <list>
 
 #include "simple/support.hpp"
 #include "simple/graphical.hpp"
@@ -16,13 +17,18 @@
 #include "simple/interactive/event.h"
 
 #include "simple_vg.h"
-#include "simple_vg.cpp"
+#include "simple_vg.cpp" // TODO: woops, don't do this
 #include "math.hpp"
 
 #if defined __EMSCRIPTEN__
 #include <emscripten.h>
 #endif
 
+#if defined __ANDROID__
+#undef main
+#endif
+
+using namespace simple::vg;
 using namespace std::chrono_literals;
 using namespace simple;
 using namespace graphical::color_literals;
@@ -44,6 +50,7 @@ using scancode = interactive::scancode;
 using keycode = interactive::keycode;
 using mouse_button = interactive::mouse_button;
 using common::lerp;
+using simple::support::make_range;
 
 constexpr int max_int = std::numeric_limits<int>::max();
 
@@ -51,6 +58,7 @@ support::random::engine::tiny<unsigned> tiny_rand{std::random_device{}};
 support::random::distribution::naive_int<int> tiny_int_dist{0, max_int};
 support::random::distribution::naive_real<float> tiny_float_dist{0, 1};
 
+constexpr
 auto trand_int(decltype(tiny_int_dist)::param_type range = {0, max_int})
 { return tiny_int_dist(tiny_rand, range); };
 
@@ -95,6 +103,16 @@ class Program
 	std::array<wave, 32> waves = {};
 	std::mutex dam;
 
+	std::list<std::pair<framebuffer, draw_fun>> framebuffers;
+	void create_framebuffers(const canvas& canvas)
+	{
+		for(auto&& [fb, draw] : framebuffers)
+		{
+			fb.create(canvas);
+			draw(canvas.begin_frame(fb));
+		}
+	}
+
 	bool run = true;
 	Program(const int argc, const char * const * const argv) : argc(argc), argv(argv) {}
 
@@ -106,6 +124,7 @@ class Program
 	std::string name = "";
 	int2 size = int2(400,400);
 	bool fullscreen = false;
+	graphical::display::mode display;
 
 
 	// nop works ok with function pointers without having to specify template params :/
@@ -152,8 +171,30 @@ class Program
 		current.remaining = current.total;
 	}
 
+	const framebuffer& request_framebuffer(int2 size, draw_fun draw,enum framebuffer::flags flags = framebuffer::flags::none)
+	{
+		framebuffers.emplace_back(
+			std::make_pair(
+				framebuffer(size, flags),
+				std::move(draw)
+			)
+		);
+		return framebuffers.back().first;
+	}
+
+	void remove_framebuffer(const framebuffer& framebuffer)
+	{
+		framebuffers.erase(std::find_if(
+			framebuffers.begin(),
+			framebuffers.end(),
+			[&](auto&& fb)
+			{
+				return &fb.first == &framebuffer;
+			}
+		));
+	}
 
-	friend int main(int argc, char const* argv[]);
+	friend int main(int argc, char* argv[]);
 };
 
 inline void process_events(Program&);
@@ -163,12 +204,15 @@ inline void process_events(Program&);
 
 void start(Program&);
 
-int main(int argc, char const* argv[]) try
+int main(int argc, char* argv[]) try
 {
 	Program program{argc, argv};
-	start(program);
+
 
 	graphical::initializer graphics;
+	program.display = (*graphics.displays().begin()).current_mode();
+	start(program);
+
 	sdlcore::initializer interactions(sdlcore::system_flag::event);
 
 	// TODO: make optional
@@ -235,13 +279,17 @@ int main(int argc, char const* argv[]) try
 		std::cout << "vsync didn't work"  << '\n';
 		program.frametime = framerate<60>::frametime;
 	}
-	float2 win_size = float2(win.size());
 
+#if !defined NANOVG_GLES2 && !defined NANOVG_GLES3
 	glewInit();
+#endif
 	auto canvas = vg::canvas(vg::canvas::flags::antialias | vg::canvas::flags::stencil_strokes);
 	canvas.clear();
 
-	program.draw_once(canvas.begin_frame(win_size));
+	program.create_framebuffers(canvas);
+	auto stolen_framebuffers = std::move(program.framebuffers);
+	glViewport(0,0, win.size().x(), win.size().y());
+	program.draw_once(canvas.begin_frame(float2(win.size())));
 
 	auto& now = Program::clock::now;
 	auto frame_start = now();
@@ -251,7 +299,8 @@ int main(int argc, char const* argv[]) try
 		frame_start = now();
 
 		process_events(program);
-		program.draw_loop(canvas.begin_frame(win_size), delta_time);
+		canvas.clear();
+		program.draw_loop(canvas.begin_frame(float2(win.size())), delta_time);
 		win.update();
 	};
 #if defined __EMSCRIPTEN__
@@ -317,6 +366,21 @@ void process_events(Program& program)
 		{
 			program.end();
 		},
+		[](const window_size_changed& w)
+		{
+			glViewport(0,0,
+				w.data.value.x(),
+				w.data.value.y()
+			);
+		},
 		[](auto) { }
 	}, *event);
 }
+
+
+
+#if defined __ANDROID__
+extern "C" __attribute__((visibility("default")))
+int SDL_main(int argc, char* argv[]) { return main(argc, argv); }
+#endif
+
-- 
GitLab