From b298859f1e0748822f4f087d4db60aaeecf6eab3 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Fri, 15 Oct 2021 21:37:48 +0300 Subject: [PATCH] Support stencils in ui::painter --- libs/ui/include/psemek/ui/painter.hpp | 4 + libs/ui/include/psemek/ui/painter_impl.hpp | 4 + libs/ui/source/painter_impl.cpp | 141 ++++++++++++++++++++- 3 files changed, 147 insertions(+), 2 deletions(-) diff --git a/libs/ui/include/psemek/ui/painter.hpp b/libs/ui/include/psemek/ui/painter.hpp index cd031dd8..3f7e2933 100644 --- a/libs/ui/include/psemek/ui/painter.hpp +++ b/libs/ui/include/psemek/ui/painter.hpp @@ -17,6 +17,10 @@ namespace psemek::ui virtual void draw_image(geom::box const & rect, gfx::texture_2d const & tex, gfx::color_rgba const & color = {0, 0, 0, 0}) = 0; virtual void draw_subimage(geom::box const & rect, gfx::texture_2d const & tex, geom::box const & part, gfx::color_rgba const & color = {0, 0, 0, 0}) = 0; + virtual void begin_stencil() = 0; + virtual void commit_stencil() = 0; + virtual void end_stencil() = 0; + virtual ~painter() {} }; diff --git a/libs/ui/include/psemek/ui/painter_impl.hpp b/libs/ui/include/psemek/ui/painter_impl.hpp index d0319edc..ad0df201 100644 --- a/libs/ui/include/psemek/ui/painter_impl.hpp +++ b/libs/ui/include/psemek/ui/painter_impl.hpp @@ -21,6 +21,10 @@ namespace psemek::ui void draw_image(geom::box const & rect, gfx::texture_2d const & tex, gfx::color_rgba const & color) override; void draw_subimage(geom::box const & rect, gfx::texture_2d const & tex, geom::box const & part, gfx::color_rgba const & color = {0, 0, 0, 0}) override; + void begin_stencil() override; + void commit_stencil() override; + void end_stencil() override; + void render(geom::matrix const & transform); private: diff --git a/libs/ui/source/painter_impl.cpp b/libs/ui/source/painter_impl.cpp index 0a16f0f9..e7f06949 100644 --- a/libs/ui/source/painter_impl.cpp +++ b/libs/ui/source/painter_impl.cpp @@ -183,6 +183,23 @@ void main() return b1.texture == b2.texture; } + struct stencil_batch + { + std::optional enable_stencil; + std::optional enable_depth; + std::optional enable_color; + + GLenum sfail, dfail, dpass; + GLenum func; + GLint ref; + GLuint mask; + }; + + bool operator == (stencil_batch const &, stencil_batch const &) + { + return false; + } + } struct painter_impl::impl @@ -198,7 +215,12 @@ void main() gfx::program textured_program; gfx::mesh textured_mesh; - std::vector> batches; + int stencil_level = 0; + static constexpr int max_stencil_level = 8; + + geom::box draw_bbox; + + std::vector> batches; template Batch & batch(Batch && n) @@ -251,6 +273,8 @@ void main() batch.vertices.push_back({rect.corner(1.f, 1.f), depth, color}); batch.indices.insert(batch.indices.end(), {base + 0, base + 1, base + 2, base + 2, base + 1, base + 3}); + + impl().draw_bbox |= rect; } void painter_impl::draw_triangle(geom::triangle> const & tri, gfx::color_rgba const & color) @@ -265,6 +289,10 @@ void main() batch.vertices.push_back({tri[2], depth, color}); batch.indices.insert(batch.indices.end(), {base + 0, base + 1, base + 2}); + + impl().draw_bbox |= tri[0]; + impl().draw_bbox |= tri[1]; + impl().draw_bbox |= tri[2]; } void painter_impl::draw_glyph(font const & f, char32_t c, geom::box const & rect, gfx::color_rgba const & color) @@ -301,6 +329,8 @@ void main() batch.vertices.push_back({round(rect.corner(1.f, 1.f)), depth, color, tc(1.f, 1.f)}); batch.indices.insert(batch.indices.end(), {base + 0, base + 1, base + 2, base + 2, base + 1, base + 3}); + + impl().draw_bbox |= rect; } void painter_impl::draw_image(geom::box const & rect, gfx::texture_2d const & tex, gfx::color_rgba const & color) @@ -324,6 +354,82 @@ void main() batch.vertices.push_back({rect.corner(1.f, 1.f), depth, color, part.corner(1.f, 1.f)}); batch.indices.insert(batch.indices.end(), {base + 0, base + 1, base + 2, base + 2, base + 1, base + 3}); + + impl().draw_bbox |= rect; + } + + void painter_impl::begin_stencil() + { + if (impl().stencil_level == impl().max_stencil_level) + throw std::runtime_error("Only 8 recursive stencil levels are supported"); + + auto & batch = impl().batch({}); + if (impl().stencil_level == 0) + batch.enable_stencil = true; + batch.enable_color = false; + batch.enable_depth = false; + + batch.func = gl::EQUAL; + batch.ref = 2 * (1 << impl().stencil_level) - 1; + batch.mask = (1 << impl().stencil_level) - 1; + + batch.sfail = gl::KEEP; + batch.dfail = gl::KEEP; + batch.dpass = gl::REPLACE; + + ++impl().stencil_level; + } + + void painter_impl::commit_stencil() + { + auto & batch = impl().batch({}); + batch.enable_color = true; + batch.enable_depth = true; + + batch.func = gl::EQUAL; + batch.ref = (1 << impl().stencil_level) - 1; + batch.mask = (1 << impl().stencil_level) - 1; + + batch.sfail = gl::KEEP; + batch.dfail = gl::KEEP; + batch.dpass = gl::KEEP; + } + + void painter_impl::end_stencil() + { + { + auto & clear_batch = impl().batch({}); + clear_batch.enable_color = false; + clear_batch.enable_depth = false; + + clear_batch.func = gl::EQUAL; + clear_batch.ref = (1 << impl().stencil_level) / 2 - 1; + clear_batch.mask = (1 << impl().stencil_level) / 2 - 1; + + clear_batch.sfail = gl::KEEP; + clear_batch.dfail = gl::KEEP; + clear_batch.dpass = gl::REPLACE; + } + + draw_rect(impl().draw_bbox, {0, 0, 0, 255}); + + { + auto & batch = impl().batch({}); + if (impl().stencil_level == 1) + batch.enable_stencil = false; + batch.enable_color = true; + batch.enable_depth = true; + + batch.func = gl::NEVER; + batch.ref = 0; + batch.mask = 0; + + batch.sfail = gl::KEEP; + batch.dfail = gl::KEEP; + batch.dpass = gl::KEEP; + } + + --impl().stencil_level; } void painter_impl::render(geom::matrix const & transform) @@ -337,7 +443,8 @@ void main() gl::ActiveTexture(gl::TEXTURE0); - auto batch_visitor = util::overload([&](colored_batch const & b){ + auto batch_visitor = util::overload( + [&](colored_batch const & b){ impl().colored_program.bind(); impl().colored_mesh.load(b.vertices, b.indices, gl::TRIANGLES); impl().colored_mesh.draw(); @@ -357,6 +464,34 @@ void main() b.texture->bind(); impl().textured_mesh.load(b.vertices, b.indices, gl::TRIANGLES); impl().textured_mesh.draw(); + }, + [&](stencil_batch const & b){ + if (b.enable_stencil) + { + if (*b.enable_stencil) + gl::Enable(gl::STENCIL_TEST); + else + gl::Disable(gl::STENCIL_TEST); + } + + if (b.enable_depth) + { + if (*b.enable_depth) + gl::DepthMask(gl::TRUE); + else + gl::DepthMask(gl::FALSE); + } + + if (b.enable_color) + { + if (*b.enable_color) + gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE); + else + gl::ColorMask(gl::FALSE, gl::FALSE, gl::FALSE, gl::FALSE); + } + + gl::StencilOp(b.sfail, b.dfail, b.dpass); + gl::StencilFunc(b.func, b.ref, b.mask); }); for (auto const & b : impl().batches) @@ -364,6 +499,8 @@ void main() impl().depth = 0; impl().batches.clear(); + impl().stencil_level = 0; + impl().draw_bbox = {}; } }