#include #include #include #include #include #include #include namespace psemek::ui { namespace { char const colored_vs[] = R"(#version 330 uniform mat4 u_transform; layout (location = 0) in vec2 in_position; layout (location = 1) in float in_depth; layout (location = 2) in vec4 in_color; out vec4 color; void main() { gl_Position = u_transform * vec4(in_position, 1.0 - float(in_depth) / 16777216.0 * 2.0, 1.0); color = in_color; } )"; char const colored_fs[] = R"(#version 330 in vec4 color; out vec4 out_color; void main() { out_color = color; } )"; char const textured_vs[] = R"(#version 330 uniform mat4 u_transform; uniform vec2 u_texture_size; layout (location = 0) in vec2 in_position; layout (location = 1) in float in_depth; layout (location = 2) in vec4 in_color; layout (location = 3) in vec2 in_texcoord; out vec4 color; out vec2 texcoord; void main() { gl_Position = u_transform * vec4(in_position, 1.0 - float(in_depth) / 16777216.0 * 2.0, 1.0); color = in_color; texcoord = in_texcoord / u_texture_size; } )"; char const textured_mix_fs[] = R"(#version 330 uniform sampler2D u_texture; in vec2 texcoord; in vec4 color; out vec4 out_color; void main() { vec4 tex_color = texture(u_texture, texcoord); if (tex_color.a == 0.0) discard; out_color = vec4(mix(tex_color.rgb, color.rgb, color.a), tex_color.a); } )"; char const textured_multiply_fs[] = R"(#version 330 uniform sampler2D u_texture; in vec2 texcoord; in vec4 color; out vec4 out_color; void main() { vec4 tex_color = texture(u_texture, texcoord); if (tex_color.a == 0.0) discard; out_color = tex_color * color; } )"; char const msdf_vs[] = R"(#version 330 uniform mat4 u_transform; uniform vec2 u_texture_size; layout (location = 0) in vec2 in_position; layout (location = 1) in float in_depth; layout (location = 2) in vec4 in_color; layout (location = 3) in vec2 in_texcoord; out vec4 color; out vec2 texcoord; void main() { gl_Position = u_transform * vec4(in_position, 1.0 - float(in_depth) / 16777216.0 * 2.0, 1.0); color = in_color; texcoord = in_texcoord / u_texture_size; } )"; char const msdf_fs[] = R"(#version 330 uniform sampler2D u_texture; uniform float u_sdf_scale; in vec2 texcoord; in vec4 color; out vec4 out_color; float median(vec3 v) { return max(min(v.r, v.g), min(max(v.r, v.g), v.b)); } void main() { float sdf_value = u_sdf_scale * (median(texture(u_texture, texcoord).rgb) - 0.5); float aa_step = length(vec2(dFdx(sdf_value), dFdy(sdf_value))) * sqrt(2.0); float alpha = smoothstep(-aa_step * 0.5, aa_step * 0.5, sdf_value); if (alpha == 0.0) discard; out_color = vec4(color.rgb, color.a * alpha); } )"; struct colored_vertex { geom::point position; std::uint32_t depth; gfx::color_rgba color; }; static_assert(sizeof(colored_vertex) == 16); struct colored_batch { std::vector vertices; std::vector indices; }; bool operator == (colored_batch const &, colored_batch const &) { return true; } struct textured_vertex { geom::point position; std::uint32_t depth; gfx::color_rgba color; geom::point texcoords; }; static_assert(sizeof(textured_vertex) == 24); struct textured_batch { gfx::texture_2d const * texture = nullptr; painter::color_mode mode = painter::color_mode::mix; std::vector vertices{}; std::vector indices{}; }; bool operator == (textured_batch const & b1, textured_batch const & b2) { return b1.texture == b2.texture && b1.mode == b2.mode; } struct msdf_vertex { geom::point position; std::uint32_t depth; gfx::color_rgba color; geom::point texcoords; }; static_assert(sizeof(textured_vertex) == 24); struct msdf_batch { gfx::texture_2d const * texture = nullptr; painter::color_mode mode = painter::color_mode::mix; float sdf_scale{0.f}; std::vector vertices{}; std::vector indices{}; }; bool operator == (msdf_batch const & b1, msdf_batch const & b2) { return b1.texture == b2.texture && b1.mode == b2.mode && b1.sdf_scale == b2.sdf_scale; } 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 { std::uint32_t depth = 0; gfx::program colored_program; gfx::mesh colored_mesh; gfx::program textured_mix_program; gfx::program textured_multiply_program; gfx::mesh textured_mesh; gfx::program msdf_program; gfx::mesh msdf_mesh; int stencil_level = 0; static constexpr int max_stencil_level = 255; std::vector> stencil_bbox; geom::box draw_bbox; std::vector> batches; std::size_t max_batch_count = 0; std::size_t max_primitive_count = 0; template Batch & batch(Batch && n) { if (!batches.empty()) { auto b = std::get_if(&batches.back()); if (b && *b == n) return *b; } batches.push_back(std::move(n)); return std::get(batches.back()); } impl(); }; painter_impl::impl::impl() : colored_program{colored_vs, colored_fs} , textured_mix_program{textured_vs, textured_mix_fs} , textured_multiply_program{textured_vs, textured_multiply_fs} , msdf_program{msdf_vs, msdf_fs} { textured_mix_program.bind(); textured_mix_program["u_texture"] = 0; textured_multiply_program.bind(); textured_multiply_program["u_texture"] = 0; msdf_program.bind(); msdf_program["u_texture"] = 0; colored_mesh.setup, std::uint32_t, gfx::normalized>(); textured_mesh.setup, std::uint32_t, gfx::normalized, geom::vector>(); msdf_mesh.setup, std::uint32_t, gfx::normalized, geom::vector>(); } painter_impl::painter_impl() : pimpl_{make_impl()} {} painter_impl::~painter_impl() { log::debug() << "UI painter max batch count: " << impl().max_batch_count; log::debug() << "UI painter max primitive count: " << impl().max_primitive_count; } void painter_impl::draw_rect(geom::box const & rect, gfx::color_rgba const & color) { auto & batch = impl().batch({}); std::uint32_t const depth = impl().depth++; std::uint32_t const base = batch.vertices.size(); batch.vertices.push_back({rect.corner(0.f, 0.f), depth, color}); batch.vertices.push_back({rect.corner(1.f, 0.f), depth, color}); batch.vertices.push_back({rect.corner(0.f, 1.f), depth, color}); 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 & c0, gfx::color_rgba const & c1, gfx::color_rgba const & c2) { auto & batch = impl().batch({}); std::uint32_t const depth = impl().depth++; std::uint32_t const base = batch.vertices.size(); batch.vertices.push_back({tri[0], depth, c0}); batch.vertices.push_back({tri[1], depth, c1}); batch.vertices.push_back({tri[2], depth, c2}); 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_image(geom::box const & rect, gfx::texture_view_2d const & tex, image_options const & options) { auto & batch = impl().batch(textured_batch{tex.texture, options.mode}); std::uint32_t const depth = impl().depth++; std::uint32_t const base = batch.vertices.size(); auto p00 = rect.corner(0.f, 0.f); auto p10 = rect.corner(1.f, 0.f); auto p01 = rect.corner(0.f, 1.f); auto p11 = rect.corner(1.f, 1.f); if (options.rotation != 0.f) { auto const c = rect.center(); p00 = c + geom::rotate(p00 - c, options.rotation); p10 = c + geom::rotate(p10 - c, options.rotation); p01 = c + geom::rotate(p01 - c, options.rotation); p11 = c + geom::rotate(p11 - c, options.rotation); } batch.vertices.push_back({p00, depth, options.color, tex.part.corner(0.f, 0.f)}); batch.vertices.push_back({p10, depth, options.color, tex.part.corner(1.f, 0.f)}); batch.vertices.push_back({p01, depth, options.color, tex.part.corner(0.f, 1.f)}); batch.vertices.push_back({p11, depth, options.color, tex.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::draw_msdf_glyph(geom::box const & rect, gfx::texture_view_2d const & tex, float sdf_scale, image_options const & options) { auto & batch = impl().batch(msdf_batch{tex.texture, options.mode, sdf_scale}); std::uint32_t const depth = impl().depth++; std::uint32_t const base = batch.vertices.size(); auto p00 = rect.corner(0.f, 0.f); auto p10 = rect.corner(1.f, 0.f); auto p01 = rect.corner(0.f, 1.f); auto p11 = rect.corner(1.f, 1.f); if (options.rotation != 0.f) { auto const c = rect.center(); p00 = c + geom::rotate(p00 - c, options.rotation); p10 = c + geom::rotate(p10 - c, options.rotation); p01 = c + geom::rotate(p01 - c, options.rotation); p11 = c + geom::rotate(p11 - c, options.rotation); } batch.vertices.push_back({p00, depth, options.color, tex.part.corner(0.f, 0.f)}); batch.vertices.push_back({p10, depth, options.color, tex.part.corner(1.f, 0.f)}); batch.vertices.push_back({p01, depth, options.color, tex.part.corner(0.f, 1.f)}); batch.vertices.push_back({p11, depth, options.color, tex.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(util::to_string("Only ", impl().max_stencil_level, " 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 = impl().stencil_level; batch.mask = -1; batch.sfail = gl::KEEP; batch.dfail = gl::KEEP; batch.dpass = gl::INCR; ++impl().stencil_level; impl().draw_bbox = {}; } void painter_impl::commit_stencil() { auto & batch = impl().batch({}); batch.enable_color = true; batch.enable_depth = true; batch.func = gl::EQUAL; batch.ref = impl().stencil_level; batch.mask = -1; batch.sfail = gl::KEEP; batch.dfail = gl::KEEP; batch.dpass = gl::KEEP; impl().stencil_bbox.push_back(impl().draw_bbox); } void painter_impl::end_stencil() { --impl().stencil_level; { auto & clear_batch = impl().batch({}); clear_batch.enable_color = false; clear_batch.enable_depth = false; clear_batch.func = gl::LEQUAL; clear_batch.ref = impl().stencil_level; clear_batch.mask = -1; clear_batch.sfail = gl::KEEP; clear_batch.dfail = gl::KEEP; clear_batch.dpass = gl::REPLACE; } draw_rect(impl().stencil_bbox.back(), {0, 0, 0, 255}); { auto & batch = impl().batch({}); if (impl().stencil_level == 0) batch.enable_stencil = false; batch.enable_color = true; batch.enable_depth = true; batch.func = gl::EQUAL; batch.ref = impl().stencil_level; batch.mask = -1; batch.sfail = gl::KEEP; batch.dfail = gl::KEEP; batch.dpass = gl::KEEP; } impl().stencil_bbox.pop_back(); } void painter_impl::start_frame(geom::box const & bbox) { impl().stencil_bbox.push_back(bbox); } void painter_impl::render(geom::matrix const & transform) { impl().colored_program.bind(); impl().colored_program["u_transform"] = transform; impl().textured_mix_program.bind(); impl().textured_mix_program["u_transform"] = transform; impl().textured_multiply_program.bind(); impl().textured_multiply_program["u_transform"] = transform; impl().msdf_program.bind(); impl().msdf_program["u_transform"] = transform; gl::ActiveTexture(gl::TEXTURE0); std::size_t primitive_count = 0; auto batch_visitor = util::overload( [&](colored_batch const & b){ primitive_count += b.indices.size() / 3; impl().colored_program.bind(); impl().colored_mesh.load(b.vertices, b.indices, gl::TRIANGLES); impl().colored_mesh.draw(); }, [&](textured_batch const & b){ if (!b.texture) return; primitive_count += b.indices.size() / 3; gfx::program & program = (b.mode == ui::painter::color_mode::mix) ? impl().textured_mix_program : impl().textured_multiply_program; program.bind(); program["u_texture_size"] = geom::cast(b.texture->size()); b.texture->bind(); impl().msdf_mesh.load(b.vertices, b.indices, gl::TRIANGLES); impl().msdf_mesh.draw(); }, [&](msdf_batch const & b){ if (!b.texture) return; primitive_count += b.indices.size() / 3; gfx::program & program = impl().msdf_program; program.bind(); program["u_texture_size"] = geom::cast(b.texture->size()); program["u_sdf_scale"] = b.sdf_scale; 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) std::visit(batch_visitor, b); impl().max_batch_count = std::max(impl().max_batch_count, impl().batches.size()); impl().max_primitive_count = std::max(impl().max_primitive_count, primitive_count); impl().depth = 0; impl().batches.clear(); impl().stencil_level = 0; impl().stencil_bbox.clear(); impl().draw_bbox = {}; } geom::box painter_impl::current_bbox() const { return impl().stencil_bbox.back(); } }