From 6103fcf72ce5349a21724a0f1382211e5d035c3c Mon Sep 17 00:00:00 2001 From: nyorain Date: Sun, 19 Aug 2018 18:03:24 +0200 Subject: [PATCH 1/4] Add glfw3 example --- docs/roadmap.md | 2 + example/example_glfw.cpp | 909 ++++++++++++++++++++++++ example/{example.cpp => example_ny.cpp} | 3 +- example/meson.build | 45 +- meson.build | 5 +- meson_options.txt | 3 +- 6 files changed, 950 insertions(+), 17 deletions(-) create mode 100644 example/example_glfw.cpp rename example/{example.cpp => example_ny.cpp} (99%) diff --git a/docs/roadmap.md b/docs/roadmap.md index 5f59cd3..02ee99d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -12,6 +12,8 @@ - [ ] make positioning textures easier (NO manual matrix...) - [ ] transform matrix on paint? - [ ] strokeWidth < fringeWidth (nvg line 2258), github.com/memononen/nanovg/issues/68 +- [ ] remove code duplication in ny/glfw backends. Define app and such + only once (or at least the widgets once) problem of __scaling__ should be solved in this release: When using a non-window transform (e.g. level transform) then diff --git a/example/example_glfw.cpp b/example/example_glfw.cpp new file mode 100644 index 0000000..0662198 --- /dev/null +++ b/example/example_glfw.cpp @@ -0,0 +1,909 @@ +// Copyright (c) 2017 nyorain +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt + +// Example utility for creating window and vulkan swapchain setup +#include "render.hpp" + +// the used rvg headers. +#include +#include +#include +#include +#include +#include + +// katachi to build our bezier curves or use svg +#include +#include + +// vpp to allow more high-level vulkan usage. +#include +#include +#include +#include +#include + +// some matrix/vector utilities +#include +#include + +// logging/debugging +#include + +#define GLFW_INCLUDE_VULKAN +#include +#include +#include + +// where the resources are located. +static const std::string baseResPath = "../"; + +using namespace nytl; +using namespace nytl::vec::operators; +using namespace nytl::vec::cw::operators; + +// settings +constexpr auto appName = "rvg-example"; +constexpr auto engineName = "vpp;rvg"; +constexpr auto useValidation = true; +constexpr auto startMsaa = vk::SampleCountBits::e1; +constexpr auto layerName = "VK_LAYER_LUNARG_standard_validation"; +constexpr auto printFrames = true; +constexpr auto vsync = true; +constexpr auto clearColor = std::array{{0.f, 0.f, 0.f, 1.f}}; +constexpr auto textHeight = 16; + +struct Context { + rvg::Context& ctx; + rvg::Font& font; +}; + +/// Visualizes correctly and wrongly interpolated gradients. +/// It displays three horizontal gradients: the first manually +/// mixed correctly (by using rvg::mix), the second by using a rvg +/// linear gradient. The third is mixed manually but incorrectly, by +/// simply calculating 'fac * colorA + (1 - fac) * colorB' which will +/// result in a somewhat uglier (and obviously different and incorrect) +/// gradient (as is sadly currently the default in most software). +class GradientWidget { +public: + GradientWidget() = default; + GradientWidget(const Context&, Vec2f pos, + rvg::Color start, rvg::Color end); + void draw(vk::CommandBuffer cmdBuf); + +protected: + struct Rect { + rvg::RectShape shape; + rvg::Paint paint; + }; + + rvg::Text topText_; + std::vector topRects_; + + rvg::Text middleText_; + rvg::RectShape middleShape_; + rvg::Paint gradient_; + + rvg::Text bottomText_; + std::vector bottomRects_; +}; + +class PathWidget { +public: + PathWidget() = default; + PathWidget(const Context&, Vec2f pos, Vec2f size); + void draw(vk::CommandBuffer); + void clicked(Vec2f pos); + + Vec2f pos() const { return pos_; } + Vec2f size() const { return size_; } + +protected: + bool first_ {true}; + Vec2f pos_; + Vec2f size_; + + ktc::Subpath path_; + rvg::Scissor scissor_; + rvg::Shape shape_; + rvg::RectShape bg_; + rvg::Paint paint_; + rvg::Text text_; +}; + +class PendulumWidget { +public: + struct { + float j {0.05}; + float c {0.05}; + float l {0.5}; + float m {0.5}; + float g {-9.81}; // we work in window coordinates + } constants; + + float screenLength {350.f}; + float angle {0.01f}; + float avel {0.f}; + + // in last step + float accel {0.f}; + +public: + PendulumWidget() = default; + PendulumWidget(const Context& cctx, nytl::Vec2f pos); + void update(float dt); + nytl::Vec2f massPos() const; + void changeCenter(nytl::Vec2f nc); + void draw(vk::CommandBuffer cb); + + void left(bool pressed); + void right(bool pressed); + +protected: + bool left_ {}, right_ {}; + nytl::Vec2f center_; + rvg::Text text_; + rvg::CircleShape fixture_; + rvg::CircleShape mass_; + rvg::Shape connection_; + + rvg::Paint whitePaint_; + rvg::Paint redPaint_; + + float xVel_ {}; + float xFriction_ {8.f}; // not as it should be... +}; + +class App { +public: + App(rvg::Context& ctx); + + void update(double dt); + void draw(vk::CommandBuffer); + void resize(Vec2ui size); + + void clicked(Vec2f pos); + void key(unsigned, bool pressed); + +protected: + rvg::Context& ctx_; + rvg::Font font_; + rvg::Font awesomeFont_; + + GradientWidget gradWidget_; + PendulumWidget pendulum_; + PathWidget path_; + + rvg::Paint paint_; + rvg::Transform transform_; + rvg::CircleShape circle_; + rvg::Text bottomText_; + rvg::Shape starShape_; + rvg::Shape hsvCircle_; + + rvg::Texture texture_; + rvg::Paint texturePaint_; + rvg::Paint gradientPaint_; + + std::vector texts_; + double angle_ {}; + double scale_ {1.f}; + nytl::Vec2ui size_ {}; +}; + +// - implementation - +template +void scale(nytl::Mat4& mat, nytl::Vec3 fac) { + for(auto i = 0; i < 3; ++i) { + mat[i][i] *= fac[i]; + } +} + +template +void translate(nytl::Mat4& mat, nytl::Vec3 move) { + for(auto i = 0; i < 3; ++i) { + mat[i][3] += move[i]; + } +} + +template +nytl::Mat4 rotMat(double angle) { + auto mat = nytl::identity<4, T>(); + + auto c = std::cos(angle); + auto s = std::sin(angle); + + mat[0][0] = c; + mat[0][1] = -s; + mat[1][0] = s; + mat[1][1] = c; + + return mat; +} + +enum class HorzAlign { + left, + center, + right +}; + +enum class VertAlign { + top, + middle, + bottom +}; + +/// Returns the position to align the text in the given rect +Vec2f alignText(std::string_view text, const rvg::Font& font, + float height, Rect2f rect, HorzAlign halign = HorzAlign::center, + VertAlign valign = VertAlign::middle) { + + auto size = Vec2f{font.width(text, height), height}; + + Vec2f ret = rect.position; + if(halign == HorzAlign::center) { + ret.x += (rect.size.x - size.x) / 2.f; + } else if(halign == HorzAlign::right) { + ret.x += rect.size.x - size.x; + } + + if(valign == VertAlign::middle) { + ret.y += (rect.size.y - size.y) / 2.f; + } else if(valign == VertAlign::bottom) { + ret.y += rect.size.y - size.y; + } + + return ret; +} + +// Pendulum +PendulumWidget::PendulumWidget(const Context& cctx, nytl::Vec2f pos) + : center_(pos) { + + constexpr auto centerRadius = 10.f; + constexpr auto massRadius = 20.f; + + auto& ctx = cctx.ctx; + auto end = massPos(); + auto drawMode = rvg::DrawMode {}; + drawMode.aaFill = true; + drawMode.fill = true; + fixture_ = {ctx, pos, centerRadius, drawMode}; + mass_ = {ctx, end, massRadius, drawMode}; + + drawMode.fill = false; + drawMode.stroke = 3.f; + drawMode.aaStroke = true; + connection_ = {ctx, {pos, end}, drawMode}; + + whitePaint_ = {ctx, rvg::colorPaint(rvg::Color::white)}; + redPaint_ = {ctx, rvg::colorPaint(rvg::Color::red)}; + + auto ppos = pos - Vec2f {0.f, massRadius + 10.f}; + auto string = "Move me using the arrow keys"; + auto textPos = alignText(string, cctx.font, textHeight, {ppos, {}}); + text_ = {ctx, textPos, string, cctx.font, textHeight}; +} + +void PendulumWidget::update(float dt) { + constexpr auto leftBound = 650.f; + constexpr auto rightBound = 1450.f; + + float u = -xFriction_ * xVel_; + if(center_.x < leftBound) { + center_.x = leftBound; + u = -xVel_ / dt; + } else if(left_) { + u -= 5.f; + } + + if(center_.x > rightBound) { + center_.x = rightBound; + u = -xVel_ / dt; + } else if(right_) { + u += 5.f; + } + + xVel_ += dt * u; + + auto scale = 400.f; + center_.x += scale * dt * xVel_; + changeCenter(center_); + + // logic + angle += dt * avel; + + auto& c = constants; + accel = c.m * c.l * (u * std::cos(angle) + c.g * std::sin(angle)); + accel -= c.c * avel; + accel /= (c.j + c.m * c.l * c.l); + avel += dt * accel; + + // rendering + auto end = massPos(); + mass_.change()->center = end; + connection_.change()->points[1] = end; +} + +nytl::Vec2f PendulumWidget::massPos() const { + float c = std::cos(angle + 0.5 * nytl::constants::pi); + float s = std::sin(angle + 0.5 * nytl::constants::pi); + return center_ + screenLength * nytl::Vec2f{c, s}; +} + +void PendulumWidget::changeCenter(nytl::Vec2f nc) { + center_ = nc; + auto end = massPos(); + mass_.change()->center = end; + connection_.change()->points = {nc, end}; + fixture_.change()->center = nc; +} + +void PendulumWidget::left(bool pressed) { + left_ = pressed; +} + +void PendulumWidget::right(bool pressed) { + right_ = pressed; +} + +void PendulumWidget::draw(vk::CommandBuffer cb) { + whitePaint_.bind(cb); + fixture_.fill(cb); + connection_.stroke(cb); + text_.draw(cb); + + redPaint_.bind(cb); + mass_.fill(cb); +} + +// GradientWidget +GradientWidget::GradientWidget(const Context& ctx, Vec2f pos, + rvg::Color start, rvg::Color end) { + + constexpr auto lineHeight = 100; + constexpr auto textWidth = 250; + constexpr auto gradientWidth = 300; + constexpr auto gradientSteps = 32; + constexpr auto stepWidth = float(gradientWidth) / gradientSteps; + + auto& rctx = ctx.ctx; + auto dm = rvg::DrawMode {true, false}; + + auto yoff = (lineHeight - textHeight) / 2; + auto p = pos + Vec{10, yoff}; + topText_ = {rctx, p, "[Stepped] Using rvg::mix:", ctx.font, textHeight}; + + p.y += lineHeight; + middleText_ = {rctx, p, "Using a linear gradient:", ctx.font, textHeight}; + auto mpos = pos + Vec {textWidth, lineHeight}; + auto msize = Vec2f {gradientWidth, lineHeight}; + middleShape_ = {rctx, mpos, msize, dm}; + gradient_ = {rctx, rvg::linearGradient(mpos, + mpos + Vec {gradientWidth, 0.f}, start, end)}; + + p.y += lineHeight; + bottomText_ = {rctx, p, "[Stepped] Using incorrect mixing", ctx.font, + textHeight}; + + auto rsize = Vec {stepWidth, lineHeight}; + auto topy = pos.y; + auto boty = pos.y + 2 * lineHeight; + for(auto i = 0u; i < gradientSteps; ++i) { + auto fac = float(i) / (gradientSteps - 1); + auto x = pos.x + textWidth + i * stepWidth; + + topRects_.emplace_back(); + auto p1 = rvg::colorPaint(rvg::mix(start, end, 1 - fac)); + topRects_.back().paint = {rctx, p1}; + topRects_.back().shape = {rctx, {x, topy}, rsize, dm}; + + bottomRects_.emplace_back(); + auto col = (1 - fac) * start.rgbaNorm() + fac * end.rgbaNorm(); + auto p2 = rvg::colorPaint({rvg::norm, col}); + bottomRects_.back().paint = {rctx, p2}; + bottomRects_.back().shape = {rctx, {x, boty}, rsize, dm}; + } +} + +void GradientWidget::draw(vk::CommandBuffer cb) { + topText_.draw(cb); + middleText_.draw(cb); + bottomText_.draw(cb); + + dlg_assert(topRects_.size() == bottomRects_.size()); + for(auto i = 0u; i < topRects_.size(); ++i) { + topRects_[i].paint.bind(cb); + topRects_[i].shape.fill(cb); + + bottomRects_[i].paint.bind(cb); + bottomRects_[i].shape.fill(cb); + } + + gradient_.bind(cb); + middleShape_.fill(cb); +} + +// Path +PathWidget::PathWidget(const Context& ctx, Vec2f pos, Vec2f size) { + pos_ = pos; + size_ = size; + + auto drawMode = rvg::DrawMode {false, 1.f}; + drawMode.aaStroke = true; + drawMode.deviceLocal = true; + shape_ = {ctx.ctx, {}, drawMode}; + scissor_ = {ctx.ctx, {pos, size}}; + + drawMode.stroke = 4.f; + bg_ = {ctx.ctx, pos + Vec {2.f, 2.f}, size - Vec {4.f, 4.f}, + drawMode, {4.f, 4.f, 4.f, 4.f}}; + paint_ = {ctx.ctx, rvg::colorPaint(rvg::Color::white)}; +} + +void PathWidget::draw(vk::CommandBuffer cb) { + scissor_.bind(cb); + paint_.bind(cb); + bg_.stroke(cb); + shape_.stroke(cb); + scissor_.context().defaultScissor().bind(cb); +} + +void PathWidget::clicked(Vec2f pos) { + if(first_) { + first_ = false; + path_.start = pos; + } else { + path_.sqBezier(pos); + shape_.change()->points = ktc::flatten(path_); + } +} + +// App +App::App(rvg::Context& ctx) : ctx_(ctx), + // font_(ctx_, baseResPath + "example/OpenSans-Regular.ttf"), + font_(ctx_, "Roboto-Regular.ttf"), + awesomeFont_(ctx_, baseResPath + "example/fontawesome-webfont.ttf") { + + constexpr auto gradPos = Vec {50.f, 50.f}; + constexpr auto pathPos = Vec {50.f, 450.f}; + constexpr auto pathSize = Vec {400.f, 400.f}; + constexpr auto pendulumPos = Vec {900.f, 450.f}; + constexpr auto texPath = "example/thunderstorm.jpg"; + constexpr auto circlePos = Vec {850.f, 200.f}; + constexpr auto circleRad = 120.f; + constexpr auto hsvCenter = Vec {1150.f, 200.f}; + constexpr auto hsvRad = 120.f; + constexpr auto starPos = Vec {1440.f, 205.f}; + constexpr auto starRad = 130.f; + constexpr auto textOff = Vec {0.f, 150.f}; + + font_.fallback(awesomeFont_); + + auto addText = [&](Vec2f center, const char* string) { + auto pos = alignText(string, font_, textHeight, {center, {}}); + texts_.emplace_back(ctx, pos, string, font_, textHeight); + }; + + transform_ = {ctx}; + gradWidget_ = {{ctx, font_}, gradPos, rvg::Color::red, rvg::Color::green}; + path_ = {{ctx, font_}, pathPos, pathSize}; + pendulum_ = {{ctx, font_}, pendulumPos}; + bottomText_ = {ctx, {}, u8" https://github.com/nyorain/rvg ", font_, + textHeight}; + paint_ = {ctx, rvg::colorPaint(rvg::Color::white)}; + + addText(pathPos + Vec {pathSize.x / 2.f, pathSize.y + 20.f}, + "Click me: Anti aliased smooth bezier curves"); + + texture_ = rvg::Texture(ctx, baseResPath + texPath); + + auto mat = nytl::identity<4, float>(); + mat[0][0] = 0.5 / circleRad; + mat[1][1] = 0.5 / circleRad; + mat[0][3] = -0.5 * (circlePos.x - circleRad) / circleRad; + mat[1][3] = -0.5 * (circlePos.y - circleRad) / circleRad; + texturePaint_ = {ctx, rvg::texturePaintRGBA(mat, texture_.vkImageView())}; + + // star + auto starPoints = [&](Vec2f center, float scale) { + std::vector points; + points.push_back(center); + using nytl::constants::pi; + auto angle = -0.5 * pi; + for(auto i = 0u; i < 6; ++i) { + auto pos = center + scale * Vec {std::cos(angle), std::sin(angle)}; + points.push_back(Vec2f(pos)); + angle += (4 / 5.f) * pi; + } + + return points; + }; + + starShape_ = {ctx, starPoints(starPos, starRad), {true, 2.f}}; + gradientPaint_ = {ctx, rvg::radialGradient(starPos, -5.f, starRad - 15.f, + rvg::u32rgba(0x7474FFFF), rvg::u32rgba(0xFFFF74FF))}; + addText(starPos + textOff, "No anti aliasing"); + + // hsv wheel + auto drawMode = rvg::DrawMode {true, 0.f}; + + auto hsvPointCount = 128u + 2u; + auto colorPoints = std::vector {hsvPointCount}; + auto hsvPoints = std::vector {hsvPointCount}; + colorPoints[0] = rvg::hsvNorm(0.f, 0.f, 0.5f).rgba(); + hsvPoints[0] = hsvCenter; + for(auto i = 1u; i < hsvPointCount; ++i) { + auto fac = (i - 1) / float(hsvPointCount - 2); + auto col = rvg::hsvNorm(fac, 1.f, 1.f); + colorPoints[i] = col.rgba(); + fac *= 2 * nytl::constants::pi; + auto off = Vec2f {std::cos(fac), std::sin(fac)}; + hsvPoints[i] = hsvCenter + hsvRad * off; + } + + drawMode.color.fill = true; + drawMode.color.points = std::move(colorPoints); + hsvCircle_ = {ctx, std::move(hsvPoints), drawMode}; + addText(hsvCenter + textOff, "Using per-point color"); + + drawMode.aaFill = true; + drawMode.aaStroke = true; + drawMode.stroke = 2.5f; + circle_ = {ctx, circlePos, circleRad, drawMode}; + addText(circlePos + textOff, "Anti aliasing & using a texture"); +} + +void App::update(double dt) { + pendulum_.update(dt); +} + +void App::resize(Vec2ui size) { + // setup a matrix that will transform from window coords to vulkan coords + auto mat = nytl::identity<4, float>(); + auto s = nytl::Vec {2.f / size.x, 2.f / size.y, 1}; + scale(mat, s); + translate(mat, {-1, -1, 0}); + mat = mat * rotMat(angle_); + mat[0][0] *= scale_; + mat[1][1] *= scale_; + *transform_.change() = mat; + size_ = size; + + auto textWidth = bottomText_.width(); + auto tchange = bottomText_.change(); + tchange->position.x = (size[0] - textWidth) / 2; + tchange->position.y = size[1] - bottomText_.height() - 20; +} + +void App::draw(vk::CommandBuffer cb) { + transform_.bind(cb); + paint_.bind(cb); + + gradWidget_.draw(cb); + path_.draw(cb); + + gradientPaint_.bind(cb); + starShape_.fill(cb); + + paint_.bind(cb); + circle_.stroke(cb); + texturePaint_.bind(cb); + circle_.fill(cb); + + ctx_.pointColorPaint().bind(cb); + hsvCircle_.fill(cb); + + paint_.bind(cb); + for(auto& t : texts_) { + t.draw(cb); + } + + pendulum_.draw(cb); + + paint_.bind(cb); + bottomText_.draw(cb); +} + +void App::clicked(Vec2f pos) { + auto in = [&](Vec2f p, Vec2f size) { + using namespace nytl::vec::cw; + return pos == clamp(pos, p, p + size); + }; + + auto h = bottomText_.height(); + if(in(bottomText_.position(), {bottomText_.width(), h})) { + // opens the github link in browser + // ikr, std::system isn't a good choice, generally. + // But here, i feel like it's enough +#ifdef RVG_EXAMPLE_UNIX + std::system("xdg-open https://www.github.com/nyorain/rvg"); +#elif defined(RVG_EXAMPLE_WIN) + // https://stackoverflow.com/questions/3739327 + std::system("explorer https://www.github.com/nyorain/rvg"); +#endif + } else if(in(path_.pos(), path_.size())) { + path_.clicked(pos); + } +} + +void App::key(unsigned key, bool pressed) { + if(key == GLFW_KEY_LEFT) { + pendulum_.left(pressed); + } else if(key == GLFW_KEY_RIGHT) { + pendulum_.right(pressed); + } + + if(!pressed) { + return; + } + + // TODO + auto t = false; + + if(key == GLFW_KEY_B) { + *paint_.change() = rvg::colorPaint({rvg::norm, 0.2, 0.2, 0.8}); + } else if(key == GLFW_KEY_G) { + *paint_.change() = rvg::colorPaint({rvg::norm, 0.1, 0.6, 0.3}); + } else if(key == GLFW_KEY_R) { + *paint_.change() = rvg::colorPaint({rvg::norm, 0.8, 0.2, 0.3}); + } else if(key == GLFW_KEY_D) { + *paint_.change() = rvg::colorPaint({rvg::norm, 0.1, 0.1, 0.1}); + } else if(key == GLFW_KEY_W) { + *paint_.change() = rvg::colorPaint(rvg::Color::white); + } else if(key == GLFW_KEY_P) { + *paint_.change() = rvg::linearGradient({0, 0}, {2000, 1000}, + {255, 0, 0}, {255, 255, 0}); + } else if(key == GLFW_KEY_C) { + *paint_.change() = rvg::radialGradient({1000, 500}, 0, 1000, + {255, 0, 0}, {255, 255, 0}); + } + + else if(key == GLFW_KEY_Q) { + angle_ += 0.1; + t = true; + } else if(key == GLFW_KEY_E) { + angle_ -= 0.1; + t = true; + } else if(key == GLFW_KEY_I) { + scale_ *= 1.1; + t = true; + } else if(key == GLFW_KEY_O) { + scale_ /= 1.1; + t = true; + } + + if(t) { + auto size = size_; + auto mat = nytl::identity<4, float>(); + auto s = nytl::Vec {2.f / size.x, 2.f / size.y, 1}; + scale(mat, s); + translate(mat, {-1, -1, 0}); + mat = mat * rotMat(angle_); + mat[0][0] *= scale_; + mat[1][1] *= scale_; + *transform_.change() = mat; + } +} + +// main +int main() { + // - initialization - + if(!::glfwInit()) { + throw std::runtime_error("Failed to init glfw"); + } + + // vulkan init: instance + uint32_t count; + const char** extensions = ::glfwGetRequiredInstanceExtensions(&count); + + std::vector iniExtensions {extensions, extensions + count}; + iniExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + + vk::ApplicationInfo appInfo (appName, 1, engineName, 1, VK_API_VERSION_1_0); + vk::InstanceCreateInfo instanceInfo; + instanceInfo.pApplicationInfo = &appInfo; + + instanceInfo.enabledExtensionCount = iniExtensions.size(); + instanceInfo.ppEnabledExtensionNames = iniExtensions.data(); + + if(useValidation) { + auto layers = { + layerName, + "VK_LAYER_RENDERDOC_Capture", + }; + + instanceInfo.enabledLayerCount = layers.size(); + instanceInfo.ppEnabledLayerNames = layers.begin(); + } + + vpp::Instance instance {}; + try { + instance = {instanceInfo}; + if(!instance.vkInstance()) { + throw std::runtime_error("vkCreateInstance returned a nullptr"); + } + } catch(const std::exception& error) { + dlg_error("Vulkan instance creation failed: {}", error.what()); + dlg_error("\tYour system may not support vulkan"); + dlg_error("\tThis application requires vulkan to work"); + throw; + } + + // debug callback + std::unique_ptr debugCallback; + if(useValidation) { + debugCallback = std::make_unique(instance); + } + + // init glfw window + const auto size = nytl::Vec {1200u, 800u}; + ::glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = ::glfwCreateWindow(size.x, size.y, "rvg", NULL, NULL); + if(!window) { + throw std::runtime_error("Failed to create glfw window"); + } + + // avoiding reinterpret_cast due to aliasing warnings + VkInstance vkini; + auto handle = instance.vkHandle(); + std::memcpy(&vkini, &handle, sizeof(vkini)); + static_assert(sizeof(VkInstance) == sizeof(vk::Instance)); + + VkSurfaceKHR vkSurf {}; + VkResult err = ::glfwCreateWindowSurface(vkini, window, NULL, &vkSurf); + if(err) { + auto str = std::string("Failed to create vulkan surface: "); + str += vk::name(static_cast(err)); + throw std::runtime_error(str); + } + + vk::SurfaceKHR surface {}; + std::memcpy(&surface, &vkSurf, sizeof(surface)); + static_assert(sizeof(VkSurfaceKHR) == sizeof(vk::SurfaceKHR)); + + // create device + // enable some extra features + float priorities[1] = {0.0}; + + auto phdevs = vk::enumeratePhysicalDevices(instance); + auto phdev = vpp::choose(phdevs, instance, surface); + + auto queueFlags = vk::QueueBits::compute | vk::QueueBits::graphics; + int queueFam = vpp::findQueueFamily(phdev, instance, surface, queueFlags); + + vk::DeviceCreateInfo devInfo; + vk::DeviceQueueCreateInfo queueInfo({}, queueFam, 1, priorities); + + auto exts = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + devInfo.pQueueCreateInfos = &queueInfo; + devInfo.queueCreateInfoCount = 1u; + devInfo.ppEnabledExtensionNames = exts.begin(); + devInfo.enabledExtensionCount = 1u; + + auto features = vk::PhysicalDeviceFeatures {}; + features.shaderClipDistance = true; + devInfo.pEnabledFeatures = &features; + + auto device = vpp::Device(instance, phdev, devInfo); + auto presentQueue = device.queue(queueFam); + + auto renderInfo = RendererCreateInfo { + device, surface, size, *presentQueue, + startMsaa, vsync, clearColor + }; + + // optional so we can manually destroy it before vulkan surface + auto orenderer = std::optional(renderInfo); + auto& renderer = *orenderer; + + // app + rvg::Context ctx(device, {renderer.renderPass(), 0, true}); + App app(ctx); + + // render recoreding + renderer.onRender += [&](vk::CommandBuffer cb){ + ctx.bindDefaults(cb); + app.draw(cb); + }; + + ctx.updateDevice(); + renderer.invalidate(); + + // connect window & renderer + struct WinInfo { + Renderer* renderer; + App* app; + } winInfo = { + &renderer, + &app, + }; + + ::glfwSetWindowUserPointer(window, &winInfo); + + auto sizeCallback = [](auto* window, int width, int height) { + auto ptr = ::glfwGetWindowUserPointer(window); + const auto& winInfo = *static_cast(ptr); + auto size = nytl::Vec {unsigned(width), unsigned(height)}; + winInfo.renderer->resize(size); + winInfo.app->resize(size); + }; + + auto keyCallback = [](auto* window, int key, int, int action, int) { + auto pressed = action != GLFW_RELEASE; + auto ptr = ::glfwGetWindowUserPointer(window); + const auto& winInfo = *static_cast(ptr); + winInfo.app->key(key, pressed); + }; + + auto mouseCallback = [](auto* window, int button, int action, int) { + auto ptr = ::glfwGetWindowUserPointer(window); + const auto& winInfo = *static_cast(ptr); + auto pressed = action == GLFW_PRESS; + if(pressed && button == GLFW_MOUSE_BUTTON_LEFT) { + double x,y; + ::glfwGetCursorPos(window, &x, &y); + winInfo.app->clicked({float(x), float(y)}); + } + }; + + ::glfwSetFramebufferSizeCallback(window, sizeCallback); + ::glfwSetKeyCallback(window, keyCallback); + ::glfwSetMouseButtonCallback(window, mouseCallback); + + // - main loop - + using Clock = std::chrono::high_resolution_clock; + using Secf = std::chrono::duration>; + + auto lastFrame = Clock::now(); + auto fpsCounter = 0u; + auto secCounter = 0.f; + + while(!::glfwWindowShouldClose(window)) { + auto now = Clock::now(); + auto diff = now - lastFrame; + auto dt = std::chrono::duration_cast(diff).count(); + lastFrame = now; + + app.update(dt); + ::glfwPollEvents(); + + auto [rec, seph] = ctx.upload(); + + if(rec) { + dlg_info("Rerecording due to context"); + renderer.invalidate(); + } + + auto wait = { + vpp::StageSemaphore { + seph, + vk::PipelineStageBits::allGraphics, + } + }; + + vpp::RenderInfo info; + if(seph) { + info.wait = wait; + } + + renderer.renderSync(info); + + if(printFrames) { + ++fpsCounter; + secCounter += dt; + if(secCounter >= 1.f) { + dlg_info("{} fps", fpsCounter); + secCounter = 0.f; + fpsCounter = 0; + } + } + } + + orenderer.reset(); + vkDestroySurfaceKHR(vkini, vkSurf, nullptr); + ::glfwDestroyWindow(window); + ::glfwTerminate(); +} diff --git a/example/example.cpp b/example/example_ny.cpp similarity index 99% rename from example/example.cpp rename to example/example_ny.cpp index 86060c7..7fe3fe6 100644 --- a/example/example.cpp +++ b/example/example_ny.cpp @@ -57,7 +57,7 @@ constexpr auto layerName = "VK_LAYER_LUNARG_standard_validation"; constexpr auto printFrames = true; constexpr auto vsync = true; constexpr auto clearColor = std::array{{0.f, 0.f, 0.f, 1.f}}; -constexpr auto textHeight = 22; +constexpr auto textHeight = 16; struct Context { rvg::Context& ctx; @@ -620,6 +620,7 @@ void App::clicked(Vec2f pos) { auto h = bottomText_.height(); if(in(bottomText_.position(), {bottomText_.width(), h})) { + // opens the github link in browser // ikr, std::system isn't a good choice, generally. // But here, i feel like it's enough #ifdef RVG_EXAMPLE_UNIX diff --git a/example/meson.build b/example/meson.build index e46ec09..fb2dced 100644 --- a/example/meson.build +++ b/example/meson.build @@ -1,25 +1,44 @@ -dep_ny = dependency('ny', fallback: ['ny', 'ny_dep']) +example_args = [] +if build_machine.system() == 'windows' + example_args += '-DRVG_EXAMPLE_WIN' +elif build_machine.system() == 'linux' + example_args += '-DRVG_EXAMPLE_UNIX' +endif example_src = [ - 'example.cpp', 'render.cpp', - 'window.cpp', ] example_deps = [ rvg_dep, - dep_ny ] -example_args = [] -if build_machine.system() == 'windows' - example_args += '-DRVG_EXAMPLE_WIN' -elif build_machine.system() == 'linux' - example_args += '-DRVG_EXAMPLE_UNIX' +# ny +if build_example_ny + dep_ny = dependency('ny', fallback: ['ny', 'ny_dep']) + example_src_ny = example_src + [ + 'example_ny.cpp', + 'window.cpp', + ] + example_deps_ny = example_deps + [dep_ny] + + executable('example_ny', + cpp_args: example_args, + sources: example_src_ny, + dependencies: example_deps_ny) +endif + + +# glfw +if build_example_glfw + dep_glfw = dependency('glfw3') + example_src_glfw = example_src + ['example_glfw.cpp'] + example_deps_glfw = example_deps + [dep_glfw] + + executable('example_glfw', + cpp_args: example_args, + sources: example_src_glfw, + dependencies: example_deps_glfw) endif -executable('example', - cpp_args: example_args, - sources: example_src, - dependencies: example_deps) diff --git a/meson.build b/meson.build index 685cf34..068fe9f 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,8 @@ project('rvg', ['cpp', 'c'], 'warning_level=3', 'werror=true']) -build_example = get_option('examples') +build_example_ny = get_option('example-ny') +build_example_glfw = get_option('example-glfw') build_tests = get_option('tests') warnings = [ @@ -73,7 +74,7 @@ rvg_dep = declare_dependency( dependencies: rvg_deps, include_directories: rvg_inc) -if build_example +if build_example_ny or build_example_glfw subdir('example') endif diff --git a/meson_options.txt b/meson_options.txt index 3b9c36f..3d16d3f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,2 +1,3 @@ -option('examples', type: 'boolean', value: false) +option('example-ny', type: 'boolean', value: false) +option('example-glfw', type: 'boolean', value: false) option('tests', type: 'boolean', value: false) From 4b4dd5708f2081856d5af51f3ebf803a3ff0e602 Mon Sep 17 00:00:00 2001 From: nyorain Date: Sun, 19 Aug 2018 18:09:29 +0200 Subject: [PATCH 2/4] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c51f4e..136bcd1 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ all issues and questions coming up (simple things such as spelling errors or missing docs on a function are appreciated as well). For more information check out the example concepts below, the -[example](example/example.cpp) showing off many features, +[example](example/example_ny.cpp) showing off many features, or read the [introduction](docs/intro.md) which documents some basic concepts and shows how to integrate rvg in more detail. From 83c0629ae1bbf12d22b8d5f5fc2df6c11855a489 Mon Sep 17 00:00:00 2001 From: nyorain Date: Sun, 19 Aug 2018 18:12:43 +0200 Subject: [PATCH 3/4] Fix example drawMode reusing --- example/example_glfw.cpp | 1 + example/example_ny.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/example/example_glfw.cpp b/example/example_glfw.cpp index 0662198..0465690 100644 --- a/example/example_glfw.cpp +++ b/example/example_glfw.cpp @@ -552,6 +552,7 @@ App::App(rvg::Context& ctx) : ctx_(ctx), drawMode.aaFill = true; drawMode.aaStroke = true; drawMode.stroke = 2.5f; + drawMode.color = {}; circle_ = {ctx, circlePos, circleRad, drawMode}; addText(circlePos + textOff, "Anti aliasing & using a texture"); } diff --git a/example/example_ny.cpp b/example/example_ny.cpp index 7fe3fe6..b3a5870 100644 --- a/example/example_ny.cpp +++ b/example/example_ny.cpp @@ -557,6 +557,7 @@ App::App(rvg::Context& ctx) : ctx_(ctx), drawMode.aaFill = true; drawMode.aaStroke = true; drawMode.stroke = 2.5f; + drawMode.color = {}; circle_ = {ctx, circlePos, circleRad, drawMode}; addText(circlePos + textOff, "Anti aliasing & using a texture"); } From d9b7a4b8ae5554ed6208ae64bb5abfc6d4006efc Mon Sep 17 00:00:00 2001 From: nyorain Date: Sun, 19 Aug 2018 18:55:35 +0200 Subject: [PATCH 4/4] Update README.md --- README.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 136bcd1..bbe6e7f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Retained vulkan/vector graphics -Vulkan library for high-level 2D vector-like rendering. +Vulkan library for high-level 2D vector-like rendering in modern C++17. Modeled loosely after svg, inspired by nanoVG. Uses an object-oriented, retained mode idiom for rendering which makes it highly efficient for rendering with vulkan since curves and shapes are not @@ -13,15 +13,15 @@ Aims to provide a compromise between high level drawing functionality and an api that can efficiently be implemented on the gpu. Could e.g. easily be used for a vulkan gui library. -Written in C++17, the project is tagged as C by github because it embeds -[nuklears](https://github.com/vurtun/nuklear) font handling and +The project builds upon tested and proven libraries where possible, such as +[fontstash](https://github.com/memononen/fontstash) for font atlas building and some [stb headers](https://github.com/nothings/stb). Note that the project is still in a rather early stage, please report -all issues and questions coming up (simple things such as spelling errors +all issues and questions (simple things such as spelling errors or missing docs on a function are appreciated as well). For more information check out the example concepts below, the -[example](example/example_ny.cpp) showing off many features, +[example](example/example_glfw.cpp) showing off many features, or read the [introduction](docs/intro.md) which documents some basic concepts and shows how to integrate rvg in more detail. @@ -139,18 +139,26 @@ Screenshot of [example/example.cpp](example/example.cpp): ## Building The library uses meson as build system. It uses a few of my other -libraries as dependencies to stay modular. +libraries as dependencies to stay modular but those will be automatically +built using meson. The only hard dependencies are vulkan as well as glslang +for building the spirv shaders. You need a solid C++17 compiler (currently not tested with msvc), -gcc >= 7 is tested. +gcc >= 7 is tested. For gcc 7 you currently have to turn off werror +in meson (`meson configure -Dwerror=false` in build dir). +For the glfw example you need the glfw3 library (obviously with vulkan +support). After building ``` -meson build -Dexamples=true -ninja -C build +meson build -Dexample-glfw=true +cd build +ninja ``` -you should be able to run the example in the build dir via `./example/example` +you should be able to run the example in the build dir via `./example/example_glfw`. +There is also an example using my experimental ny window abstraction instead of glfw, +enable it via `-Dexample-ny=true` (requires several low level xcb/wayland libraries). # Notes