From 3d78a073d90d8f232604fbdc76a6a583ffab364b Mon Sep 17 00:00:00 2001 From: David Robillard Date: Mon, 24 May 2021 09:16:08 -0400 Subject: Consistently refer to C++ as "cpp" and fix installation --- .clant.json | 2 +- README.md | 4 +- bindings/cpp/include/.clang-tidy | 14 + bindings/cpp/include/pugl/cairo.hpp | 45 + bindings/cpp/include/pugl/gl.hpp | 70 ++ bindings/cpp/include/pugl/pugl.hpp | 721 ++++++++++++++ bindings/cpp/include/pugl/stub.hpp | 45 + bindings/cpp/include/pugl/vulkan.hpp | 168 ++++ bindings/cxx/include/.clang-tidy | 14 - bindings/cxx/include/pugl/cairo.hpp | 45 - bindings/cxx/include/pugl/gl.hpp | 70 -- bindings/cxx/include/pugl/pugl.hpp | 721 -------------- bindings/cxx/include/pugl/stub.hpp | 45 - bindings/cxx/include/pugl/vulkan.hpp | 168 ---- doc/cpp/index.rst | 2 +- doc/cpp/meson.build | 4 +- doc/cpp/overview.rst | 2 +- examples/meson.build | 8 +- examples/pugl_cpp_demo.cpp | 148 +++ examples/pugl_cxx_demo.cpp | 148 --- examples/pugl_vulkan_cpp_demo.cpp | 1826 ++++++++++++++++++++++++++++++++++ examples/pugl_vulkan_cxx_demo.cpp | 1826 ---------------------------------- examples/pugl_vulkan_demo.c | 2 +- meson.build | 26 +- 24 files changed, 3065 insertions(+), 3059 deletions(-) create mode 100644 bindings/cpp/include/.clang-tidy create mode 100644 bindings/cpp/include/pugl/cairo.hpp create mode 100644 bindings/cpp/include/pugl/gl.hpp create mode 100644 bindings/cpp/include/pugl/pugl.hpp create mode 100644 bindings/cpp/include/pugl/stub.hpp create mode 100644 bindings/cpp/include/pugl/vulkan.hpp delete mode 100644 bindings/cxx/include/.clang-tidy delete mode 100644 bindings/cxx/include/pugl/cairo.hpp delete mode 100644 bindings/cxx/include/pugl/gl.hpp delete mode 100644 bindings/cxx/include/pugl/pugl.hpp delete mode 100644 bindings/cxx/include/pugl/stub.hpp delete mode 100644 bindings/cxx/include/pugl/vulkan.hpp create mode 100644 examples/pugl_cpp_demo.cpp delete mode 100644 examples/pugl_cxx_demo.cpp create mode 100644 examples/pugl_vulkan_cpp_demo.cpp delete mode 100644 examples/pugl_vulkan_cxx_demo.cpp diff --git a/.clant.json b/.clant.json index 6f48901..cb178c7 100644 --- a/.clant.json +++ b/.clant.json @@ -1,7 +1,7 @@ { "version": "1.0.0", "include_dirs": [ - "bindings/cxx/include", + "bindings/cpp/include", "include" ], "exclude_patterns": [ diff --git a/README.md b/README.md index 6993f78..422410f 100644 --- a/README.md +++ b/README.md @@ -83,12 +83,12 @@ tests and demonstrations: * `pugl_print_events` is a utility that prints all received events to the console in a human readable format. - * `pugl_cxx_demo` is a simple cube demo that uses the C++ API. + * `pugl_cpp_demo` is a simple cube demo that uses the C++ API. * `pugl_vulkan_demo` is a simple example of using Vulkan in C that simply clears the window. - * `pugl_vulkan_cxx_demo` is a more advanced Vulkan demo in C++ that draws many + * `pugl_vulkan_cpp_demo` is a more advanced Vulkan demo in C++ that draws many animated rectangles like `pugl_shader_demo`. All example programs support several command line options to control various diff --git a/bindings/cpp/include/.clang-tidy b/bindings/cpp/include/.clang-tidy new file mode 100644 index 0000000..816223d --- /dev/null +++ b/bindings/cpp/include/.clang-tidy @@ -0,0 +1,14 @@ +Checks: > + *, + -*-uppercase-literal-suffix, + -clang-diagnostic-unused-macros, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-static-cast-downcast, + -google-runtime-references, + -hicpp-named-parameter, + -llvmlibc-*, + -modernize-use-trailing-return-type, + -readability-implicit-bool-conversion, + -readability-named-parameter, +FormatStyle: file +HeaderFilterRegex: 'pugl/.*' diff --git a/bindings/cpp/include/pugl/cairo.hpp b/bindings/cpp/include/pugl/cairo.hpp new file mode 100644 index 0000000..b42af0d --- /dev/null +++ b/bindings/cpp/include/pugl/cairo.hpp @@ -0,0 +1,45 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef PUGL_CAIRO_HPP +#define PUGL_CAIRO_HPP + +#include "pugl/cairo.h" +#include "pugl/pugl.h" + +namespace pugl { + +/** + @defgroup cairopp Cairo + Cairo graphics support. + @ingroup puglpp + @{ +*/ + +/// @copydoc puglCairoBackend +inline const PuglBackend* +cairoBackend() noexcept +{ + return puglCairoBackend(); +} + +/** + @} +*/ + +} // namespace pugl + +#endif // PUGL_CAIRO_HPP diff --git a/bindings/cpp/include/pugl/gl.hpp b/bindings/cpp/include/pugl/gl.hpp new file mode 100644 index 0000000..3e23a57 --- /dev/null +++ b/bindings/cpp/include/pugl/gl.hpp @@ -0,0 +1,70 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef PUGL_GL_HPP +#define PUGL_GL_HPP + +#include "pugl/gl.h" +#include "pugl/pugl.h" +#include "pugl/pugl.hpp" + +namespace pugl { + +/** + @defgroup glpp OpenGL + OpenGL graphics support. + @ingroup puglpp + @{ +*/ + +/// @copydoc PuglGlFunc +using GlFunc = PuglGlFunc; + +/// @copydoc puglGetProcAddress +inline GlFunc +getProcAddress(const char* name) noexcept +{ + return puglGetProcAddress(name); +} + +/// @copydoc puglEnterContext +inline Status +enterContext(View& view) noexcept +{ + return static_cast(puglEnterContext(view.cobj())); +} + +/// @copydoc puglLeaveContext +inline Status +leaveContext(View& view) noexcept +{ + return static_cast(puglLeaveContext(view.cobj())); +} + +/// @copydoc puglGlBackend +inline const PuglBackend* +glBackend() noexcept +{ + return puglGlBackend(); +} + +/** + @} +*/ + +} // namespace pugl + +#endif // PUGL_GL_HPP diff --git a/bindings/cpp/include/pugl/pugl.hpp b/bindings/cpp/include/pugl/pugl.hpp new file mode 100644 index 0000000..51cfdb8 --- /dev/null +++ b/bindings/cpp/include/pugl/pugl.hpp @@ -0,0 +1,721 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef PUGL_PUGL_HPP +#define PUGL_PUGL_HPP + +#include "pugl/pugl.h" + +#include + +#if defined(PUGL_HPP_THROW_FAILED_CONSTRUCTION) +# include +#elif defined(PUGL_HPP_ASSERT_CONSTRUCTION) +# include +#endif + +namespace pugl { + +/** + @defgroup puglpp Pugl C++ API + Pugl C++ API wrapper. + @{ +*/ + +namespace detail { + +/// Free function for a C object +template +using FreeFunc = void (*)(T*); + +/// Generic C++ wrapper for a C object +template Free> +class Wrapper +{ +public: + Wrapper(const Wrapper&) = delete; + Wrapper& operator=(const Wrapper&) = delete; + + Wrapper(Wrapper&& wrapper) noexcept + : _ptr{wrapper._ptr} + { + wrapper._ptr = nullptr; + } + + Wrapper& operator=(Wrapper&& wrapper) noexcept + { + _ptr = wrapper._ptr; + wrapper._ptr = nullptr; + return *this; + } + + ~Wrapper() noexcept { Free(_ptr); } + + T* cobj() noexcept { return _ptr; } + const T* cobj() const noexcept { return _ptr; } + +protected: + explicit Wrapper(T* ptr) noexcept + : _ptr{ptr} + {} + +private: + T* _ptr; +}; + +} // namespace detail + +using Rect = PuglRect; ///< @copydoc PuglRect + +/** + @defgroup eventspp Events + @{ +*/ + +/** + A strongly-typed analogue of PuglEvent. + + This is bit-for-bit identical to the corresponding PuglEvent, so events are + simply cast to this type to avoid any copying overhead. + + @tparam t The `type` field of the corresponding PuglEvent. + + @tparam Base The specific struct type of the corresponding PuglEvent. +*/ +template +struct Event final : Base { + /// The type of the corresponding C event structure + using BaseEvent = Base; + + /// The `type` field of the corresponding C event structure + static constexpr const PuglEventType type = t; +}; + +using Mod = PuglMod; ///< @copydoc PuglMod +using Mods = PuglMods; ///< @copydoc PuglMods +using Key = PuglKey; ///< @copydoc PuglKey +using EventType = PuglEventType; ///< @copydoc PuglEventType +using EventFlag = PuglEventFlag; ///< @copydoc PuglEventFlag +using EventFlags = PuglEventFlags; ///< @copydoc PuglEventFlags +using CrossingMode = PuglCrossingMode; ///< @copydoc PuglCrossingMode + +/// @copydoc PuglEventCreate +using CreateEvent = Event; + +/// @copydoc PuglEventDestroy +using DestroyEvent = Event; + +/// @copydoc PuglEventConfigure +using ConfigureEvent = Event; + +/// @copydoc PuglEventMap +using MapEvent = Event; + +/// @copydoc PuglEventUnmap +using UnmapEvent = Event; + +/// @copydoc PuglEventUpdate +using UpdateEvent = Event; + +/// @copydoc PuglEventExpose +using ExposeEvent = Event; + +/// @copydoc PuglEventClose +using CloseEvent = Event; + +/// @copydoc PuglEventFocus +using FocusInEvent = Event; + +/// @copydoc PuglEventFocus +using FocusOutEvent = Event; + +/// @copydoc PuglEventKey +using KeyPressEvent = Event; + +/// @copydoc PuglEventKey +using KeyReleaseEvent = Event; + +/// @copydoc PuglEventText +using TextEvent = Event; + +/// @copydoc PuglEventCrossing +using PointerInEvent = Event; + +/// @copydoc PuglEventCrossing +using PointerOutEvent = Event; + +/// @copydoc PuglEventButton +using ButtonPressEvent = Event; + +/// @copydoc PuglEventButton +using ButtonReleaseEvent = Event; + +/// @copydoc PuglEventMotion +using MotionEvent = Event; + +/// @copydoc PuglEventScroll +using ScrollEvent = Event; + +/// @copydoc PuglEventClient +using ClientEvent = Event; + +/// @copydoc PuglEventTimer +using TimerEvent = Event; + +/// @copydoc PuglEventLoopEnter +using LoopEnterEvent = Event; + +/// @copydoc PuglEventLoopLeave +using LoopLeaveEvent = Event; + +/** + @} + @defgroup statuspp Status + @{ +*/ + +/// @copydoc PuglStatus +enum class Status { + success, ///< @copydoc PUGL_SUCCESS + failure, ///< @copydoc PUGL_FAILURE + unknownError, ///< @copydoc PUGL_UNKNOWN_ERROR + badBackend, ///< @copydoc PUGL_BAD_BACKEND + badConfiguration, ///< @copydoc PUGL_BAD_CONFIGURATION + badParameter, ///< @copydoc PUGL_BAD_PARAMETER + backendFailed, ///< @copydoc PUGL_BACKEND_FAILED + registrationFailed, ///< @copydoc PUGL_REGISTRATION_FAILED + realizeFailed, ///< @copydoc PUGL_REALIZE_FAILED + setFormatFailed, ///< @copydoc PUGL_SET_FORMAT_FAILED + createContextFailed, ///< @copydoc PUGL_CREATE_CONTEXT_FAILED + unsupportedType, ///< @copydoc PUGL_UNSUPPORTED_TYPE +}; + +static_assert(Status(PUGL_UNSUPPORTED_TYPE) == Status::unsupportedType, ""); + +/// @copydoc puglStrerror +inline const char* +strerror(const Status status) noexcept +{ + return puglStrerror(static_cast(status)); +} + +/** + @} + @defgroup worldpp World + @{ +*/ + +/// @copydoc PuglWorldType +enum class WorldType { + program, ///< @copydoc PUGL_PROGRAM + module, ///< @copydoc PUGL_MODULE +}; + +static_assert(WorldType(PUGL_MODULE) == WorldType::module, ""); + +/// @copydoc PuglWorldFlag +enum class WorldFlag { + threads = PUGL_WORLD_THREADS, ///< @copydoc PUGL_WORLD_THREADS +}; + +static_assert(WorldFlag(PUGL_WORLD_THREADS) == WorldFlag::threads, ""); + +using WorldFlags = PuglWorldFlags; ///< @copydoc PuglWorldFlags + +#if defined(PUGL_HPP_THROW_FAILED_CONSTRUCTION) + +/// An exception thrown when construction fails +class FailedConstructionError : public std::exception +{ +public: + FailedConstructionError(const char* const msg) noexcept + : _msg{msg} + {} + + virtual const char* what() const noexcept override; + +private: + const char* _msg; +}; + +# define PUGL_CHECK_CONSTRUCTION(cond, msg) \ + do { \ + if (!(cond)) { \ + throw FailedConstructionError(msg); \ + } \ + } while (0) + +#elif defined(PUGL_HPP_ASSERT_CONSTRUCTION) +# define PUGL_CHECK_CONSTRUCTION(cond, msg) assert(cond); +#else +/** + Configurable macro for handling construction failure. + + If `PUGL_HPP_THROW_FAILED_CONSTRUCTION` is defined, then this throws a + `pugl::FailedConstructionError` if construction fails. + + If `PUGL_HPP_ASSERT_CONSTRUCTION` is defined, then this asserts if + construction fails. + + Otherwise, this does nothing. +*/ +# define PUGL_CHECK_CONSTRUCTION(cond, msg) +#endif + +/// @copydoc PuglWorld +class World : public detail::Wrapper +{ +public: + World(const World&) = delete; + World& operator=(const World&) = delete; + + World(World&&) = delete; + World& operator=(World&&) = delete; + + ~World() = default; + + World(WorldType type, WorldFlag flag) + : Wrapper{puglNewWorld(static_cast(type), + static_cast(flag))} + { + PUGL_CHECK_CONSTRUCTION(cobj(), "Failed to create pugl::World"); + } + + World(WorldType type, WorldFlags flags) + : Wrapper{puglNewWorld(static_cast(type), flags)} + { + PUGL_CHECK_CONSTRUCTION(cobj(), "Failed to create pugl::World"); + } + + explicit World(WorldType type) + : World{type, WorldFlags{}} + {} + + /// @copydoc puglGetNativeWorld + void* nativeWorld() noexcept { return puglGetNativeWorld(cobj()); } + + /// @copydoc puglSetClassName + Status setClassName(const char* const name) noexcept + { + return static_cast(puglSetClassName(cobj(), name)); + } + + /// @copydoc puglGetTime + double time() const noexcept { return puglGetTime(cobj()); } + + /// @copydoc puglUpdate + Status update(const double timeout) noexcept + { + return static_cast(puglUpdate(cobj(), timeout)); + } +}; + +/** + @} + @defgroup viewpp View + @{ +*/ + +using Backend = PuglBackend; ///< @copydoc PuglBackend +using NativeView = PuglNativeView; ///< @copydoc PuglNativeView + +/// @copydoc PuglViewHint +enum class ViewHint { + useCompatProfile, ///< @copydoc PUGL_USE_COMPAT_PROFILE + useDebugContext, ///< @copydoc PUGL_USE_DEBUG_CONTEXT + contextVersionMajor, ///< @copydoc PUGL_CONTEXT_VERSION_MAJOR + contextVersionMinor, ///< @copydoc PUGL_CONTEXT_VERSION_MINOR + redBits, ///< @copydoc PUGL_RED_BITS + greenBits, ///< @copydoc PUGL_GREEN_BITS + blueBits, ///< @copydoc PUGL_BLUE_BITS + alphaBits, ///< @copydoc PUGL_ALPHA_BITS + depthBits, ///< @copydoc PUGL_DEPTH_BITS + stencilBits, ///< @copydoc PUGL_STENCIL_BITS + samples, ///< @copydoc PUGL_SAMPLES + doubleBuffer, ///< @copydoc PUGL_DOUBLE_BUFFER + swapInterval, ///< @copydoc PUGL_SWAP_INTERVAL + resizable, ///< @copydoc PUGL_RESIZABLE + ignoreKeyRepeat, ///< @copydoc PUGL_IGNORE_KEY_REPEAT + refreshRate, ///< @copydoc PUGL_REFRESH_RATE +}; + +static_assert(ViewHint(PUGL_REFRESH_RATE) == ViewHint::refreshRate, ""); + +using ViewHintValue = PuglViewHintValue; ///< @copydoc PuglViewHintValue + +/// @copydoc PuglCursor +enum class Cursor { + arrow, ///< @copydoc PUGL_CURSOR_ARROW + caret, ///< @copydoc PUGL_CURSOR_CARET + crosshair, ///< @copydoc PUGL_CURSOR_CROSSHAIR + hand, ///< @copydoc PUGL_CURSOR_HAND + no, ///< @copydoc PUGL_CURSOR_NO + leftRight, ///< @copydoc PUGL_CURSOR_LEFT_RIGHT + upDown, ///< @copydoc PUGL_CURSOR_UP_DOWN +}; + +static_assert(Cursor(PUGL_CURSOR_UP_DOWN) == Cursor::upDown, ""); + +/// @copydoc PuglView +class View : protected detail::Wrapper +{ +public: + /** + @name Setup + Methods for creating and destroying a view. + @{ + */ + + explicit View(World& world) + : Wrapper{puglNewView(world.cobj())} + , _world(world) + { + PUGL_CHECK_CONSTRUCTION(cobj(), "Failed to create pugl::View"); + } + + const World& world() const noexcept { return _world; } + World& world() noexcept { return _world; } + + /** + Set the object that will be called to handle events. + + This is a type-safe wrapper for the C functions puglSetHandle() and + puglSetEventFunc() that will automatically dispatch events to the + `onEvent` method of `handler` that takes the appropriate event type. + The handler must have such a method defined for every event type, but if + the handler is the view itself, a `using` declaration can be used to + "inherit" the default implementation to avoid having to define every + method. For example: + + @code + class MyView : public pugl::View + { + public: + explicit MyView(pugl::World& world) + : pugl::View{world} + { + setEventHandler(*this); + } + + using pugl::View::onEvent; + + pugl::Status onEvent(const pugl::ConfigureEvent& event) noexcept; + pugl::Status onEvent(const pugl::ExposeEvent& event) noexcept; + }; + @endcode + + This facility is just a convenience, applications may use the C API + directly to set a handle and event function to set up a different + approach for event handling. + */ + template + Status setEventHandler(Handler& handler) + { + puglSetHandle(cobj(), &handler); + return static_cast(puglSetEventFunc(cobj(), eventFunc)); + } + + /// @copydoc puglSetBackend + Status setBackend(const PuglBackend* backend) noexcept + { + return static_cast(puglSetBackend(cobj(), backend)); + } + + /// @copydoc puglSetViewHint + Status setHint(ViewHint hint, int value) noexcept + { + return static_cast( + puglSetViewHint(cobj(), static_cast(hint), value)); + } + + /// @copydoc puglGetViewHint + int getHint(ViewHint hint) noexcept + { + return puglGetViewHint(cobj(), static_cast(hint)); + } + + /** + @} + @name Frame + Methods for working with the position and size of a view. + @{ + */ + + /// @copydoc puglGetFrame + Rect frame() const noexcept { return puglGetFrame(cobj()); } + + /// @copydoc puglSetFrame + Status setFrame(Rect frame) noexcept + { + return static_cast(puglSetFrame(cobj(), frame)); + } + + /// @copydoc puglSetDefaultSize + Status setDefaultSize(int width, int height) noexcept + { + return static_cast(puglSetDefaultSize(cobj(), width, height)); + } + + /// @copydoc puglSetMinSize + Status setMinSize(int width, int height) noexcept + { + return static_cast(puglSetMinSize(cobj(), width, height)); + } + + /// @copydoc puglSetMaxSize + Status setMaxSize(int width, int height) noexcept + { + return static_cast(puglSetMaxSize(cobj(), width, height)); + } + + /// @copydoc puglSetAspectRatio + Status setAspectRatio(int minX, int minY, int maxX, int maxY) noexcept + { + return static_cast( + puglSetAspectRatio(cobj(), minX, minY, maxX, maxY)); + } + + /** + @} + @name Windows + Methods for working with top-level windows. + @{ + */ + + /// @copydoc puglSetWindowTitle + Status setWindowTitle(const char* title) noexcept + { + return static_cast(puglSetWindowTitle(cobj(), title)); + } + + /// @copydoc puglSetParentWindow + Status setParentWindow(NativeView parent) noexcept + { + return static_cast(puglSetParentWindow(cobj(), parent)); + } + + /// @copydoc puglSetTransientFor + Status setTransientFor(NativeView parent) noexcept + { + return static_cast(puglSetTransientFor(cobj(), parent)); + } + + /// @copydoc puglRealize + Status realize() noexcept { return static_cast(puglRealize(cobj())); } + + /// @copydoc puglShow + Status show() noexcept { return static_cast(puglShow(cobj())); } + + /// @copydoc puglHide + Status hide() noexcept { return static_cast(puglHide(cobj())); } + + /// @copydoc puglGetVisible + bool visible() const noexcept { return puglGetVisible(cobj()); } + + /// @copydoc puglGetNativeWindow + NativeView nativeWindow() noexcept { return puglGetNativeWindow(cobj()); } + + /** + @} + @name Graphics + Methods for working with the graphics context and scheduling + redisplays. + @{ + */ + + /// @copydoc puglGetContext + void* context() noexcept { return puglGetContext(cobj()); } + + /// @copydoc puglPostRedisplay + Status postRedisplay() noexcept + { + return static_cast(puglPostRedisplay(cobj())); + } + + /// @copydoc puglPostRedisplayRect + Status postRedisplayRect(const Rect rect) noexcept + { + return static_cast(puglPostRedisplayRect(cobj(), rect)); + } + + /** + @} + @name Interaction + Methods for interacting with the user and window system. + @{ + */ + + /// @copydoc puglGrabFocus + Status grabFocus() noexcept + { + return static_cast(puglGrabFocus(cobj())); + } + + /// @copydoc puglHasFocus + bool hasFocus() const noexcept { return puglHasFocus(cobj()); } + + /// @copydoc puglSetCursor + Status setCursor(const Cursor cursor) noexcept + { + return static_cast( + puglSetCursor(cobj(), static_cast(cursor))); + } + + /// @copydoc puglRequestAttention + Status requestAttention() noexcept + { + return static_cast(puglRequestAttention(cobj())); + } + + /** + Activate a repeating timer event. + + This starts a timer which will send a timer event to `view` every + `timeout` seconds. This can be used to perform some action in a view at a + regular interval with relatively low frequency. Note that the frequency + of timer events may be limited by how often update() is called. + + If the given timer already exists, it is replaced. + + @param id The identifier for this timer. This is an application-specific + ID that should be a low number, typically the value of a constant or `enum` + that starts from 0. There is a platform-specific limit to the number of + supported timers, and overhead associated with each, so applications should + create only a few timers and perform several tasks in one if necessary. + + @param timeout The period, in seconds, of this timer. This is not + guaranteed to have a resolution better than 10ms (the maximum timer + resolution on Windows) and may be rounded up if it is too short. On X11 + and MacOS, a resolution of about 1ms can usually be relied on. + + @return #PUGL_FAILURE if timers are not supported by the system, + #PUGL_UNKNOWN_ERROR if setting the timer failed. + */ + Status startTimer(const uintptr_t id, const double timeout) noexcept + { + return static_cast(puglStartTimer(cobj(), id, timeout)); + } + + /** + Stop an active timer. + + @param id The ID previously passed to startTimer(). + + @return #PUGL_FAILURE if timers are not supported by this system, + #PUGL_UNKNOWN_ERROR if stopping the timer failed. + */ + Status stopTimer(const uintptr_t id) noexcept + { + return static_cast(puglStopTimer(cobj(), id)); + } + + /** + @} + */ + + PuglView* cobj() noexcept { return Wrapper::cobj(); } + const PuglView* cobj() const noexcept { return Wrapper::cobj(); } + +private: + template + static Status dispatch(Target& target, const PuglEvent* event) + { + switch (event->type) { + case PUGL_NOTHING: + return Status::success; + case PUGL_CREATE: + return target.onEvent(static_cast(event->any)); + case PUGL_DESTROY: + return target.onEvent(static_cast(event->any)); + case PUGL_CONFIGURE: + return target.onEvent( + static_cast(event->configure)); + case PUGL_MAP: + return target.onEvent(static_cast(event->any)); + case PUGL_UNMAP: + return target.onEvent(static_cast(event->any)); + case PUGL_UPDATE: + return target.onEvent(static_cast(event->any)); + case PUGL_EXPOSE: + return target.onEvent(static_cast(event->expose)); + case PUGL_CLOSE: + return target.onEvent(static_cast(event->any)); + case PUGL_FOCUS_IN: + return target.onEvent(static_cast(event->focus)); + case PUGL_FOCUS_OUT: + return target.onEvent(static_cast(event->focus)); + case PUGL_KEY_PRESS: + return target.onEvent(static_cast(event->key)); + case PUGL_KEY_RELEASE: + return target.onEvent(static_cast(event->key)); + case PUGL_TEXT: + return target.onEvent(static_cast(event->text)); + case PUGL_POINTER_IN: + return target.onEvent( + static_cast(event->crossing)); + case PUGL_POINTER_OUT: + return target.onEvent( + static_cast(event->crossing)); + case PUGL_BUTTON_PRESS: + return target.onEvent( + static_cast(event->button)); + case PUGL_BUTTON_RELEASE: + return target.onEvent( + static_cast(event->button)); + case PUGL_MOTION: + return target.onEvent(static_cast(event->motion)); + case PUGL_SCROLL: + return target.onEvent(static_cast(event->scroll)); + case PUGL_CLIENT: + return target.onEvent(static_cast(event->client)); + case PUGL_TIMER: + return target.onEvent(static_cast(event->timer)); + case PUGL_LOOP_ENTER: + return target.onEvent(static_cast(event->any)); + case PUGL_LOOP_LEAVE: + return target.onEvent(static_cast(event->any)); + } + + return Status::failure; + } + + template + static PuglStatus eventFunc(PuglView* view, const PuglEvent* event) noexcept + { + auto* target = static_cast(puglGetHandle(view)); + +#ifdef __cpp_exceptions + try { + return static_cast(dispatch(*target, event)); + } catch (...) { + return PUGL_UNKNOWN_ERROR; + } +#else + return static_cast(pugl::dispatch(*target, event)); +#endif + } + + World& _world; +}; + +/** + @} + @} +*/ + +} // namespace pugl + +#endif // PUGL_PUGL_HPP diff --git a/bindings/cpp/include/pugl/stub.hpp b/bindings/cpp/include/pugl/stub.hpp new file mode 100644 index 0000000..e4a33c1 --- /dev/null +++ b/bindings/cpp/include/pugl/stub.hpp @@ -0,0 +1,45 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef PUGL_STUB_HPP +#define PUGL_STUB_HPP + +#include "pugl/pugl.h" +#include "pugl/stub.h" + +namespace pugl { + +/** + @defgroup stubpp Stub + Stub graphics support. + @ingroup puglpp + @{ +*/ + +/// @copydoc puglStubBackend +inline const PuglBackend* +stubBackend() noexcept +{ + return puglStubBackend(); +} + +/** + @} +*/ + +} // namespace pugl + +#endif // PUGL_STUB_HPP diff --git a/bindings/cpp/include/pugl/vulkan.hpp b/bindings/cpp/include/pugl/vulkan.hpp new file mode 100644 index 0000000..d65b2d6 --- /dev/null +++ b/bindings/cpp/include/pugl/vulkan.hpp @@ -0,0 +1,168 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* + Note that this header includes Vulkan headers, so if you are writing a + program or plugin that dynamically loads vulkan, you should first define + `VK_NO_PROTOTYPES` before including it. +*/ + +#ifndef PUGL_VULKAN_HPP +#define PUGL_VULKAN_HPP + +#include "pugl/pugl.h" +#include "pugl/pugl.hpp" +#include "pugl/vulkan.h" + +#include + +#include + +namespace pugl { + +/** + @defgroup vulkanpp Vulkan + Vulkan graphics support. + + Note that the Pugl C++ wrapper does not use vulkan-hpp because it is a + heavyweight dependency which not everyone uses, and its design is not very + friendly to dynamic loading in plugins anyway. However, if you do use + vulkan-hpp smart handles, it is relatively straightforward to wrap the + result of createSurface() manually. + + @ingroup puglpp + @{ +*/ + +/// @copydoc PuglVulkanLoader +class VulkanLoader final + : public detail::Wrapper +{ +public: + /** + Create a new dynamic loader for Vulkan functions. + + This dynamically loads the Vulkan library and gets the load functions + from it. + + Note that this constructor does not throw exceptions, though failure is + possible. To check if the Vulkan library failed to load, test this + loader, which is explicitly convertible to `bool`. It is safe to use a + failed loader, but the accessors will always return null. + */ + explicit VulkanLoader(World& world) noexcept + : Wrapper{puglNewVulkanLoader(world.cobj())} + {} + + /** + Return the `vkGetInstanceProcAddr` function. + + @return Null if the Vulkan library failed to load, or does not contain + this function (which is unlikely and indicates a broken system). + */ + PFN_vkGetInstanceProcAddr getInstanceProcAddrFunc() const noexcept + { + return cobj() ? puglGetInstanceProcAddrFunc(cobj()) : nullptr; + } + + /** + Return the `vkGetDeviceProcAddr` function. + + @return Null if the Vulkan library failed to load, or does not contain + this function (which is unlikely and indicates a broken system). + */ + PFN_vkGetDeviceProcAddr getDeviceProcAddrFunc() const noexcept + { + return cobj() ? puglGetDeviceProcAddrFunc(cobj()) : nullptr; + } + + /// Return true if this loader is valid to use + explicit operator bool() const noexcept { return cobj(); } +}; + +/** + A simple wrapper for an array of static C strings. + + This provides a minimal API that supports iteration, like `std::vector`, but + avoids allocation, exceptions, and a dependency on the C++ standard library. +*/ +class StaticStringArray final +{ +public: + using value_type = const char*; + using const_iterator = const char* const*; + using size_type = uint32_t; + + StaticStringArray(const char* const* strings, const uint32_t size) noexcept + : _strings{strings} + , _size{size} + {} + + const char* const* begin() const noexcept { return _strings; } + const char* const* end() const noexcept { return _strings + _size; } + const char* const* data() const noexcept { return _strings; } + uint32_t size() const noexcept { return _size; } + +private: + const char* const* _strings; + uint32_t _size; +}; + +/** + Return the Vulkan instance extensions required to draw to a PuglView. + + If successful, the returned array always contains "VK_KHR_surface", along + with whatever other platform-specific extensions are required. + + @return An array of extension name strings. +*/ +inline StaticStringArray +getInstanceExtensions() noexcept +{ + uint32_t count = 0; + const char* const* const extensions = puglGetInstanceExtensions(&count); + + return StaticStringArray{extensions, count}; +} + +/// @copydoc puglCreateSurface +inline VkResult +createSurface(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr, + View& view, + VkInstance instance, + const VkAllocationCallbacks* const allocator, + VkSurfaceKHR* const surface) noexcept +{ + const VkResult r = puglCreateSurface( + vkGetInstanceProcAddr, view.cobj(), instance, allocator, surface); + + return (!r && !surface) ? VK_ERROR_INITIALIZATION_FAILED : r; +} + +/// @copydoc puglVulkanBackend +inline const PuglBackend* +vulkanBackend() noexcept +{ + return puglVulkanBackend(); +} + +/** + @} +*/ + +} // namespace pugl + +#endif // PUGL_VULKAN_HPP diff --git a/bindings/cxx/include/.clang-tidy b/bindings/cxx/include/.clang-tidy deleted file mode 100644 index 816223d..0000000 --- a/bindings/cxx/include/.clang-tidy +++ /dev/null @@ -1,14 +0,0 @@ -Checks: > - *, - -*-uppercase-literal-suffix, - -clang-diagnostic-unused-macros, - -cppcoreguidelines-pro-bounds-pointer-arithmetic, - -cppcoreguidelines-pro-type-static-cast-downcast, - -google-runtime-references, - -hicpp-named-parameter, - -llvmlibc-*, - -modernize-use-trailing-return-type, - -readability-implicit-bool-conversion, - -readability-named-parameter, -FormatStyle: file -HeaderFilterRegex: 'pugl/.*' diff --git a/bindings/cxx/include/pugl/cairo.hpp b/bindings/cxx/include/pugl/cairo.hpp deleted file mode 100644 index 15dc5de..0000000 --- a/bindings/cxx/include/pugl/cairo.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef PUGL_CAIRO_HPP -#define PUGL_CAIRO_HPP - -#include "pugl/cairo.h" -#include "pugl/pugl.h" - -namespace pugl { - -/** - @defgroup cairoxx Cairo - Cairo graphics support. - @ingroup puglxx - @{ -*/ - -/// @copydoc puglCairoBackend -inline const PuglBackend* -cairoBackend() noexcept -{ - return puglCairoBackend(); -} - -/** - @} -*/ - -} // namespace pugl - -#endif // PUGL_CAIRO_HPP diff --git a/bindings/cxx/include/pugl/gl.hpp b/bindings/cxx/include/pugl/gl.hpp deleted file mode 100644 index 023dd45..0000000 --- a/bindings/cxx/include/pugl/gl.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef PUGL_GL_HPP -#define PUGL_GL_HPP - -#include "pugl/gl.h" -#include "pugl/pugl.h" -#include "pugl/pugl.hpp" - -namespace pugl { - -/** - @defgroup glxx OpenGL - OpenGL graphics support. - @ingroup puglxx - @{ -*/ - -/// @copydoc PuglGlFunc -using GlFunc = PuglGlFunc; - -/// @copydoc puglGetProcAddress -inline GlFunc -getProcAddress(const char* name) noexcept -{ - return puglGetProcAddress(name); -} - -/// @copydoc puglEnterContext -inline Status -enterContext(View& view) noexcept -{ - return static_cast(puglEnterContext(view.cobj())); -} - -/// @copydoc puglLeaveContext -inline Status -leaveContext(View& view) noexcept -{ - return static_cast(puglLeaveContext(view.cobj())); -} - -/// @copydoc puglGlBackend -inline const PuglBackend* -glBackend() noexcept -{ - return puglGlBackend(); -} - -/** - @} -*/ - -} // namespace pugl - -#endif // PUGL_GL_HPP diff --git a/bindings/cxx/include/pugl/pugl.hpp b/bindings/cxx/include/pugl/pugl.hpp deleted file mode 100644 index fc3bb03..0000000 --- a/bindings/cxx/include/pugl/pugl.hpp +++ /dev/null @@ -1,721 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef PUGL_PUGL_HPP -#define PUGL_PUGL_HPP - -#include "pugl/pugl.h" - -#include - -#if defined(PUGL_HPP_THROW_FAILED_CONSTRUCTION) -# include -#elif defined(PUGL_HPP_ASSERT_CONSTRUCTION) -# include -#endif - -namespace pugl { - -/** - @defgroup puglxx Pugl C++ API - Pugl C++ API wrapper. - @{ -*/ - -namespace detail { - -/// Free function for a C object -template -using FreeFunc = void (*)(T*); - -/// Generic C++ wrapper for a C object -template Free> -class Wrapper -{ -public: - Wrapper(const Wrapper&) = delete; - Wrapper& operator=(const Wrapper&) = delete; - - Wrapper(Wrapper&& wrapper) noexcept - : _ptr{wrapper._ptr} - { - wrapper._ptr = nullptr; - } - - Wrapper& operator=(Wrapper&& wrapper) noexcept - { - _ptr = wrapper._ptr; - wrapper._ptr = nullptr; - return *this; - } - - ~Wrapper() noexcept { Free(_ptr); } - - T* cobj() noexcept { return _ptr; } - const T* cobj() const noexcept { return _ptr; } - -protected: - explicit Wrapper(T* ptr) noexcept - : _ptr{ptr} - {} - -private: - T* _ptr; -}; - -} // namespace detail - -using Rect = PuglRect; ///< @copydoc PuglRect - -/** - @defgroup eventsxx Events - @{ -*/ - -/** - A strongly-typed analogue of PuglEvent. - - This is bit-for-bit identical to the corresponding PuglEvent, so events are - simply cast to this type to avoid any copying overhead. - - @tparam t The `type` field of the corresponding PuglEvent. - - @tparam Base The specific struct type of the corresponding PuglEvent. -*/ -template -struct Event final : Base { - /// The type of the corresponding C event structure - using BaseEvent = Base; - - /// The `type` field of the corresponding C event structure - static constexpr const PuglEventType type = t; -}; - -using Mod = PuglMod; ///< @copydoc PuglMod -using Mods = PuglMods; ///< @copydoc PuglMods -using Key = PuglKey; ///< @copydoc PuglKey -using EventType = PuglEventType; ///< @copydoc PuglEventType -using EventFlag = PuglEventFlag; ///< @copydoc PuglEventFlag -using EventFlags = PuglEventFlags; ///< @copydoc PuglEventFlags -using CrossingMode = PuglCrossingMode; ///< @copydoc PuglCrossingMode - -/// @copydoc PuglEventCreate -using CreateEvent = Event; - -/// @copydoc PuglEventDestroy -using DestroyEvent = Event; - -/// @copydoc PuglEventConfigure -using ConfigureEvent = Event; - -/// @copydoc PuglEventMap -using MapEvent = Event; - -/// @copydoc PuglEventUnmap -using UnmapEvent = Event; - -/// @copydoc PuglEventUpdate -using UpdateEvent = Event; - -/// @copydoc PuglEventExpose -using ExposeEvent = Event; - -/// @copydoc PuglEventClose -using CloseEvent = Event; - -/// @copydoc PuglEventFocus -using FocusInEvent = Event; - -/// @copydoc PuglEventFocus -using FocusOutEvent = Event; - -/// @copydoc PuglEventKey -using KeyPressEvent = Event; - -/// @copydoc PuglEventKey -using KeyReleaseEvent = Event; - -/// @copydoc PuglEventText -using TextEvent = Event; - -/// @copydoc PuglEventCrossing -using PointerInEvent = Event; - -/// @copydoc PuglEventCrossing -using PointerOutEvent = Event; - -/// @copydoc PuglEventButton -using ButtonPressEvent = Event; - -/// @copydoc PuglEventButton -using ButtonReleaseEvent = Event; - -/// @copydoc PuglEventMotion -using MotionEvent = Event; - -/// @copydoc PuglEventScroll -using ScrollEvent = Event; - -/// @copydoc PuglEventClient -using ClientEvent = Event; - -/// @copydoc PuglEventTimer -using TimerEvent = Event; - -/// @copydoc PuglEventLoopEnter -using LoopEnterEvent = Event; - -/// @copydoc PuglEventLoopLeave -using LoopLeaveEvent = Event; - -/** - @} - @defgroup statusxx Status - @{ -*/ - -/// @copydoc PuglStatus -enum class Status { - success, ///< @copydoc PUGL_SUCCESS - failure, ///< @copydoc PUGL_FAILURE - unknownError, ///< @copydoc PUGL_UNKNOWN_ERROR - badBackend, ///< @copydoc PUGL_BAD_BACKEND - badConfiguration, ///< @copydoc PUGL_BAD_CONFIGURATION - badParameter, ///< @copydoc PUGL_BAD_PARAMETER - backendFailed, ///< @copydoc PUGL_BACKEND_FAILED - registrationFailed, ///< @copydoc PUGL_REGISTRATION_FAILED - realizeFailed, ///< @copydoc PUGL_REALIZE_FAILED - setFormatFailed, ///< @copydoc PUGL_SET_FORMAT_FAILED - createContextFailed, ///< @copydoc PUGL_CREATE_CONTEXT_FAILED - unsupportedType, ///< @copydoc PUGL_UNSUPPORTED_TYPE -}; - -static_assert(Status(PUGL_UNSUPPORTED_TYPE) == Status::unsupportedType, ""); - -/// @copydoc puglStrerror -inline const char* -strerror(const Status status) noexcept -{ - return puglStrerror(static_cast(status)); -} - -/** - @} - @defgroup worldxx World - @{ -*/ - -/// @copydoc PuglWorldType -enum class WorldType { - program, ///< @copydoc PUGL_PROGRAM - module, ///< @copydoc PUGL_MODULE -}; - -static_assert(WorldType(PUGL_MODULE) == WorldType::module, ""); - -/// @copydoc PuglWorldFlag -enum class WorldFlag { - threads = PUGL_WORLD_THREADS, ///< @copydoc PUGL_WORLD_THREADS -}; - -static_assert(WorldFlag(PUGL_WORLD_THREADS) == WorldFlag::threads, ""); - -using WorldFlags = PuglWorldFlags; ///< @copydoc PuglWorldFlags - -#if defined(PUGL_HPP_THROW_FAILED_CONSTRUCTION) - -/// An exception thrown when construction fails -class FailedConstructionError : public std::exception -{ -public: - FailedConstructionError(const char* const msg) noexcept - : _msg{msg} - {} - - virtual const char* what() const noexcept override; - -private: - const char* _msg; -}; - -# define PUGL_CHECK_CONSTRUCTION(cond, msg) \ - do { \ - if (!(cond)) { \ - throw FailedConstructionError(msg); \ - } \ - } while (0) - -#elif defined(PUGL_HPP_ASSERT_CONSTRUCTION) -# define PUGL_CHECK_CONSTRUCTION(cond, msg) assert(cond); -#else -/** - Configurable macro for handling construction failure. - - If `PUGL_HPP_THROW_FAILED_CONSTRUCTION` is defined, then this throws a - `pugl::FailedConstructionError` if construction fails. - - If `PUGL_HPP_ASSERT_CONSTRUCTION` is defined, then this asserts if - construction fails. - - Otherwise, this does nothing. -*/ -# define PUGL_CHECK_CONSTRUCTION(cond, msg) -#endif - -/// @copydoc PuglWorld -class World : public detail::Wrapper -{ -public: - World(const World&) = delete; - World& operator=(const World&) = delete; - - World(World&&) = delete; - World& operator=(World&&) = delete; - - ~World() = default; - - World(WorldType type, WorldFlag flag) - : Wrapper{puglNewWorld(static_cast(type), - static_cast(flag))} - { - PUGL_CHECK_CONSTRUCTION(cobj(), "Failed to create pugl::World"); - } - - World(WorldType type, WorldFlags flags) - : Wrapper{puglNewWorld(static_cast(type), flags)} - { - PUGL_CHECK_CONSTRUCTION(cobj(), "Failed to create pugl::World"); - } - - explicit World(WorldType type) - : World{type, WorldFlags{}} - {} - - /// @copydoc puglGetNativeWorld - void* nativeWorld() noexcept { return puglGetNativeWorld(cobj()); } - - /// @copydoc puglSetClassName - Status setClassName(const char* const name) noexcept - { - return static_cast(puglSetClassName(cobj(), name)); - } - - /// @copydoc puglGetTime - double time() const noexcept { return puglGetTime(cobj()); } - - /// @copydoc puglUpdate - Status update(const double timeout) noexcept - { - return static_cast(puglUpdate(cobj(), timeout)); - } -}; - -/** - @} - @defgroup viewxx View - @{ -*/ - -using Backend = PuglBackend; ///< @copydoc PuglBackend -using NativeView = PuglNativeView; ///< @copydoc PuglNativeView - -/// @copydoc PuglViewHint -enum class ViewHint { - useCompatProfile, ///< @copydoc PUGL_USE_COMPAT_PROFILE - useDebugContext, ///< @copydoc PUGL_USE_DEBUG_CONTEXT - contextVersionMajor, ///< @copydoc PUGL_CONTEXT_VERSION_MAJOR - contextVersionMinor, ///< @copydoc PUGL_CONTEXT_VERSION_MINOR - redBits, ///< @copydoc PUGL_RED_BITS - greenBits, ///< @copydoc PUGL_GREEN_BITS - blueBits, ///< @copydoc PUGL_BLUE_BITS - alphaBits, ///< @copydoc PUGL_ALPHA_BITS - depthBits, ///< @copydoc PUGL_DEPTH_BITS - stencilBits, ///< @copydoc PUGL_STENCIL_BITS - samples, ///< @copydoc PUGL_SAMPLES - doubleBuffer, ///< @copydoc PUGL_DOUBLE_BUFFER - swapInterval, ///< @copydoc PUGL_SWAP_INTERVAL - resizable, ///< @copydoc PUGL_RESIZABLE - ignoreKeyRepeat, ///< @copydoc PUGL_IGNORE_KEY_REPEAT - refreshRate, ///< @copydoc PUGL_REFRESH_RATE -}; - -static_assert(ViewHint(PUGL_REFRESH_RATE) == ViewHint::refreshRate, ""); - -using ViewHintValue = PuglViewHintValue; ///< @copydoc PuglViewHintValue - -/// @copydoc PuglCursor -enum class Cursor { - arrow, ///< @copydoc PUGL_CURSOR_ARROW - caret, ///< @copydoc PUGL_CURSOR_CARET - crosshair, ///< @copydoc PUGL_CURSOR_CROSSHAIR - hand, ///< @copydoc PUGL_CURSOR_HAND - no, ///< @copydoc PUGL_CURSOR_NO - leftRight, ///< @copydoc PUGL_CURSOR_LEFT_RIGHT - upDown, ///< @copydoc PUGL_CURSOR_UP_DOWN -}; - -static_assert(Cursor(PUGL_CURSOR_UP_DOWN) == Cursor::upDown, ""); - -/// @copydoc PuglView -class View : protected detail::Wrapper -{ -public: - /** - @name Setup - Methods for creating and destroying a view. - @{ - */ - - explicit View(World& world) - : Wrapper{puglNewView(world.cobj())} - , _world(world) - { - PUGL_CHECK_CONSTRUCTION(cobj(), "Failed to create pugl::View"); - } - - const World& world() const noexcept { return _world; } - World& world() noexcept { return _world; } - - /** - Set the object that will be called to handle events. - - This is a type-safe wrapper for the C functions puglSetHandle() and - puglSetEventFunc() that will automatically dispatch events to the - `onEvent` method of `handler` that takes the appropriate event type. - The handler must have such a method defined for every event type, but if - the handler is the view itself, a `using` declaration can be used to - "inherit" the default implementation to avoid having to define every - method. For example: - - @code - class MyView : public pugl::View - { - public: - explicit MyView(pugl::World& world) - : pugl::View{world} - { - setEventHandler(*this); - } - - using pugl::View::onEvent; - - pugl::Status onEvent(const pugl::ConfigureEvent& event) noexcept; - pugl::Status onEvent(const pugl::ExposeEvent& event) noexcept; - }; - @endcode - - This facility is just a convenience, applications may use the C API - directly to set a handle and event function to set up a different - approach for event handling. - */ - template - Status setEventHandler(Handler& handler) - { - puglSetHandle(cobj(), &handler); - return static_cast(puglSetEventFunc(cobj(), eventFunc)); - } - - /// @copydoc puglSetBackend - Status setBackend(const PuglBackend* backend) noexcept - { - return static_cast(puglSetBackend(cobj(), backend)); - } - - /// @copydoc puglSetViewHint - Status setHint(ViewHint hint, int value) noexcept - { - return static_cast( - puglSetViewHint(cobj(), static_cast(hint), value)); - } - - /// @copydoc puglGetViewHint - int getHint(ViewHint hint) noexcept - { - return puglGetViewHint(cobj(), static_cast(hint)); - } - - /** - @} - @name Frame - Methods for working with the position and size of a view. - @{ - */ - - /// @copydoc puglGetFrame - Rect frame() const noexcept { return puglGetFrame(cobj()); } - - /// @copydoc puglSetFrame - Status setFrame(Rect frame) noexcept - { - return static_cast(puglSetFrame(cobj(), frame)); - } - - /// @copydoc puglSetDefaultSize - Status setDefaultSize(int width, int height) noexcept - { - return static_cast(puglSetDefaultSize(cobj(), width, height)); - } - - /// @copydoc puglSetMinSize - Status setMinSize(int width, int height) noexcept - { - return static_cast(puglSetMinSize(cobj(), width, height)); - } - - /// @copydoc puglSetMaxSize - Status setMaxSize(int width, int height) noexcept - { - return static_cast(puglSetMaxSize(cobj(), width, height)); - } - - /// @copydoc puglSetAspectRatio - Status setAspectRatio(int minX, int minY, int maxX, int maxY) noexcept - { - return static_cast( - puglSetAspectRatio(cobj(), minX, minY, maxX, maxY)); - } - - /** - @} - @name Windows - Methods for working with top-level windows. - @{ - */ - - /// @copydoc puglSetWindowTitle - Status setWindowTitle(const char* title) noexcept - { - return static_cast(puglSetWindowTitle(cobj(), title)); - } - - /// @copydoc puglSetParentWindow - Status setParentWindow(NativeView parent) noexcept - { - return static_cast(puglSetParentWindow(cobj(), parent)); - } - - /// @copydoc puglSetTransientFor - Status setTransientFor(NativeView parent) noexcept - { - return static_cast(puglSetTransientFor(cobj(), parent)); - } - - /// @copydoc puglRealize - Status realize() noexcept { return static_cast(puglRealize(cobj())); } - - /// @copydoc puglShow - Status show() noexcept { return static_cast(puglShow(cobj())); } - - /// @copydoc puglHide - Status hide() noexcept { return static_cast(puglHide(cobj())); } - - /// @copydoc puglGetVisible - bool visible() const noexcept { return puglGetVisible(cobj()); } - - /// @copydoc puglGetNativeWindow - NativeView nativeWindow() noexcept { return puglGetNativeWindow(cobj()); } - - /** - @} - @name Graphics - Methods for working with the graphics context and scheduling - redisplays. - @{ - */ - - /// @copydoc puglGetContext - void* context() noexcept { return puglGetContext(cobj()); } - - /// @copydoc puglPostRedisplay - Status postRedisplay() noexcept - { - return static_cast(puglPostRedisplay(cobj())); - } - - /// @copydoc puglPostRedisplayRect - Status postRedisplayRect(const Rect rect) noexcept - { - return static_cast(puglPostRedisplayRect(cobj(), rect)); - } - - /** - @} - @name Interaction - Methods for interacting with the user and window system. - @{ - */ - - /// @copydoc puglGrabFocus - Status grabFocus() noexcept - { - return static_cast(puglGrabFocus(cobj())); - } - - /// @copydoc puglHasFocus - bool hasFocus() const noexcept { return puglHasFocus(cobj()); } - - /// @copydoc puglSetCursor - Status setCursor(const Cursor cursor) noexcept - { - return static_cast( - puglSetCursor(cobj(), static_cast(cursor))); - } - - /// @copydoc puglRequestAttention - Status requestAttention() noexcept - { - return static_cast(puglRequestAttention(cobj())); - } - - /** - Activate a repeating timer event. - - This starts a timer which will send a timer event to `view` every - `timeout` seconds. This can be used to perform some action in a view at a - regular interval with relatively low frequency. Note that the frequency - of timer events may be limited by how often update() is called. - - If the given timer already exists, it is replaced. - - @param id The identifier for this timer. This is an application-specific - ID that should be a low number, typically the value of a constant or `enum` - that starts from 0. There is a platform-specific limit to the number of - supported timers, and overhead associated with each, so applications should - create only a few timers and perform several tasks in one if necessary. - - @param timeout The period, in seconds, of this timer. This is not - guaranteed to have a resolution better than 10ms (the maximum timer - resolution on Windows) and may be rounded up if it is too short. On X11 - and MacOS, a resolution of about 1ms can usually be relied on. - - @return #PUGL_FAILURE if timers are not supported by the system, - #PUGL_UNKNOWN_ERROR if setting the timer failed. - */ - Status startTimer(const uintptr_t id, const double timeout) noexcept - { - return static_cast(puglStartTimer(cobj(), id, timeout)); - } - - /** - Stop an active timer. - - @param id The ID previously passed to startTimer(). - - @return #PUGL_FAILURE if timers are not supported by this system, - #PUGL_UNKNOWN_ERROR if stopping the timer failed. - */ - Status stopTimer(const uintptr_t id) noexcept - { - return static_cast(puglStopTimer(cobj(), id)); - } - - /** - @} - */ - - PuglView* cobj() noexcept { return Wrapper::cobj(); } - const PuglView* cobj() const noexcept { return Wrapper::cobj(); } - -private: - template - static Status dispatch(Target& target, const PuglEvent* event) - { - switch (event->type) { - case PUGL_NOTHING: - return Status::success; - case PUGL_CREATE: - return target.onEvent(static_cast(event->any)); - case PUGL_DESTROY: - return target.onEvent(static_cast(event->any)); - case PUGL_CONFIGURE: - return target.onEvent( - static_cast(event->configure)); - case PUGL_MAP: - return target.onEvent(static_cast(event->any)); - case PUGL_UNMAP: - return target.onEvent(static_cast(event->any)); - case PUGL_UPDATE: - return target.onEvent(static_cast(event->any)); - case PUGL_EXPOSE: - return target.onEvent(static_cast(event->expose)); - case PUGL_CLOSE: - return target.onEvent(static_cast(event->any)); - case PUGL_FOCUS_IN: - return target.onEvent(static_cast(event->focus)); - case PUGL_FOCUS_OUT: - return target.onEvent(static_cast(event->focus)); - case PUGL_KEY_PRESS: - return target.onEvent(static_cast(event->key)); - case PUGL_KEY_RELEASE: - return target.onEvent(static_cast(event->key)); - case PUGL_TEXT: - return target.onEvent(static_cast(event->text)); - case PUGL_POINTER_IN: - return target.onEvent( - static_cast(event->crossing)); - case PUGL_POINTER_OUT: - return target.onEvent( - static_cast(event->crossing)); - case PUGL_BUTTON_PRESS: - return target.onEvent( - static_cast(event->button)); - case PUGL_BUTTON_RELEASE: - return target.onEvent( - static_cast(event->button)); - case PUGL_MOTION: - return target.onEvent(static_cast(event->motion)); - case PUGL_SCROLL: - return target.onEvent(static_cast(event->scroll)); - case PUGL_CLIENT: - return target.onEvent(static_cast(event->client)); - case PUGL_TIMER: - return target.onEvent(static_cast(event->timer)); - case PUGL_LOOP_ENTER: - return target.onEvent(static_cast(event->any)); - case PUGL_LOOP_LEAVE: - return target.onEvent(static_cast(event->any)); - } - - return Status::failure; - } - - template - static PuglStatus eventFunc(PuglView* view, const PuglEvent* event) noexcept - { - auto* target = static_cast(puglGetHandle(view)); - -#ifdef __cpp_exceptions - try { - return static_cast(dispatch(*target, event)); - } catch (...) { - return PUGL_UNKNOWN_ERROR; - } -#else - return static_cast(pugl::dispatch(*target, event)); -#endif - } - - World& _world; -}; - -/** - @} - @} -*/ - -} // namespace pugl - -#endif // PUGL_PUGL_HPP diff --git a/bindings/cxx/include/pugl/stub.hpp b/bindings/cxx/include/pugl/stub.hpp deleted file mode 100644 index fbafcee..0000000 --- a/bindings/cxx/include/pugl/stub.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef PUGL_STUB_HPP -#define PUGL_STUB_HPP - -#include "pugl/pugl.h" -#include "pugl/stub.h" - -namespace pugl { - -/** - @defgroup stubxx Stub - Stub graphics support. - @ingroup puglxx - @{ -*/ - -/// @copydoc puglStubBackend -inline const PuglBackend* -stubBackend() noexcept -{ - return puglStubBackend(); -} - -/** - @} -*/ - -} // namespace pugl - -#endif // PUGL_STUB_HPP diff --git a/bindings/cxx/include/pugl/vulkan.hpp b/bindings/cxx/include/pugl/vulkan.hpp deleted file mode 100644 index f3dbcad..0000000 --- a/bindings/cxx/include/pugl/vulkan.hpp +++ /dev/null @@ -1,168 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/* - Note that this header includes Vulkan headers, so if you are writing a - program or plugin that dynamically loads vulkan, you should first define - `VK_NO_PROTOTYPES` before including it. -*/ - -#ifndef PUGL_VULKAN_HPP -#define PUGL_VULKAN_HPP - -#include "pugl/pugl.h" -#include "pugl/pugl.hpp" -#include "pugl/vulkan.h" - -#include - -#include - -namespace pugl { - -/** - @defgroup vulkanxx Vulkan - Vulkan graphics support. - - Note that the Pugl C++ wrapper does not use vulkan-hpp because it is a - heavyweight dependency which not everyone uses, and its design is not very - friendly to dynamic loading in plugins anyway. However, if you do use - vulkan-hpp smart handles, it is relatively straightforward to wrap the - result of createSurface() manually. - - @ingroup puglxx - @{ -*/ - -/// @copydoc PuglVulkanLoader -class VulkanLoader final - : public detail::Wrapper -{ -public: - /** - Create a new dynamic loader for Vulkan functions. - - This dynamically loads the Vulkan library and gets the load functions - from it. - - Note that this constructor does not throw exceptions, though failure is - possible. To check if the Vulkan library failed to load, test this - loader, which is explicitly convertible to `bool`. It is safe to use a - failed loader, but the accessors will always return null. - */ - explicit VulkanLoader(World& world) noexcept - : Wrapper{puglNewVulkanLoader(world.cobj())} - {} - - /** - Return the `vkGetInstanceProcAddr` function. - - @return Null if the Vulkan library failed to load, or does not contain - this function (which is unlikely and indicates a broken system). - */ - PFN_vkGetInstanceProcAddr getInstanceProcAddrFunc() const noexcept - { - return cobj() ? puglGetInstanceProcAddrFunc(cobj()) : nullptr; - } - - /** - Return the `vkGetDeviceProcAddr` function. - - @return Null if the Vulkan library failed to load, or does not contain - this function (which is unlikely and indicates a broken system). - */ - PFN_vkGetDeviceProcAddr getDeviceProcAddrFunc() const noexcept - { - return cobj() ? puglGetDeviceProcAddrFunc(cobj()) : nullptr; - } - - /// Return true if this loader is valid to use - explicit operator bool() const noexcept { return cobj(); } -}; - -/** - A simple wrapper for an array of static C strings. - - This provides a minimal API that supports iteration, like `std::vector`, but - avoids allocation, exceptions, and a dependency on the C++ standard library. -*/ -class StaticStringArray final -{ -public: - using value_type = const char*; - using const_iterator = const char* const*; - using size_type = uint32_t; - - StaticStringArray(const char* const* strings, const uint32_t size) noexcept - : _strings{strings} - , _size{size} - {} - - const char* const* begin() const noexcept { return _strings; } - const char* const* end() const noexcept { return _strings + _size; } - const char* const* data() const noexcept { return _strings; } - uint32_t size() const noexcept { return _size; } - -private: - const char* const* _strings; - uint32_t _size; -}; - -/** - Return the Vulkan instance extensions required to draw to a PuglView. - - If successful, the returned array always contains "VK_KHR_surface", along - with whatever other platform-specific extensions are required. - - @return An array of extension name strings. -*/ -inline StaticStringArray -getInstanceExtensions() noexcept -{ - uint32_t count = 0; - const char* const* const extensions = puglGetInstanceExtensions(&count); - - return StaticStringArray{extensions, count}; -} - -/// @copydoc puglCreateSurface -inline VkResult -createSurface(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr, - View& view, - VkInstance instance, - const VkAllocationCallbacks* const allocator, - VkSurfaceKHR* const surface) noexcept -{ - const VkResult r = puglCreateSurface( - vkGetInstanceProcAddr, view.cobj(), instance, allocator, surface); - - return (!r && !surface) ? VK_ERROR_INITIALIZATION_FAILED : r; -} - -/// @copydoc puglVulkanBackend -inline const PuglBackend* -vulkanBackend() noexcept -{ - return puglVulkanBackend(); -} - -/** - @} -*/ - -} // namespace pugl - -#endif // PUGL_VULKAN_HPP diff --git a/doc/cpp/index.rst b/doc/cpp/index.rst index b11d028..76f45af 100644 --- a/doc/cpp/index.rst +++ b/doc/cpp/index.rst @@ -9,4 +9,4 @@ Pugl deployment overview api/pugl - api/puglxx + api/puglpp diff --git a/doc/cpp/meson.build b/doc/cpp/meson.build index d8bae11..ad25319 100644 --- a/doc/cpp/meson.build +++ b/doc/cpp/meson.build @@ -31,7 +31,7 @@ docs = custom_target( output: 'singlehtml', build_by_default: true, install: true, - install_dir: docdir / 'puglxx-0') + install_dir: docdir / 'puglpp-0') docs = custom_target( 'C++ API Documentation (html)', @@ -40,4 +40,4 @@ docs = custom_target( output: 'html', build_by_default: true, install: true, - install_dir: docdir / 'puglxx-0') + install_dir: docdir / 'puglpp-0') diff --git a/doc/cpp/overview.rst b/doc/cpp/overview.rst index 1928fba..faa265d 100644 --- a/doc/cpp/overview.rst +++ b/doc/cpp/overview.rst @@ -15,7 +15,7 @@ The C++ bindings are very lightweight and do not require virtual functions, RTTI, exceptions, or linking to the C++ standard library. -They are provided by the package ``puglxx-0`` which must be used in addition to the desired platform+backend package above. +They are provided by the package ``puglpp-0`` which must be used in addition to the desired platform+backend package above. The core API (excluding backend-specific components) is declared in ``pugl.hpp``: diff --git a/examples/meson.build b/examples/meson.build index a0b851c..5f2f9b4 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -2,7 +2,7 @@ data_dir = get_option('prefix') / get_option('datadir') / 'pugl-0' example_args = ['-DPUGL_DATA_DIR="@0@"'.format(data_dir)] gl_examples = [ - 'pugl_cxx_demo.cpp', + 'pugl_cpp_demo.cpp', 'pugl_embed_demo.c', 'pugl_print_events.c', 'pugl_shader_demo.c', @@ -14,14 +14,14 @@ cairo_examples = [ ] vulkan_examples = [ - 'pugl_vulkan_cxx_demo.cpp', + 'pugl_vulkan_cpp_demo.cpp', 'pugl_vulkan_demo.c', ] includes = include_directories( '.', '..', - '../bindings/cxx/include', + '../bindings/cpp/include', '../include', ) @@ -67,7 +67,7 @@ if vulkan_dep.found() target = example.split('.')[0] dependencies = [dl_dep, vulkan_backend_dep] - if target == 'pugl_vulkan_cxx_demo' + if target == 'pugl_vulkan_cpp_demo' source += ['file_utils.c'] endif diff --git a/examples/pugl_cpp_demo.cpp b/examples/pugl_cpp_demo.cpp new file mode 100644 index 0000000..e49c416 --- /dev/null +++ b/examples/pugl_cpp_demo.cpp @@ -0,0 +1,148 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "cube_view.h" +#include "demo_utils.h" +#include "test/test_utils.h" + +#include "pugl/gl.hpp" +#include "pugl/pugl.h" +#include "pugl/pugl.hpp" + +#include + +class CubeView : public pugl::View +{ +public: + explicit CubeView(pugl::World& world) + : pugl::View{world} + { + setEventHandler(*this); + } + + template + pugl::Status onEvent(const pugl::Event&) noexcept + { + return pugl::Status::success; + } + + static pugl::Status onEvent(const pugl::ConfigureEvent& event) noexcept; + pugl::Status onEvent(const pugl::UpdateEvent& event) noexcept; + pugl::Status onEvent(const pugl::ExposeEvent& event) noexcept; + pugl::Status onEvent(const pugl::KeyPressEvent& event) noexcept; + pugl::Status onEvent(const pugl::CloseEvent& event) noexcept; + + bool quit() const { return _quit; } + +private: + double _xAngle{0.0}; + double _yAngle{0.0}; + double _lastDrawTime{0.0}; + bool _quit{false}; +}; + +pugl::Status +CubeView::onEvent(const pugl::ConfigureEvent& event) noexcept +{ + reshapeCube(static_cast(event.width), + static_cast(event.height)); + + return pugl::Status::success; +} + +pugl::Status +CubeView::onEvent(const pugl::UpdateEvent&) noexcept +{ + return postRedisplay(); +} + +pugl::Status +CubeView::onEvent(const pugl::ExposeEvent&) noexcept +{ + const double thisTime = world().time(); + const double dTime = thisTime - _lastDrawTime; + const double dAngle = dTime * 100.0; + + _xAngle = fmod(_xAngle + dAngle, 360.0); + _yAngle = fmod(_yAngle + dAngle, 360.0); + displayCube(cobj(), + 8.0f, + static_cast(_xAngle), + static_cast(_yAngle), + false); + + _lastDrawTime = thisTime; + + return pugl::Status::success; +} + +pugl::Status +CubeView::onEvent(const pugl::KeyPressEvent& event) noexcept +{ + if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') { + _quit = true; + } + + return pugl::Status::success; +} + +pugl::Status +CubeView::onEvent(const pugl::CloseEvent&) noexcept +{ + _quit = true; + + return pugl::Status::success; +} + +int +main(int argc, char** argv) +{ + const PuglTestOptions opts = puglParseTestOptions(&argc, &argv); + if (opts.help) { + puglPrintTestUsage("pugl_cpp_demo", ""); + return 1; + } + + pugl::World world{pugl::WorldType::program}; + CubeView view{world}; + PuglFpsPrinter fpsPrinter{}; + + world.setClassName("PuglCppTest"); + + view.setWindowTitle("Pugl C++ Test"); + view.setDefaultSize(512, 512); + view.setMinSize(64, 64); + view.setMaxSize(256, 256); + view.setAspectRatio(1, 1, 16, 9); + view.setBackend(pugl::glBackend()); + view.setHint(pugl::ViewHint::resizable, opts.resizable); + view.setHint(pugl::ViewHint::samples, opts.samples); + view.setHint(pugl::ViewHint::doubleBuffer, opts.doubleBuffer); + view.setHint(pugl::ViewHint::swapInterval, opts.sync); + view.setHint(pugl::ViewHint::ignoreKeyRepeat, opts.ignoreKeyRepeat); + view.realize(); + view.show(); + + unsigned framesDrawn = 0; + while (!view.quit()) { + world.update(0.0); + + ++framesDrawn; + puglPrintFps(world.cobj(), &fpsPrinter, &framesDrawn); + } + + return 0; +} diff --git a/examples/pugl_cxx_demo.cpp b/examples/pugl_cxx_demo.cpp deleted file mode 100644 index d663a3f..0000000 --- a/examples/pugl_cxx_demo.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include "cube_view.h" -#include "demo_utils.h" -#include "test/test_utils.h" - -#include "pugl/gl.hpp" -#include "pugl/pugl.h" -#include "pugl/pugl.hpp" - -#include - -class CubeView : public pugl::View -{ -public: - explicit CubeView(pugl::World& world) - : pugl::View{world} - { - setEventHandler(*this); - } - - template - pugl::Status onEvent(const pugl::Event&) noexcept - { - return pugl::Status::success; - } - - static pugl::Status onEvent(const pugl::ConfigureEvent& event) noexcept; - pugl::Status onEvent(const pugl::UpdateEvent& event) noexcept; - pugl::Status onEvent(const pugl::ExposeEvent& event) noexcept; - pugl::Status onEvent(const pugl::KeyPressEvent& event) noexcept; - pugl::Status onEvent(const pugl::CloseEvent& event) noexcept; - - bool quit() const { return _quit; } - -private: - double _xAngle{0.0}; - double _yAngle{0.0}; - double _lastDrawTime{0.0}; - bool _quit{false}; -}; - -pugl::Status -CubeView::onEvent(const pugl::ConfigureEvent& event) noexcept -{ - reshapeCube(static_cast(event.width), - static_cast(event.height)); - - return pugl::Status::success; -} - -pugl::Status -CubeView::onEvent(const pugl::UpdateEvent&) noexcept -{ - return postRedisplay(); -} - -pugl::Status -CubeView::onEvent(const pugl::ExposeEvent&) noexcept -{ - const double thisTime = world().time(); - const double dTime = thisTime - _lastDrawTime; - const double dAngle = dTime * 100.0; - - _xAngle = fmod(_xAngle + dAngle, 360.0); - _yAngle = fmod(_yAngle + dAngle, 360.0); - displayCube(cobj(), - 8.0f, - static_cast(_xAngle), - static_cast(_yAngle), - false); - - _lastDrawTime = thisTime; - - return pugl::Status::success; -} - -pugl::Status -CubeView::onEvent(const pugl::KeyPressEvent& event) noexcept -{ - if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') { - _quit = true; - } - - return pugl::Status::success; -} - -pugl::Status -CubeView::onEvent(const pugl::CloseEvent&) noexcept -{ - _quit = true; - - return pugl::Status::success; -} - -int -main(int argc, char** argv) -{ - const PuglTestOptions opts = puglParseTestOptions(&argc, &argv); - if (opts.help) { - puglPrintTestUsage("pugl_cxx_demo", ""); - return 1; - } - - pugl::World world{pugl::WorldType::program}; - CubeView view{world}; - PuglFpsPrinter fpsPrinter{}; - - world.setClassName("PuglCppTest"); - - view.setWindowTitle("Pugl C++ Test"); - view.setDefaultSize(512, 512); - view.setMinSize(64, 64); - view.setMaxSize(256, 256); - view.setAspectRatio(1, 1, 16, 9); - view.setBackend(pugl::glBackend()); - view.setHint(pugl::ViewHint::resizable, opts.resizable); - view.setHint(pugl::ViewHint::samples, opts.samples); - view.setHint(pugl::ViewHint::doubleBuffer, opts.doubleBuffer); - view.setHint(pugl::ViewHint::swapInterval, opts.sync); - view.setHint(pugl::ViewHint::ignoreKeyRepeat, opts.ignoreKeyRepeat); - view.realize(); - view.show(); - - unsigned framesDrawn = 0; - while (!view.quit()) { - world.update(0.0); - - ++framesDrawn; - puglPrintFps(world.cobj(), &fpsPrinter, &framesDrawn); - } - - return 0; -} diff --git a/examples/pugl_vulkan_cpp_demo.cpp b/examples/pugl_vulkan_cpp_demo.cpp new file mode 100644 index 0000000..d92e652 --- /dev/null +++ b/examples/pugl_vulkan_cpp_demo.cpp @@ -0,0 +1,1826 @@ +/* + Copyright 2019-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* + An example of drawing with Vulkan. + + This is an example of using Vulkan for pixel-perfect 2D drawing. It uses + the same data and shaders as pugl_shader_demo.c and attempts to draw the + same thing, except using Vulkan. + + Since Vulkan is a complicated and very verbose API, this example is + unfortunately much larger than the others. You should not use this as a + resource to learn Vulkan, but it provides a decent demo of using Vulkan with + Pugl that works nicely on all supported platforms. +*/ + +#include "demo_utils.h" +#include "file_utils.h" +#include "rects.h" +#include "test/test_utils.h" + +#include "sybok.hpp" + +#include "pugl/pugl.h" +#include "pugl/pugl.hpp" +#include "pugl/vulkan.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr uintptr_t resizeTimerId = 1u; + +struct PhysicalDeviceSelection { + sk::PhysicalDevice physicalDevice; + uint32_t graphicsFamilyIndex; +}; + +/// Basic Vulkan context associated with the window +struct VulkanContext { + VkResult init(pugl::VulkanLoader& loader, const PuglTestOptions& opts); + + sk::VulkanApi vk; + sk::Instance instance; + sk::DebugReportCallbackEXT debugCallback; +}; + +/// Basic setup of graphics device +struct GraphicsDevice { + VkResult init(const pugl::VulkanLoader& loader, + const VulkanContext& context, + pugl::View& view, + const PuglTestOptions& opts); + + sk::SurfaceKHR surface; + sk::PhysicalDevice physicalDevice{}; + uint32_t graphicsIndex{}; + VkSurfaceFormatKHR surfaceFormat{}; + VkPresentModeKHR presentMode{}; + VkPresentModeKHR resizePresentMode{}; + sk::Device device{}; + sk::Queue graphicsQueue{}; + sk::CommandPool commandPool{}; +}; + +/// Buffer allocated on the GPU +struct Buffer { + VkResult init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + VkDeviceSize size, + VkBufferUsageFlags usage, + VkMemoryPropertyFlags properties); + + sk::Buffer buffer; + sk::DeviceMemory deviceMemory; +}; + +/// A set of frames that can be rendered concurrently +struct Swapchain { + VkResult init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + VkSurfaceCapabilitiesKHR capabilities, + VkExtent2D extent, + VkSwapchainKHR oldSwapchain, + bool resizing); + + VkSurfaceCapabilitiesKHR capabilities{}; + VkExtent2D extent{}; + sk::SwapchainKHR swapchain{}; + std::vector imageViews{}; +}; + +/// A pass that renders to a target +struct RenderPass { + VkResult init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const Swapchain& swapchain); + + sk::RenderPass renderPass; + std::vector framebuffers; + sk::CommandBuffers> commandBuffers; +}; + +/// Uniform buffer for constant data used in shaders +struct UniformBufferObject { + mat4 projection; +}; + +/// Rectangle data that does not depend on renderer configuration +struct RectData { + VkResult init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + size_t nRects); + + sk::DescriptorSetLayout descriptorSetLayout{}; + Buffer uniformBuffer{}; + sk::MappedMemory uniformData{}; + Buffer modelBuffer{}; + Buffer instanceBuffer{}; + sk::MappedMemory vertexData{}; + size_t numRects{}; +}; + +/// Shader modules for drawing rectangles +struct RectShaders { + VkResult init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const std::string& programPath); + + sk::ShaderModule vert{}; + sk::ShaderModule frag{}; +}; + +/// A pipeline to render rectangles with our shaders +struct RectPipeline { + VkResult init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const RectData& rectData, + const RectShaders& shaders, + const Swapchain& swapchain, + const RenderPass& renderPass); + + sk::DescriptorPool descriptorPool{}; + sk::DescriptorSets> descriptorSets{}; + sk::PipelineLayout pipelineLayout{}; + std::array pipelines{}; + uint32_t numImages{}; +}; + +/// Synchronization primitives used to coordinate drawing frames +struct RenderSync { + VkResult init(const sk::VulkanApi& vk, + const sk::Device& device, + uint32_t numImages); + + std::vector imageAvailable{}; + std::vector renderFinished{}; + std::vector inFlight{}; + size_t currentFrame{}; +}; + +/// Renderer that owns the above and everything required to draw +struct Renderer { + VkResult init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const RectData& rectData, + const RectShaders& rectShaders, + VkExtent2D extent, + bool resizing); + + VkResult recreate(const sk::VulkanApi& vk, + const sk::SurfaceKHR& surface, + const GraphicsDevice& gpu, + const RectData& rectData, + const RectShaders& rectShaders, + VkExtent2D extent, + bool resizing); + + Swapchain swapchain; + RenderPass renderPass; + RectPipeline rectPipeline; + RenderSync sync; +}; + +VkResult +selectSurfaceFormat(const sk::VulkanApi& vk, + const sk::PhysicalDevice& physicalDevice, + const sk::SurfaceKHR& surface, + VkSurfaceFormatKHR& surfaceFormat) +{ + std::vector formats; + if (VkResult r = vk.getPhysicalDeviceSurfaceFormatsKHR( + physicalDevice, surface, formats)) { + return r; + } + + for (const auto& format : formats) { + if (format.format == VK_FORMAT_B8G8R8A8_UNORM && + format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + surfaceFormat = format; + return VK_SUCCESS; + } + } + + return VK_ERROR_FORMAT_NOT_SUPPORTED; +} + +VkResult +selectPresentMode(const sk::VulkanApi& vk, + const sk::PhysicalDevice& physicalDevice, + const sk::SurfaceKHR& surface, + const bool multiBuffer, + const bool sync, + VkPresentModeKHR& presentMode) +{ + // Map command line options to mode priorities + static constexpr VkPresentModeKHR priorities[][2][4] = { + { + // No double buffer, no sync + {VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_RELAXED_KHR, + VK_PRESENT_MODE_FIFO_KHR}, + + // No double buffer, sync (nonsense, map to FIFO relaxed) + {VK_PRESENT_MODE_FIFO_RELAXED_KHR, + VK_PRESENT_MODE_FIFO_KHR, + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_IMMEDIATE_KHR}, + }, + { + // Double buffer, no sync + { + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_FIFO_RELAXED_KHR, + VK_PRESENT_MODE_FIFO_KHR, + }, + + // Double buffer, sync + {VK_PRESENT_MODE_FIFO_KHR, + VK_PRESENT_MODE_FIFO_RELAXED_KHR, + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_IMMEDIATE_KHR}, + }, + }; + + std::vector modes; + if (VkResult r = vk.getPhysicalDeviceSurfacePresentModesKHR( + physicalDevice, surface, modes)) { + return r; + } + + const auto& tryModes = priorities[bool(multiBuffer)][bool(sync)]; + for (const auto m : tryModes) { + if (std::find(modes.begin(), modes.end(), m) != modes.end()) { + presentMode = m; + return VK_SUCCESS; + } + } + + return VK_ERROR_INCOMPATIBLE_DRIVER; +} + +VkResult +openDevice(const sk::VulkanApi& vk, + const sk::PhysicalDevice& physicalDevice, + const uint32_t graphicsFamilyIndex, + sk::Device& device) +{ + const float graphicsQueuePriority = 1.0f; + const char* const swapchainName = "VK_KHR_swapchain"; + + const VkDeviceQueueCreateInfo queueCreateInfo{ + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + nullptr, + 0u, + graphicsFamilyIndex, + SK_COUNTED(1u, &graphicsQueuePriority), + }; + + const VkDeviceCreateInfo createInfo{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + nullptr, + 0u, + SK_COUNTED(1u, &queueCreateInfo), + SK_COUNTED(0u, nullptr), // Deprecated + SK_COUNTED(1u, &swapchainName), + nullptr}; + + return vk.createDevice(physicalDevice, createInfo, device); +} + +/// Return whether the physical device supports the extensions we require +VkResult +deviceSupportsRequiredExtensions(const sk::VulkanApi& vk, + const sk::PhysicalDevice& device, + bool& supported) +{ + VkResult r = VK_SUCCESS; + + std::vector props; + if ((r = vk.enumerateDeviceExtensionProperties(device, props))) { + return r; + } + + supported = std::any_of( + props.begin(), props.end(), [&](const VkExtensionProperties& e) { + return !strcmp(e.extensionName, "VK_KHR_swapchain"); + }); + + return VK_SUCCESS; +} + +/// Return the index of the graphics queue, if there is one +VkResult +findGraphicsQueue(const sk::VulkanApi& vk, + const sk::SurfaceKHR& surface, + const sk::PhysicalDevice& device, + uint32_t& queueIndex) +{ + VkResult r = VK_SUCCESS; + + std::vector queueProps; + if ((r = vk.getPhysicalDeviceQueueFamilyProperties(device, queueProps))) { + return r; + } + + for (uint32_t q = 0u; q < queueProps.size(); ++q) { + if (queueProps[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + bool supported = false; + if ((r = vk.getPhysicalDeviceSurfaceSupportKHR( + device, q, surface, supported))) { + return r; + } + + if (supported) { + queueIndex = q; + return VK_SUCCESS; + } + } + } + + return VK_ERROR_FEATURE_NOT_PRESENT; +} + +/// Select a physical graphics device to use (simply the first found) +VkResult +selectPhysicalDevice(const sk::VulkanApi& vk, + const sk::Instance& instance, + const sk::SurfaceKHR& surface, + PhysicalDeviceSelection& selection) +{ + VkResult r = VK_SUCCESS; + + std::vector devices; + if ((r = vk.enumeratePhysicalDevices(instance, devices))) { + return r; + } + + for (const auto& device : devices) { + auto supported = false; + if ((r = deviceSupportsRequiredExtensions(vk, device, supported))) { + return r; + } + + if (supported) { + auto queueIndex = 0u; + if ((r = findGraphicsQueue(vk, surface, device, queueIndex))) { + return r; + } + + selection = PhysicalDeviceSelection{device, queueIndex}; + return VK_SUCCESS; + } + } + + return VK_ERROR_INCOMPATIBLE_DISPLAY_KHR; +} + +VkResult +GraphicsDevice::init(const pugl::VulkanLoader& loader, + const VulkanContext& context, + pugl::View& view, + const PuglTestOptions& opts) +{ + const auto& vk = context.vk; + VkResult r = VK_SUCCESS; + + // Create a Vulkan surface for the window using the Pugl API + VkSurfaceKHR surfaceHandle = {}; + if ((r = pugl::createSurface(loader.getInstanceProcAddrFunc(), + view, + context.instance, + nullptr, + &surfaceHandle))) { + return r; + } + + // Wrap surface in a safe RAII handle + surface = + sk::SurfaceKHR{surfaceHandle, {context.instance, vk.vkDestroySurfaceKHR}}; + + PhysicalDeviceSelection physicalDeviceSelection = {}; + // Select a physical device to use + if ((r = selectPhysicalDevice( + vk, context.instance, surface, physicalDeviceSelection))) { + return r; + } + + physicalDevice = physicalDeviceSelection.physicalDevice; + graphicsIndex = physicalDeviceSelection.graphicsFamilyIndex; + + if ((r = selectSurfaceFormat(vk, physicalDevice, surface, surfaceFormat)) || + (r = selectPresentMode(vk, + physicalDevice, + surface, + opts.doubleBuffer, + opts.sync, + presentMode)) || + (r = selectPresentMode( + vk, physicalDevice, surface, true, false, resizePresentMode)) || + (r = openDevice(vk, physicalDevice, graphicsIndex, device))) { + return r; + } + + const VkCommandPoolCreateInfo commandPoolInfo{ + VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, {}, graphicsIndex}; + + if ((r = vk.createCommandPool(device, commandPoolInfo, commandPool))) { + return r; + } + + graphicsQueue = vk.getDeviceQueue(device, graphicsIndex, 0); + return VK_SUCCESS; +} + +uint32_t +findMemoryType(const sk::VulkanApi& vk, + const sk::PhysicalDevice& physicalDevice, + const uint32_t typeFilter, + const VkMemoryPropertyFlags& properties) +{ + VkPhysicalDeviceMemoryProperties memProperties = + vk.getPhysicalDeviceMemoryProperties(physicalDevice); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & + properties) == properties) { + return i; + } + } + + return UINT32_MAX; +} + +VkResult +Buffer::init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const VkDeviceSize size, + const VkBufferUsageFlags usage, + const VkMemoryPropertyFlags properties) +{ + const VkBufferCreateInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + nullptr, + {}, + size, + usage, + VK_SHARING_MODE_EXCLUSIVE, + SK_COUNTED(0, nullptr)}; + + const auto& device = gpu.device; + + VkResult r = VK_SUCCESS; + if ((r = vk.createBuffer(device, bufferInfo, buffer))) { + return r; + } + + const auto requirements = vk.getBufferMemoryRequirements(device, buffer); + const auto memoryTypeIndex = findMemoryType( + vk, gpu.physicalDevice, requirements.memoryTypeBits, properties); + + if (memoryTypeIndex == UINT32_MAX) { + return VK_ERROR_FEATURE_NOT_PRESENT; + } + + const VkMemoryAllocateInfo allocInfo{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + nullptr, + requirements.size, + memoryTypeIndex}; + + if ((r = vk.allocateMemory(device, allocInfo, deviceMemory)) || + (r = vk.bindBufferMemory(device, buffer, deviceMemory, 0))) { + return r; + } + + return VK_SUCCESS; +} + +VkResult +Swapchain::init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const VkSurfaceCapabilitiesKHR surfaceCapabilities, + const VkExtent2D surfaceExtent, + VkSwapchainKHR oldSwapchain, + bool resizing) +{ + capabilities = surfaceCapabilities; + extent = surfaceExtent; + + const auto minNumImages = + (!capabilities.maxImageCount || capabilities.maxImageCount >= 3u) + ? 3u + : capabilities.maxImageCount; + + const VkSwapchainCreateInfoKHR swapchainCreateInfo{ + VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + nullptr, + {}, + gpu.surface, + minNumImages, + gpu.surfaceFormat.format, + gpu.surfaceFormat.colorSpace, + surfaceExtent, + 1, + (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT), + VK_SHARING_MODE_EXCLUSIVE, + SK_COUNTED(0, nullptr), + capabilities.currentTransform, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, + resizing ? gpu.resizePresentMode : gpu.presentMode, + VK_TRUE, + oldSwapchain}; + + VkResult r = VK_SUCCESS; + std::vector images; + if ((r = vk.createSwapchainKHR(gpu.device, swapchainCreateInfo, swapchain)) || + (r = vk.getSwapchainImagesKHR(gpu.device, swapchain, images))) { + return r; + } + + imageViews = std::vector(images.size()); + for (size_t i = 0; i < images.size(); ++i) { + const VkImageViewCreateInfo imageViewCreateInfo{ + VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + nullptr, + {}, + images[i], + VK_IMAGE_VIEW_TYPE_2D, + gpu.surfaceFormat.format, + {}, + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + if ((r = vk.createImageView( + gpu.device, imageViewCreateInfo, imageViews[i]))) { + return r; + } + } + + return VK_SUCCESS; +} + +VkResult +RenderPass::init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const Swapchain& swapchain) +{ + const auto numImages = static_cast(swapchain.imageViews.size()); + + assert(numImages > 0); + + // Create command buffers + const VkCommandBufferAllocateInfo commandBufferAllocateInfo{ + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + nullptr, + gpu.commandPool, + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + numImages}; + + VkResult r = VK_SUCCESS; + if ((r = vk.allocateCommandBuffers( + gpu.device, commandBufferAllocateInfo, commandBuffers))) { + return r; + } + + static constexpr VkAttachmentReference colorAttachmentRef{ + 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + + static constexpr VkSubpassDescription subpass{ + {}, + VK_PIPELINE_BIND_POINT_GRAPHICS, + SK_COUNTED(0, nullptr), + SK_COUNTED(1, &colorAttachmentRef, nullptr, nullptr), + SK_COUNTED(0u, nullptr)}; + + static constexpr VkSubpassDependency dependency{ + VK_SUBPASS_EXTERNAL, + 0, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + (VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT), + {}, + {}}; + + const VkAttachmentDescription colorAttachment{ + {}, + gpu.surfaceFormat.format, + VK_SAMPLE_COUNT_1_BIT, + VK_ATTACHMENT_LOAD_OP_CLEAR, + VK_ATTACHMENT_STORE_OP_STORE, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_DONT_CARE, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }; + + const VkRenderPassCreateInfo renderPassCreateInfo{ + VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + nullptr, + {}, + SK_COUNTED(1, &colorAttachment), + SK_COUNTED(1, &subpass), + SK_COUNTED(1, &dependency)}; + + if ((r = vk.createRenderPass(gpu.device, renderPassCreateInfo, renderPass))) { + return r; + } + + // Create framebuffers + framebuffers = std::vector(numImages); + for (uint32_t i = 0; i < numImages; ++i) { + const VkFramebufferCreateInfo framebufferCreateInfo{ + VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + nullptr, + {}, + renderPass, + SK_COUNTED(1, &swapchain.imageViews[i].get()), + swapchain.extent.width, + swapchain.extent.height, + 1}; + + if ((r = vk.createFramebuffer( + gpu.device, framebufferCreateInfo, framebuffers[i]))) { + return r; + } + } + + return VK_SUCCESS; +} + +std::vector +readFile(const char* const programPath, const std::string& filename) +{ + std::unique_ptr path{ + resourcePath(programPath, filename.c_str()), &free}; + + std::cerr << "Loading shader: " << path.get() << std::endl; + + std::unique_ptr file{fopen(path.get(), "rb"), + &fclose}; + + if (!file) { + std::cerr << "Failed to open file '" << filename << "'\n"; + return {}; + } + + fseek(file.get(), 0, SEEK_END); + const auto fileSize = static_cast(ftell(file.get())); + fseek(file.get(), 0, SEEK_SET); + + const auto numWords = fileSize / sizeof(uint32_t); + std::vector buffer(numWords); + + fread(buffer.data(), sizeof(uint32_t), numWords, file.get()); + + return buffer; +} + +VkResult +createShaderModule(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const std::vector& code, + sk::ShaderModule& shaderModule) +{ + const VkShaderModuleCreateInfo createInfo{ + VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + nullptr, + {}, + code.size() * sizeof(uint32_t), + code.data()}; + + return vk.createShaderModule(gpu.device, createInfo, shaderModule); +} + +VkResult +RectShaders::init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const std::string& programPath) +{ + auto vertShaderCode = readFile(programPath.c_str(), "shaders/rect.vert.spv"); + + auto fragShaderCode = readFile(programPath.c_str(), "shaders/rect.frag.spv"); + + if (vertShaderCode.empty() || fragShaderCode.empty()) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkResult r = VK_SUCCESS; + if ((r = createShaderModule(vk, gpu, vertShaderCode, vert)) || + (r = createShaderModule(vk, gpu, fragShaderCode, frag))) { + return r; + } + + return VK_SUCCESS; +} + +VkResult +RectPipeline::init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const RectData& rectData, + const RectShaders& shaders, + const Swapchain& swapchain, + const RenderPass& renderPass) +{ + const auto oldNumImages = numImages; + VkResult r = VK_SUCCESS; + + numImages = static_cast(swapchain.imageViews.size()); + pipelines = {}; + pipelineLayout = {}; + descriptorSets = {}; + + if (numImages != oldNumImages) { + // Create layout descriptor pool + + const VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + numImages}; + + const VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{ + VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + nullptr, + VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + numImages, + 1u, + &poolSize}; + if ((r = vk.createDescriptorPool( + gpu.device, descriptorPoolCreateInfo, descriptorPool))) { + return r; + } + } + + const std::vector layouts( + numImages, rectData.descriptorSetLayout.get()); + + const VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{ + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + nullptr, + descriptorPool, + numImages, + layouts.data()}; + if ((r = vk.allocateDescriptorSets( + gpu.device, descriptorSetAllocateInfo, descriptorSets))) { + return r; + } + + const VkDescriptorBufferInfo bufferInfo{ + rectData.uniformBuffer.buffer, 0, sizeof(UniformBufferObject)}; + + const std::array descriptorWrites{ + {{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + nullptr, + descriptorSets[0], + 0, + 0, + 1, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + nullptr, + &bufferInfo, + nullptr}}}; + + const std::array descriptorCopies{}; + + vk.updateDescriptorSets(gpu.device, descriptorWrites, descriptorCopies); + + static constexpr std::array + vertexAttributeDescriptions{ + {// Model + {0u, 0u, VK_FORMAT_R32G32_SFLOAT, 0}, + + // Rect instance attributes + {1u, 1u, VK_FORMAT_R32G32_SFLOAT, offsetof(Rect, pos)}, + {2u, 1u, VK_FORMAT_R32G32_SFLOAT, offsetof(Rect, size)}, + {3u, 1u, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Rect, fillColor)}}}; + + static constexpr std::array + vertexBindingDescriptions{ + VkVertexInputBindingDescription{ + 0, sizeof(vec2), VK_VERTEX_INPUT_RATE_VERTEX}, + VkVertexInputBindingDescription{ + 1u, sizeof(Rect), VK_VERTEX_INPUT_RATE_INSTANCE}}; + + static constexpr VkPipelineInputAssemblyStateCreateInfo inputAssembly{ + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + nullptr, + {}, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, + false}; + + static constexpr VkPipelineRasterizationStateCreateInfo rasterizer{ + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + nullptr, + {}, + 0, + 0, + VK_POLYGON_MODE_FILL, + VK_CULL_MODE_BACK_BIT, + VK_FRONT_FACE_CLOCKWISE, + 0, + 0, + 0, + 0, + 1.0f}; + + static constexpr VkPipelineMultisampleStateCreateInfo multisampling{ + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + nullptr, + {}, + VK_SAMPLE_COUNT_1_BIT, + false, + 0.0f, + nullptr, + false, + false}; + + static constexpr VkPipelineColorBlendAttachmentState colorBlendAttachment{ + true, + VK_BLEND_FACTOR_SRC_ALPHA, + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + VK_BLEND_OP_ADD, + VK_BLEND_FACTOR_ONE, + VK_BLEND_FACTOR_ZERO, + VK_BLEND_OP_ADD, + (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT)}; + + const VkPipelineShaderStageCreateInfo shaderStages[] = { + {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + {}, + VK_SHADER_STAGE_VERTEX_BIT, + shaders.vert.get(), + "main", + nullptr}, + {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + {}, + VK_SHADER_STAGE_FRAGMENT_BIT, + shaders.frag.get(), + "main", + nullptr}}; + + const VkPipelineVertexInputStateCreateInfo vertexInputInfo{ + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + nullptr, + {}, + SK_COUNTED(static_cast(vertexBindingDescriptions.size()), + vertexBindingDescriptions.data()), + SK_COUNTED(static_cast(vertexAttributeDescriptions.size()), + vertexAttributeDescriptions.data())}; + + const VkViewport viewport{0.0f, + 0.0f, + float(swapchain.extent.width), + float(swapchain.extent.height), + 0.0f, + 1.0f}; + + const VkRect2D scissor{{0, 0}, swapchain.extent}; + + const VkPipelineViewportStateCreateInfo viewportState{ + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + nullptr, + {}, + SK_COUNTED(1, &viewport), + SK_COUNTED(1, &scissor)}; + + const VkPipelineColorBlendStateCreateInfo colorBlending{ + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + nullptr, + {}, + false, + VK_LOGIC_OP_COPY, + SK_COUNTED(1, &colorBlendAttachment), + {1.0f, 0.0f, 0.0f, 0.0f}}; + + const VkPipelineLayoutCreateInfo layoutInfo{ + VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + nullptr, + {}, + SK_COUNTED(1, &rectData.descriptorSetLayout.get()), + SK_COUNTED(0, nullptr)}; + + if ((r = vk.createPipelineLayout(gpu.device, layoutInfo, pipelineLayout))) { + return r; + } + + const std::array pipelineInfos{ + {{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + nullptr, + {}, + SK_COUNTED(2, shaderStages), + &vertexInputInfo, + &inputAssembly, + nullptr, + &viewportState, + &rasterizer, + &multisampling, + nullptr, + &colorBlending, + nullptr, + pipelineLayout, + renderPass.renderPass, + 0u, + {}, + 0}}}; + + if ((r = vk.createGraphicsPipelines( + gpu.device, {}, pipelineInfos, pipelines))) { + return r; + } + + return VK_SUCCESS; +} + +VkResult +RectData::init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const size_t nRects) +{ + numRects = nRects; + + static constexpr VkDescriptorSetLayoutBinding uboLayoutBinding{ + 0, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 1, + VK_SHADER_STAGE_VERTEX_BIT, + nullptr}; + + const VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo{ + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + nullptr, + {}, + 1, + &uboLayoutBinding}; + + VkResult r = VK_SUCCESS; + if ((r = vk.createDescriptorSetLayout( + gpu.device, descriptorSetLayoutInfo, descriptorSetLayout)) || + (r = uniformBuffer.init(vk, + gpu, + sizeof(UniformBufferObject), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) || + (r = vk.mapMemory(gpu.device, + uniformBuffer.deviceMemory, + 0, + sizeof(UniformBufferObject), + {}, + uniformData))) { + return r; + } + + const VkBufferUsageFlags usageFlags = + (VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT); + + const VkMemoryPropertyFlags propertyFlags = + (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + if ((r = modelBuffer.init( + vk, gpu, sizeof(rectVertices), usageFlags, propertyFlags))) { + return r; + } + + { + // Copy model vertices (directly, we do this only once) + sk::MappedMemory modelData; + if ((r = vk.mapMemory(gpu.device, + modelBuffer.deviceMemory, + 0, + static_cast(sizeof(rectVertices)), + {}, + modelData))) { + return r; + } + + memcpy(modelData.get(), rectVertices, sizeof(rectVertices)); + } + + if ((r = instanceBuffer.init( + vk, gpu, sizeof(Rect) * numRects, usageFlags, propertyFlags))) { + return r; + } + + // Map attribute vertices (we will update them every frame) + const auto rectsSize = static_cast(sizeof(Rect) * numRects); + if ((r = vk.mapMemory(gpu.device, + instanceBuffer.deviceMemory, + 0, + rectsSize, + {}, + vertexData))) { + return r; + } + + return VK_SUCCESS; +} + +VkResult +RenderSync::init(const sk::VulkanApi& vk, + const sk::Device& device, + const uint32_t numImages) +{ + const auto maxInFlight = std::max(1u, numImages - 1u); + VkResult r = VK_SUCCESS; + + imageAvailable = std::vector(numImages); + renderFinished = std::vector(numImages); + for (uint32_t i = 0; i < numImages; ++i) { + static constexpr VkSemaphoreCreateInfo semaphoreInfo{ + VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, {}}; + + if ((r = vk.createSemaphore(device, semaphoreInfo, imageAvailable[i])) || + (r = vk.createSemaphore(device, semaphoreInfo, renderFinished[i]))) { + return r; + } + } + + inFlight = std::vector(maxInFlight); + for (uint32_t i = 0; i < maxInFlight; ++i) { + static constexpr VkFenceCreateInfo fenceInfo{ + VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + nullptr, + VK_FENCE_CREATE_SIGNALED_BIT}; + + if ((r = vk.createFence(device, fenceInfo, inFlight[i]))) { + return r; + } + } + + return VK_SUCCESS; +} + +VkResult +Renderer::init(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const RectData& rectData, + const RectShaders& rectShaders, + const VkExtent2D extent, + bool resizing) +{ + VkResult r = VK_SUCCESS; + VkSurfaceCapabilitiesKHR capabilities = {}; + + if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR( + gpu.physicalDevice, gpu.surface, capabilities)) || + (r = swapchain.init(vk, gpu, capabilities, extent, {}, resizing)) || + (r = renderPass.init(vk, gpu, swapchain)) || + (r = rectPipeline.init( + vk, gpu, rectData, rectShaders, swapchain, renderPass))) { + return r; + } + + const auto numFrames = static_cast(swapchain.imageViews.size()); + return sync.init(vk, gpu.device, numFrames); +} + +VkResult +Renderer::recreate(const sk::VulkanApi& vk, + const sk::SurfaceKHR& surface, + const GraphicsDevice& gpu, + const RectData& rectData, + const RectShaders& rectShaders, + const VkExtent2D extent, + bool resizing) +{ + VkResult r = VK_SUCCESS; + const auto oldNumImages = swapchain.imageViews.size(); + + VkSurfaceCapabilitiesKHR capabilities = {}; + if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR( + gpu.physicalDevice, surface, capabilities)) || + (r = swapchain.init( + vk, gpu, capabilities, extent, swapchain.swapchain, resizing)) || + (r = renderPass.init(vk, gpu, swapchain)) || + (r = rectPipeline.init( + vk, gpu, rectData, rectShaders, swapchain, renderPass))) { + return r; + } + + const auto numFrames = static_cast(swapchain.imageViews.size()); + if (swapchain.imageViews.size() != oldNumImages) { + return sync.init(vk, gpu.device, numFrames); + } + + return VK_SUCCESS; +} + +VKAPI_ATTR +VkBool32 VKAPI_CALL +debugCallback(VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT, + uint64_t, + size_t, + int32_t, + const char* layerPrefix, + const char* msg, + void*) +{ + std::cerr << sk::string(static_cast(flags)) << ": " + << layerPrefix << ": " << msg << std::endl; + + return VK_FALSE; +} + +bool +hasExtension(const char* name, + const std::vector& properties) +{ + for (const auto& p : properties) { + if (!strcmp(p.extensionName, name)) { + return true; + } + } + + return false; +} + +bool +hasLayer(const char* name, const std::vector& properties) +{ + for (const auto& p : properties) { + if (!strcmp(p.layerName, name)) { + return true; + } + } + + return false; +} + +template +void +logInfo(const char* heading, const Value& value) +{ + std::cout << std::setw(26) << std::left << (std::string(heading) + ":") + << value << std::endl; +} + +VkResult +createInstance(sk::VulkanInitApi& initApi, + const PuglTestOptions& opts, + sk::Instance& instance) +{ + VkResult r = VK_SUCCESS; + + std::vector layerProps; + std::vector extProps; + if ((r = initApi.enumerateInstanceLayerProperties(layerProps)) || + (r = initApi.enumerateInstanceExtensionProperties(extProps))) { + return r; + } + + const auto puglExtensions = pugl::getInstanceExtensions(); + auto extensions = + std::vector(puglExtensions.begin(), puglExtensions.end()); + + // Add extra extensions we want to use if they are supported + if (hasExtension("VK_EXT_debug_report", extProps)) { + extensions.push_back("VK_EXT_debug_report"); + } + + // Add validation layers if error checking is enabled + std::vector layers; + if (opts.errorChecking) { + for (const char* l : {"VK_LAYER_KHRONOS_validation", + "VK_LAYER_LUNARG_standard_validation"}) { + if (hasLayer(l, layerProps)) { + layers.push_back(l); + } + } + } + + for (const auto& e : extensions) { + logInfo("Using instance extension", e); + } + + for (const auto& l : layers) { + logInfo("Using instance layer", l); + } + + static constexpr VkApplicationInfo appInfo{ + VK_STRUCTURE_TYPE_APPLICATION_INFO, + nullptr, + "Pugl Vulkan Demo", + 0, + nullptr, + 0, + VK_MAKE_VERSION(1, 0, 0), + }; + + const VkInstanceCreateInfo createInfo{ + VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + nullptr, + VkInstanceCreateFlags{}, + &appInfo, + SK_COUNTED(uint32_t(layers.size()), layers.data()), + SK_COUNTED(uint32_t(extensions.size()), extensions.data())}; + + return initApi.createInstance(createInfo, instance); +} + +VkResult +getDebugReportCallback(sk::VulkanApi& api, + sk::Instance& instance, + const bool verbose, + sk::DebugReportCallbackEXT& callback) +{ + if (api.vkCreateDebugReportCallbackEXT) { + VkDebugReportFlagsEXT flags = (VK_DEBUG_REPORT_WARNING_BIT_EXT | + VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | + VK_DEBUG_REPORT_ERROR_BIT_EXT); + + if (verbose) { + flags |= VK_DEBUG_REPORT_INFORMATION_BIT_EXT; + flags |= VK_DEBUG_REPORT_DEBUG_BIT_EXT; + } + + const VkDebugReportCallbackCreateInfoEXT createInfo{ + VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT, + nullptr, + flags, + debugCallback, + nullptr}; + + return api.createDebugReportCallbackEXT(instance, createInfo, callback); + } + + return VK_ERROR_FEATURE_NOT_PRESENT; +} + +void +recordCommandBuffer(sk::CommandScope& cmd, + const Swapchain& swapchain, + const RenderPass& renderPass, + const RectPipeline& rectPipeline, + const RectData& rectData, + const size_t imageIndex) +{ + const VkClearColorValue clearColorValue{{0.0f, 0.0f, 0.0f, 1.0f}}; + const VkClearValue clearValue{clearColorValue}; + + const VkRenderPassBeginInfo renderPassBegin{ + VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + nullptr, + renderPass.renderPass, + renderPass.framebuffers[imageIndex], + VkRect2D{{0, 0}, swapchain.extent}, + SK_COUNTED(1, &clearValue)}; + + auto pass = cmd.beginRenderPass(renderPassBegin, VK_SUBPASS_CONTENTS_INLINE); + + pass.bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, rectPipeline.pipelines[0]); + + const std::array offsets{0}; + pass.bindVertexBuffers( + 0u, SK_COUNTED(1u, &rectData.modelBuffer.buffer.get(), offsets.data())); + + pass.bindVertexBuffers( + 1u, SK_COUNTED(1u, &rectData.instanceBuffer.buffer.get(), offsets.data())); + + pass.bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, + rectPipeline.pipelineLayout, + 0u, + SK_COUNTED(1u, rectPipeline.descriptorSets.get()), + 0u, + nullptr); + + pass.draw(4u, static_cast(rectData.numRects), 0u, 0u); +} + +VkResult +recordCommandBuffers(const sk::VulkanApi& vk, + const Swapchain& swapchain, + const RenderPass& renderPass, + const RectPipeline& rectPipeline, + const RectData& rectData) +{ + VkResult r = VK_SUCCESS; + + for (size_t i = 0; i < swapchain.imageViews.size(); ++i) { + const VkCommandBufferBeginInfo beginInfo{ + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + nullptr, + VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + nullptr}; + + auto* const commandBuffer = renderPass.commandBuffers[i]; + auto cmd = vk.beginCommandBuffer(commandBuffer, beginInfo); + if (!cmd) { + return cmd.error(); + } + + recordCommandBuffer(cmd, swapchain, renderPass, rectPipeline, rectData, i); + + if ((r = cmd.end())) { + return r; + } + } + + return VK_SUCCESS; +} + +class PuglVulkanDemo; + +class View : public pugl::View +{ +public: + View(pugl::World& world, PuglVulkanDemo& app) + : pugl::View{world} + , _app{app} + { + setEventHandler(*this); + } + + template + pugl::Status onEvent(const pugl::Event&) noexcept + { + return pugl::Status::success; + } + + pugl::Status onEvent(const pugl::ConfigureEvent& event); + pugl::Status onEvent(const pugl::UpdateEvent& event); + pugl::Status onEvent(const pugl::ExposeEvent& event); + pugl::Status onEvent(const pugl::LoopEnterEvent& event); + pugl::Status onEvent(const pugl::TimerEvent& event); + pugl::Status onEvent(const pugl::LoopLeaveEvent& event); + pugl::Status onEvent(const pugl::KeyPressEvent& event); + pugl::Status onEvent(const pugl::CloseEvent& event); + +private: + PuglVulkanDemo& _app; +}; + +class PuglVulkanDemo +{ +public: + PuglVulkanDemo(const char* executablePath, + const PuglTestOptions& o, + size_t numRects); + + const char* programPath; + PuglTestOptions opts; + pugl::World world; + pugl::VulkanLoader loader; + View view; + VulkanContext vulkan; + GraphicsDevice gpu; + Renderer renderer; + RectData rectData; + RectShaders rectShaders; + uint32_t framesDrawn{0}; + VkExtent2D extent{512u, 512u}; + std::vector rects; + bool resizing{false}; + bool quit{false}; +}; + +std::vector +makeRects(const size_t numRects, const uint32_t windowWidth) +{ + std::vector rects(numRects); + for (size_t i = 0; i < numRects; ++i) { + rects[i] = makeRect(i, static_cast(windowWidth)); + } + + return rects; +} + +PuglVulkanDemo::PuglVulkanDemo(const char* const executablePath, + const PuglTestOptions& o, + const size_t numRects) + : programPath{executablePath} + , opts{o} + , world{pugl::WorldType::program, pugl::WorldFlag::threads} + , loader{world} + , view{world, *this} + , rects{makeRects(numRects, extent.width)} +{} + +VkResult +recreateRenderer(PuglVulkanDemo& app, + const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const VkExtent2D extent, + const RectData& rectData, + const RectShaders& rectShaders) +{ + VkResult r = VK_SUCCESS; + VkSurfaceCapabilitiesKHR capabilities = {}; + if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR( + gpu.physicalDevice, gpu.surface, capabilities))) { + return r; + } + + // There is a known race issue here, so we clamp and hope for the best + const VkExtent2D clampedExtent{ + std::min(capabilities.maxImageExtent.width, + std::max(capabilities.minImageExtent.width, extent.width)), + std::min(capabilities.maxImageExtent.height, + std::max(capabilities.minImageExtent.height, extent.height))}; + + if ((r = vk.deviceWaitIdle(gpu.device)) || + (r = app.renderer.recreate(vk, + gpu.surface, + gpu, + rectData, + rectShaders, + clampedExtent, + app.resizing))) { + return r; + } + + // Reset current (initially signaled) fence because we already waited + vk.resetFence(gpu.device, + app.renderer.sync.inFlight[app.renderer.sync.currentFrame]); + + // Record new command buffers + return recordCommandBuffers(vk, + app.renderer.swapchain, + app.renderer.renderPass, + app.renderer.rectPipeline, + rectData); +} + +pugl::Status +View::onEvent(const pugl::ConfigureEvent& event) +{ + // We just record the size here and lazily resize the surface when exposed + _app.extent = {static_cast(event.width), + static_cast(event.height)}; + + return pugl::Status::success; +} + +pugl::Status +View::onEvent(const pugl::UpdateEvent&) +{ + return postRedisplay(); +} + +VkResult +beginFrame(PuglVulkanDemo& app, const sk::Device& device, uint32_t& imageIndex) +{ + const auto& vk = app.vulkan.vk; + + VkResult r = VK_SUCCESS; + + // Wait until we can start rendering the next frame + if ((r = vk.waitForFence( + device, app.renderer.sync.inFlight[app.renderer.sync.currentFrame])) || + (r = vk.resetFence( + device, app.renderer.sync.inFlight[app.renderer.sync.currentFrame]))) { + return r; + } + + // Rebuild the renderer first if the window size has changed + if (app.extent.width != app.renderer.swapchain.extent.width || + app.extent.height != app.renderer.swapchain.extent.height) { + if ((r = recreateRenderer( + app, vk, app.gpu, app.extent, app.rectData, app.rectShaders))) { + return r; + } + } + + // Acquire the next image to render, rebuilding if necessary + while ((r = vk.acquireNextImageKHR( + device, + app.renderer.swapchain.swapchain, + UINT64_MAX, + app.renderer.sync.imageAvailable[app.renderer.sync.currentFrame], + {}, + &imageIndex))) { + switch (r) { + case VK_SUBOPTIMAL_KHR: + case VK_ERROR_OUT_OF_DATE_KHR: + if ((r = recreateRenderer(app, + vk, + app.gpu, + app.renderer.swapchain.extent, + app.rectData, + app.rectShaders))) { + return r; + } + continue; + default: + return r; + } + } + + return VK_SUCCESS; +} + +void +update(PuglVulkanDemo& app, const double time) +{ + // Animate rectangles + for (size_t i = 0; i < app.rects.size(); ++i) { + moveRect(&app.rects[i], + i, + app.rects.size(), + static_cast(app.extent.width), + static_cast(app.extent.height), + time); + } + + // Update vertex buffer + memcpy(app.rectData.vertexData.get(), + app.rects.data(), + sizeof(Rect) * app.rects.size()); + + // Update uniform buffer + UniformBufferObject ubo = {{}}; + mat4Ortho(ubo.projection, + 0.0f, + float(app.renderer.swapchain.extent.width), + 0.0f, + float(app.renderer.swapchain.extent.height), + -1.0f, + 1.0f); + + memcpy(app.rectData.uniformData.get(), &ubo, sizeof(ubo)); +} + +VkResult +endFrame(const sk::VulkanApi& vk, + const GraphicsDevice& gpu, + const Renderer& renderer, + const uint32_t imageIndex) +{ + const auto currentFrame = renderer.sync.currentFrame; + VkResult r = VK_SUCCESS; + + static constexpr VkPipelineStageFlags waitStage = + VK_PIPELINE_STAGE_TRANSFER_BIT; + + const VkSubmitInfo submitInfo{ + VK_STRUCTURE_TYPE_SUBMIT_INFO, + nullptr, + SK_COUNTED(1, &renderer.sync.imageAvailable[currentFrame].get()), + &waitStage, + SK_COUNTED(1, &renderer.renderPass.commandBuffers[imageIndex]), + SK_COUNTED(1, &renderer.sync.renderFinished[imageIndex].get())}; + + if ((r = vk.queueSubmit(gpu.graphicsQueue, + submitInfo, + renderer.sync.inFlight[currentFrame]))) { + return r; + } + + const VkPresentInfoKHR presentInfo{ + VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + nullptr, + SK_COUNTED(1, &renderer.sync.renderFinished[imageIndex].get()), + SK_COUNTED(1, &renderer.swapchain.swapchain.get(), &imageIndex), + nullptr}; + + switch ((r = vk.queuePresentKHR(gpu.graphicsQueue, presentInfo))) { + case VK_SUCCESS: // Sucessfully presented + case VK_SUBOPTIMAL_KHR: // Probably a resize race, ignore + case VK_ERROR_OUT_OF_DATE_KHR: // Probably a resize race, ignore + break; + default: + return r; + } + + return VK_SUCCESS; +} + +pugl::Status +View::onEvent(const pugl::ExposeEvent&) +{ + const auto& vk = _app.vulkan.vk; + const auto& gpu = _app.gpu; + + // Acquire the next image, waiting and/or rebuilding if necessary + auto nextImageIndex = 0u; + if (beginFrame(_app, gpu.device, nextImageIndex)) { + return pugl::Status::unknownError; + } + + // Ready to go, update the data to the current time + update(_app, world().time()); + + // Submit the frame to the queue and present it + endFrame(vk, gpu, _app.renderer, nextImageIndex); + + ++_app.framesDrawn; + ++_app.renderer.sync.currentFrame; + _app.renderer.sync.currentFrame %= _app.renderer.sync.inFlight.size(); + + return pugl::Status::success; +} + +pugl::Status +View::onEvent(const pugl::LoopEnterEvent&) +{ + _app.resizing = true; + startTimer(resizeTimerId, + 1.0 / static_cast(getHint(pugl::ViewHint::refreshRate))); + + return pugl::Status::success; +} + +pugl::Status +View::onEvent(const pugl::TimerEvent&) +{ + return postRedisplay(); +} + +pugl::Status +View::onEvent(const pugl::LoopLeaveEvent&) +{ + stopTimer(resizeTimerId); + + // Trigger a swapchain recreation with the normal present mode + _app.renderer.swapchain.extent = {}; + _app.resizing = false; + + return pugl::Status::success; +} + +pugl::Status +View::onEvent(const pugl::KeyPressEvent& event) +{ + if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') { + _app.quit = true; + } + + return pugl::Status::success; +} + +pugl::Status +View::onEvent(const pugl::CloseEvent&) +{ + _app.quit = true; + + return pugl::Status::success; +} + +VkResult +VulkanContext::init(pugl::VulkanLoader& loader, const PuglTestOptions& opts) +{ + VkResult r = VK_SUCCESS; + + sk::VulkanInitApi initApi{}; + + // Load Vulkan API and set up the fundamentals + if ((r = initApi.init(loader.getInstanceProcAddrFunc())) || + (r = createInstance(initApi, opts, instance)) || + (r = vk.init(initApi, instance)) || + (r = getDebugReportCallback(vk, instance, opts.verbose, debugCallback))) { + return r; + } + + return VK_SUCCESS; +} + +int +run(const char* const programPath, + const PuglTestOptions opts, + const size_t numRects) +{ + PuglVulkanDemo app{programPath, opts, numRects}; + + VkResult r = VK_SUCCESS; + const auto width = static_cast(app.extent.width); + const auto height = static_cast(app.extent.height); + + // Realize window so we can set up Vulkan + app.world.setClassName("PuglVulkanDemo"); + app.view.setWindowTitle("Pugl Vulkan Demo"); + app.view.setAspectRatio(1, 1, 16, 9); + app.view.setDefaultSize(width, height); + app.view.setMinSize(width / 4, height / 4); + app.view.setMaxSize(width * 4, height * 4); + app.view.setBackend(pugl::vulkanBackend()); + app.view.setHint(pugl::ViewHint::resizable, opts.resizable); + const pugl::Status st = app.view.realize(); + if (st != pugl::Status::success) { + return logError("Failed to create window (%s)\n", pugl::strerror(st)); + } + + if (!app.loader) { + return logError("Failed to load Vulkan library\n"); + } + + // Load Vulkan for the view + if ((r = app.vulkan.init(app.loader, opts))) { + return logError("Failed to set up Vulkan API (%s)\n", sk::string(r)); + } + + const auto& vk = app.vulkan.vk; + + // Set up the graphics device + if ((r = app.gpu.init(app.loader, app.vulkan, app.view, opts))) { + return logError("Failed to set up device (%s)\n", sk::string(r)); + } + + logInfo("Present mode", sk::string(app.gpu.presentMode)); + logInfo("Resize present mode", sk::string(app.gpu.resizePresentMode)); + + // Set up the rectangle data we will render every frame + if ((r = app.rectData.init(vk, app.gpu, app.rects.size()))) { + return logError("Failed to allocate render data (%s)\n", sk::string(r)); + } + + // Load shader modules + if ((r = app.rectShaders.init(vk, app.gpu, app.programPath))) { + return logError("Failed to load shaders (%s)\n", sk::string(r)); + } + + if ((r = app.renderer.init(app.vulkan.vk, + app.gpu, + app.rectData, + app.rectShaders, + app.extent, + false))) { + return logError("Failed to create renderer (%s)\n", sk::string(r)); + } + + logInfo("Swapchain frames", + std::to_string(app.renderer.swapchain.imageViews.size())); + logInfo("Frames in flight", + std::to_string(app.renderer.sync.inFlight.size())); + + recordCommandBuffers(app.vulkan.vk, + app.renderer.swapchain, + app.renderer.renderPass, + app.renderer.rectPipeline, + app.rectData); + + const int refreshRate = app.view.getHint(pugl::ViewHint::refreshRate); + const double frameDuration = 1.0 / static_cast(refreshRate); + const double timeout = app.opts.sync ? frameDuration : 0.0; + + PuglFpsPrinter fpsPrinter = {app.world.time()}; + app.view.show(); + while (!app.quit) { + app.world.update(timeout); + puglPrintFps(app.world.cobj(), &fpsPrinter, &app.framesDrawn); + } + + if ((r = app.vulkan.vk.deviceWaitIdle(app.gpu.device))) { + return logError("Failed to wait for device idle (%s)\n", sk::string(r)); + } + + return 0; +} + +} // namespace + +int +main(int argc, char** argv) +{ + // Parse command line options + const char* const programPath = argv[0]; + const PuglTestOptions opts = puglParseTestOptions(&argc, &argv); + if (opts.help) { + puglPrintTestUsage(programPath, ""); + return 0; + } + + // Parse number of rectangles argument, if given + int64_t numRects = 1000; + if (argc >= 1) { + char* endptr = nullptr; + numRects = strtol(argv[0], &endptr, 10); + if (endptr != argv[0] + strlen(argv[0]) || numRects < 1) { + logError("Invalid number of rectangles: %s\n", argv[0]); + return 1; + } + } + + // Run application + return run(programPath, opts, static_cast(numRects)); +} diff --git a/examples/pugl_vulkan_cxx_demo.cpp b/examples/pugl_vulkan_cxx_demo.cpp deleted file mode 100644 index d92e652..0000000 --- a/examples/pugl_vulkan_cxx_demo.cpp +++ /dev/null @@ -1,1826 +0,0 @@ -/* - Copyright 2019-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/* - An example of drawing with Vulkan. - - This is an example of using Vulkan for pixel-perfect 2D drawing. It uses - the same data and shaders as pugl_shader_demo.c and attempts to draw the - same thing, except using Vulkan. - - Since Vulkan is a complicated and very verbose API, this example is - unfortunately much larger than the others. You should not use this as a - resource to learn Vulkan, but it provides a decent demo of using Vulkan with - Pugl that works nicely on all supported platforms. -*/ - -#include "demo_utils.h" -#include "file_utils.h" -#include "rects.h" -#include "test/test_utils.h" - -#include "sybok.hpp" - -#include "pugl/pugl.h" -#include "pugl/pugl.hpp" -#include "pugl/vulkan.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -constexpr uintptr_t resizeTimerId = 1u; - -struct PhysicalDeviceSelection { - sk::PhysicalDevice physicalDevice; - uint32_t graphicsFamilyIndex; -}; - -/// Basic Vulkan context associated with the window -struct VulkanContext { - VkResult init(pugl::VulkanLoader& loader, const PuglTestOptions& opts); - - sk::VulkanApi vk; - sk::Instance instance; - sk::DebugReportCallbackEXT debugCallback; -}; - -/// Basic setup of graphics device -struct GraphicsDevice { - VkResult init(const pugl::VulkanLoader& loader, - const VulkanContext& context, - pugl::View& view, - const PuglTestOptions& opts); - - sk::SurfaceKHR surface; - sk::PhysicalDevice physicalDevice{}; - uint32_t graphicsIndex{}; - VkSurfaceFormatKHR surfaceFormat{}; - VkPresentModeKHR presentMode{}; - VkPresentModeKHR resizePresentMode{}; - sk::Device device{}; - sk::Queue graphicsQueue{}; - sk::CommandPool commandPool{}; -}; - -/// Buffer allocated on the GPU -struct Buffer { - VkResult init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - VkDeviceSize size, - VkBufferUsageFlags usage, - VkMemoryPropertyFlags properties); - - sk::Buffer buffer; - sk::DeviceMemory deviceMemory; -}; - -/// A set of frames that can be rendered concurrently -struct Swapchain { - VkResult init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - VkSurfaceCapabilitiesKHR capabilities, - VkExtent2D extent, - VkSwapchainKHR oldSwapchain, - bool resizing); - - VkSurfaceCapabilitiesKHR capabilities{}; - VkExtent2D extent{}; - sk::SwapchainKHR swapchain{}; - std::vector imageViews{}; -}; - -/// A pass that renders to a target -struct RenderPass { - VkResult init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const Swapchain& swapchain); - - sk::RenderPass renderPass; - std::vector framebuffers; - sk::CommandBuffers> commandBuffers; -}; - -/// Uniform buffer for constant data used in shaders -struct UniformBufferObject { - mat4 projection; -}; - -/// Rectangle data that does not depend on renderer configuration -struct RectData { - VkResult init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - size_t nRects); - - sk::DescriptorSetLayout descriptorSetLayout{}; - Buffer uniformBuffer{}; - sk::MappedMemory uniformData{}; - Buffer modelBuffer{}; - Buffer instanceBuffer{}; - sk::MappedMemory vertexData{}; - size_t numRects{}; -}; - -/// Shader modules for drawing rectangles -struct RectShaders { - VkResult init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const std::string& programPath); - - sk::ShaderModule vert{}; - sk::ShaderModule frag{}; -}; - -/// A pipeline to render rectangles with our shaders -struct RectPipeline { - VkResult init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const RectData& rectData, - const RectShaders& shaders, - const Swapchain& swapchain, - const RenderPass& renderPass); - - sk::DescriptorPool descriptorPool{}; - sk::DescriptorSets> descriptorSets{}; - sk::PipelineLayout pipelineLayout{}; - std::array pipelines{}; - uint32_t numImages{}; -}; - -/// Synchronization primitives used to coordinate drawing frames -struct RenderSync { - VkResult init(const sk::VulkanApi& vk, - const sk::Device& device, - uint32_t numImages); - - std::vector imageAvailable{}; - std::vector renderFinished{}; - std::vector inFlight{}; - size_t currentFrame{}; -}; - -/// Renderer that owns the above and everything required to draw -struct Renderer { - VkResult init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const RectData& rectData, - const RectShaders& rectShaders, - VkExtent2D extent, - bool resizing); - - VkResult recreate(const sk::VulkanApi& vk, - const sk::SurfaceKHR& surface, - const GraphicsDevice& gpu, - const RectData& rectData, - const RectShaders& rectShaders, - VkExtent2D extent, - bool resizing); - - Swapchain swapchain; - RenderPass renderPass; - RectPipeline rectPipeline; - RenderSync sync; -}; - -VkResult -selectSurfaceFormat(const sk::VulkanApi& vk, - const sk::PhysicalDevice& physicalDevice, - const sk::SurfaceKHR& surface, - VkSurfaceFormatKHR& surfaceFormat) -{ - std::vector formats; - if (VkResult r = vk.getPhysicalDeviceSurfaceFormatsKHR( - physicalDevice, surface, formats)) { - return r; - } - - for (const auto& format : formats) { - if (format.format == VK_FORMAT_B8G8R8A8_UNORM && - format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - surfaceFormat = format; - return VK_SUCCESS; - } - } - - return VK_ERROR_FORMAT_NOT_SUPPORTED; -} - -VkResult -selectPresentMode(const sk::VulkanApi& vk, - const sk::PhysicalDevice& physicalDevice, - const sk::SurfaceKHR& surface, - const bool multiBuffer, - const bool sync, - VkPresentModeKHR& presentMode) -{ - // Map command line options to mode priorities - static constexpr VkPresentModeKHR priorities[][2][4] = { - { - // No double buffer, no sync - {VK_PRESENT_MODE_IMMEDIATE_KHR, - VK_PRESENT_MODE_MAILBOX_KHR, - VK_PRESENT_MODE_FIFO_RELAXED_KHR, - VK_PRESENT_MODE_FIFO_KHR}, - - // No double buffer, sync (nonsense, map to FIFO relaxed) - {VK_PRESENT_MODE_FIFO_RELAXED_KHR, - VK_PRESENT_MODE_FIFO_KHR, - VK_PRESENT_MODE_MAILBOX_KHR, - VK_PRESENT_MODE_IMMEDIATE_KHR}, - }, - { - // Double buffer, no sync - { - VK_PRESENT_MODE_MAILBOX_KHR, - VK_PRESENT_MODE_IMMEDIATE_KHR, - VK_PRESENT_MODE_FIFO_RELAXED_KHR, - VK_PRESENT_MODE_FIFO_KHR, - }, - - // Double buffer, sync - {VK_PRESENT_MODE_FIFO_KHR, - VK_PRESENT_MODE_FIFO_RELAXED_KHR, - VK_PRESENT_MODE_MAILBOX_KHR, - VK_PRESENT_MODE_IMMEDIATE_KHR}, - }, - }; - - std::vector modes; - if (VkResult r = vk.getPhysicalDeviceSurfacePresentModesKHR( - physicalDevice, surface, modes)) { - return r; - } - - const auto& tryModes = priorities[bool(multiBuffer)][bool(sync)]; - for (const auto m : tryModes) { - if (std::find(modes.begin(), modes.end(), m) != modes.end()) { - presentMode = m; - return VK_SUCCESS; - } - } - - return VK_ERROR_INCOMPATIBLE_DRIVER; -} - -VkResult -openDevice(const sk::VulkanApi& vk, - const sk::PhysicalDevice& physicalDevice, - const uint32_t graphicsFamilyIndex, - sk::Device& device) -{ - const float graphicsQueuePriority = 1.0f; - const char* const swapchainName = "VK_KHR_swapchain"; - - const VkDeviceQueueCreateInfo queueCreateInfo{ - VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - nullptr, - 0u, - graphicsFamilyIndex, - SK_COUNTED(1u, &graphicsQueuePriority), - }; - - const VkDeviceCreateInfo createInfo{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - nullptr, - 0u, - SK_COUNTED(1u, &queueCreateInfo), - SK_COUNTED(0u, nullptr), // Deprecated - SK_COUNTED(1u, &swapchainName), - nullptr}; - - return vk.createDevice(physicalDevice, createInfo, device); -} - -/// Return whether the physical device supports the extensions we require -VkResult -deviceSupportsRequiredExtensions(const sk::VulkanApi& vk, - const sk::PhysicalDevice& device, - bool& supported) -{ - VkResult r = VK_SUCCESS; - - std::vector props; - if ((r = vk.enumerateDeviceExtensionProperties(device, props))) { - return r; - } - - supported = std::any_of( - props.begin(), props.end(), [&](const VkExtensionProperties& e) { - return !strcmp(e.extensionName, "VK_KHR_swapchain"); - }); - - return VK_SUCCESS; -} - -/// Return the index of the graphics queue, if there is one -VkResult -findGraphicsQueue(const sk::VulkanApi& vk, - const sk::SurfaceKHR& surface, - const sk::PhysicalDevice& device, - uint32_t& queueIndex) -{ - VkResult r = VK_SUCCESS; - - std::vector queueProps; - if ((r = vk.getPhysicalDeviceQueueFamilyProperties(device, queueProps))) { - return r; - } - - for (uint32_t q = 0u; q < queueProps.size(); ++q) { - if (queueProps[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) { - bool supported = false; - if ((r = vk.getPhysicalDeviceSurfaceSupportKHR( - device, q, surface, supported))) { - return r; - } - - if (supported) { - queueIndex = q; - return VK_SUCCESS; - } - } - } - - return VK_ERROR_FEATURE_NOT_PRESENT; -} - -/// Select a physical graphics device to use (simply the first found) -VkResult -selectPhysicalDevice(const sk::VulkanApi& vk, - const sk::Instance& instance, - const sk::SurfaceKHR& surface, - PhysicalDeviceSelection& selection) -{ - VkResult r = VK_SUCCESS; - - std::vector devices; - if ((r = vk.enumeratePhysicalDevices(instance, devices))) { - return r; - } - - for (const auto& device : devices) { - auto supported = false; - if ((r = deviceSupportsRequiredExtensions(vk, device, supported))) { - return r; - } - - if (supported) { - auto queueIndex = 0u; - if ((r = findGraphicsQueue(vk, surface, device, queueIndex))) { - return r; - } - - selection = PhysicalDeviceSelection{device, queueIndex}; - return VK_SUCCESS; - } - } - - return VK_ERROR_INCOMPATIBLE_DISPLAY_KHR; -} - -VkResult -GraphicsDevice::init(const pugl::VulkanLoader& loader, - const VulkanContext& context, - pugl::View& view, - const PuglTestOptions& opts) -{ - const auto& vk = context.vk; - VkResult r = VK_SUCCESS; - - // Create a Vulkan surface for the window using the Pugl API - VkSurfaceKHR surfaceHandle = {}; - if ((r = pugl::createSurface(loader.getInstanceProcAddrFunc(), - view, - context.instance, - nullptr, - &surfaceHandle))) { - return r; - } - - // Wrap surface in a safe RAII handle - surface = - sk::SurfaceKHR{surfaceHandle, {context.instance, vk.vkDestroySurfaceKHR}}; - - PhysicalDeviceSelection physicalDeviceSelection = {}; - // Select a physical device to use - if ((r = selectPhysicalDevice( - vk, context.instance, surface, physicalDeviceSelection))) { - return r; - } - - physicalDevice = physicalDeviceSelection.physicalDevice; - graphicsIndex = physicalDeviceSelection.graphicsFamilyIndex; - - if ((r = selectSurfaceFormat(vk, physicalDevice, surface, surfaceFormat)) || - (r = selectPresentMode(vk, - physicalDevice, - surface, - opts.doubleBuffer, - opts.sync, - presentMode)) || - (r = selectPresentMode( - vk, physicalDevice, surface, true, false, resizePresentMode)) || - (r = openDevice(vk, physicalDevice, graphicsIndex, device))) { - return r; - } - - const VkCommandPoolCreateInfo commandPoolInfo{ - VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, {}, graphicsIndex}; - - if ((r = vk.createCommandPool(device, commandPoolInfo, commandPool))) { - return r; - } - - graphicsQueue = vk.getDeviceQueue(device, graphicsIndex, 0); - return VK_SUCCESS; -} - -uint32_t -findMemoryType(const sk::VulkanApi& vk, - const sk::PhysicalDevice& physicalDevice, - const uint32_t typeFilter, - const VkMemoryPropertyFlags& properties) -{ - VkPhysicalDeviceMemoryProperties memProperties = - vk.getPhysicalDeviceMemoryProperties(physicalDevice); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & - properties) == properties) { - return i; - } - } - - return UINT32_MAX; -} - -VkResult -Buffer::init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const VkDeviceSize size, - const VkBufferUsageFlags usage, - const VkMemoryPropertyFlags properties) -{ - const VkBufferCreateInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - nullptr, - {}, - size, - usage, - VK_SHARING_MODE_EXCLUSIVE, - SK_COUNTED(0, nullptr)}; - - const auto& device = gpu.device; - - VkResult r = VK_SUCCESS; - if ((r = vk.createBuffer(device, bufferInfo, buffer))) { - return r; - } - - const auto requirements = vk.getBufferMemoryRequirements(device, buffer); - const auto memoryTypeIndex = findMemoryType( - vk, gpu.physicalDevice, requirements.memoryTypeBits, properties); - - if (memoryTypeIndex == UINT32_MAX) { - return VK_ERROR_FEATURE_NOT_PRESENT; - } - - const VkMemoryAllocateInfo allocInfo{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - nullptr, - requirements.size, - memoryTypeIndex}; - - if ((r = vk.allocateMemory(device, allocInfo, deviceMemory)) || - (r = vk.bindBufferMemory(device, buffer, deviceMemory, 0))) { - return r; - } - - return VK_SUCCESS; -} - -VkResult -Swapchain::init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const VkSurfaceCapabilitiesKHR surfaceCapabilities, - const VkExtent2D surfaceExtent, - VkSwapchainKHR oldSwapchain, - bool resizing) -{ - capabilities = surfaceCapabilities; - extent = surfaceExtent; - - const auto minNumImages = - (!capabilities.maxImageCount || capabilities.maxImageCount >= 3u) - ? 3u - : capabilities.maxImageCount; - - const VkSwapchainCreateInfoKHR swapchainCreateInfo{ - VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, - nullptr, - {}, - gpu.surface, - minNumImages, - gpu.surfaceFormat.format, - gpu.surfaceFormat.colorSpace, - surfaceExtent, - 1, - (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT), - VK_SHARING_MODE_EXCLUSIVE, - SK_COUNTED(0, nullptr), - capabilities.currentTransform, - VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, - resizing ? gpu.resizePresentMode : gpu.presentMode, - VK_TRUE, - oldSwapchain}; - - VkResult r = VK_SUCCESS; - std::vector images; - if ((r = vk.createSwapchainKHR(gpu.device, swapchainCreateInfo, swapchain)) || - (r = vk.getSwapchainImagesKHR(gpu.device, swapchain, images))) { - return r; - } - - imageViews = std::vector(images.size()); - for (size_t i = 0; i < images.size(); ++i) { - const VkImageViewCreateInfo imageViewCreateInfo{ - VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - nullptr, - {}, - images[i], - VK_IMAGE_VIEW_TYPE_2D, - gpu.surfaceFormat.format, - {}, - {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; - - if ((r = vk.createImageView( - gpu.device, imageViewCreateInfo, imageViews[i]))) { - return r; - } - } - - return VK_SUCCESS; -} - -VkResult -RenderPass::init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const Swapchain& swapchain) -{ - const auto numImages = static_cast(swapchain.imageViews.size()); - - assert(numImages > 0); - - // Create command buffers - const VkCommandBufferAllocateInfo commandBufferAllocateInfo{ - VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - nullptr, - gpu.commandPool, - VK_COMMAND_BUFFER_LEVEL_PRIMARY, - numImages}; - - VkResult r = VK_SUCCESS; - if ((r = vk.allocateCommandBuffers( - gpu.device, commandBufferAllocateInfo, commandBuffers))) { - return r; - } - - static constexpr VkAttachmentReference colorAttachmentRef{ - 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; - - static constexpr VkSubpassDescription subpass{ - {}, - VK_PIPELINE_BIND_POINT_GRAPHICS, - SK_COUNTED(0, nullptr), - SK_COUNTED(1, &colorAttachmentRef, nullptr, nullptr), - SK_COUNTED(0u, nullptr)}; - - static constexpr VkSubpassDependency dependency{ - VK_SUBPASS_EXTERNAL, - 0, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - (VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT), - {}, - {}}; - - const VkAttachmentDescription colorAttachment{ - {}, - gpu.surfaceFormat.format, - VK_SAMPLE_COUNT_1_BIT, - VK_ATTACHMENT_LOAD_OP_CLEAR, - VK_ATTACHMENT_STORE_OP_STORE, - VK_ATTACHMENT_LOAD_OP_DONT_CARE, - VK_ATTACHMENT_STORE_OP_DONT_CARE, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - }; - - const VkRenderPassCreateInfo renderPassCreateInfo{ - VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, - nullptr, - {}, - SK_COUNTED(1, &colorAttachment), - SK_COUNTED(1, &subpass), - SK_COUNTED(1, &dependency)}; - - if ((r = vk.createRenderPass(gpu.device, renderPassCreateInfo, renderPass))) { - return r; - } - - // Create framebuffers - framebuffers = std::vector(numImages); - for (uint32_t i = 0; i < numImages; ++i) { - const VkFramebufferCreateInfo framebufferCreateInfo{ - VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, - nullptr, - {}, - renderPass, - SK_COUNTED(1, &swapchain.imageViews[i].get()), - swapchain.extent.width, - swapchain.extent.height, - 1}; - - if ((r = vk.createFramebuffer( - gpu.device, framebufferCreateInfo, framebuffers[i]))) { - return r; - } - } - - return VK_SUCCESS; -} - -std::vector -readFile(const char* const programPath, const std::string& filename) -{ - std::unique_ptr path{ - resourcePath(programPath, filename.c_str()), &free}; - - std::cerr << "Loading shader: " << path.get() << std::endl; - - std::unique_ptr file{fopen(path.get(), "rb"), - &fclose}; - - if (!file) { - std::cerr << "Failed to open file '" << filename << "'\n"; - return {}; - } - - fseek(file.get(), 0, SEEK_END); - const auto fileSize = static_cast(ftell(file.get())); - fseek(file.get(), 0, SEEK_SET); - - const auto numWords = fileSize / sizeof(uint32_t); - std::vector buffer(numWords); - - fread(buffer.data(), sizeof(uint32_t), numWords, file.get()); - - return buffer; -} - -VkResult -createShaderModule(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const std::vector& code, - sk::ShaderModule& shaderModule) -{ - const VkShaderModuleCreateInfo createInfo{ - VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - nullptr, - {}, - code.size() * sizeof(uint32_t), - code.data()}; - - return vk.createShaderModule(gpu.device, createInfo, shaderModule); -} - -VkResult -RectShaders::init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const std::string& programPath) -{ - auto vertShaderCode = readFile(programPath.c_str(), "shaders/rect.vert.spv"); - - auto fragShaderCode = readFile(programPath.c_str(), "shaders/rect.frag.spv"); - - if (vertShaderCode.empty() || fragShaderCode.empty()) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - VkResult r = VK_SUCCESS; - if ((r = createShaderModule(vk, gpu, vertShaderCode, vert)) || - (r = createShaderModule(vk, gpu, fragShaderCode, frag))) { - return r; - } - - return VK_SUCCESS; -} - -VkResult -RectPipeline::init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const RectData& rectData, - const RectShaders& shaders, - const Swapchain& swapchain, - const RenderPass& renderPass) -{ - const auto oldNumImages = numImages; - VkResult r = VK_SUCCESS; - - numImages = static_cast(swapchain.imageViews.size()); - pipelines = {}; - pipelineLayout = {}; - descriptorSets = {}; - - if (numImages != oldNumImages) { - // Create layout descriptor pool - - const VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - numImages}; - - const VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{ - VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - nullptr, - VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - numImages, - 1u, - &poolSize}; - if ((r = vk.createDescriptorPool( - gpu.device, descriptorPoolCreateInfo, descriptorPool))) { - return r; - } - } - - const std::vector layouts( - numImages, rectData.descriptorSetLayout.get()); - - const VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{ - VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - nullptr, - descriptorPool, - numImages, - layouts.data()}; - if ((r = vk.allocateDescriptorSets( - gpu.device, descriptorSetAllocateInfo, descriptorSets))) { - return r; - } - - const VkDescriptorBufferInfo bufferInfo{ - rectData.uniformBuffer.buffer, 0, sizeof(UniformBufferObject)}; - - const std::array descriptorWrites{ - {{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - nullptr, - descriptorSets[0], - 0, - 0, - 1, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - nullptr, - &bufferInfo, - nullptr}}}; - - const std::array descriptorCopies{}; - - vk.updateDescriptorSets(gpu.device, descriptorWrites, descriptorCopies); - - static constexpr std::array - vertexAttributeDescriptions{ - {// Model - {0u, 0u, VK_FORMAT_R32G32_SFLOAT, 0}, - - // Rect instance attributes - {1u, 1u, VK_FORMAT_R32G32_SFLOAT, offsetof(Rect, pos)}, - {2u, 1u, VK_FORMAT_R32G32_SFLOAT, offsetof(Rect, size)}, - {3u, 1u, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Rect, fillColor)}}}; - - static constexpr std::array - vertexBindingDescriptions{ - VkVertexInputBindingDescription{ - 0, sizeof(vec2), VK_VERTEX_INPUT_RATE_VERTEX}, - VkVertexInputBindingDescription{ - 1u, sizeof(Rect), VK_VERTEX_INPUT_RATE_INSTANCE}}; - - static constexpr VkPipelineInputAssemblyStateCreateInfo inputAssembly{ - VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, - nullptr, - {}, - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, - false}; - - static constexpr VkPipelineRasterizationStateCreateInfo rasterizer{ - VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, - nullptr, - {}, - 0, - 0, - VK_POLYGON_MODE_FILL, - VK_CULL_MODE_BACK_BIT, - VK_FRONT_FACE_CLOCKWISE, - 0, - 0, - 0, - 0, - 1.0f}; - - static constexpr VkPipelineMultisampleStateCreateInfo multisampling{ - VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, - nullptr, - {}, - VK_SAMPLE_COUNT_1_BIT, - false, - 0.0f, - nullptr, - false, - false}; - - static constexpr VkPipelineColorBlendAttachmentState colorBlendAttachment{ - true, - VK_BLEND_FACTOR_SRC_ALPHA, - VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, - VK_BLEND_OP_ADD, - VK_BLEND_FACTOR_ONE, - VK_BLEND_FACTOR_ZERO, - VK_BLEND_OP_ADD, - (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | - VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT)}; - - const VkPipelineShaderStageCreateInfo shaderStages[] = { - {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - nullptr, - {}, - VK_SHADER_STAGE_VERTEX_BIT, - shaders.vert.get(), - "main", - nullptr}, - {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - nullptr, - {}, - VK_SHADER_STAGE_FRAGMENT_BIT, - shaders.frag.get(), - "main", - nullptr}}; - - const VkPipelineVertexInputStateCreateInfo vertexInputInfo{ - VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - nullptr, - {}, - SK_COUNTED(static_cast(vertexBindingDescriptions.size()), - vertexBindingDescriptions.data()), - SK_COUNTED(static_cast(vertexAttributeDescriptions.size()), - vertexAttributeDescriptions.data())}; - - const VkViewport viewport{0.0f, - 0.0f, - float(swapchain.extent.width), - float(swapchain.extent.height), - 0.0f, - 1.0f}; - - const VkRect2D scissor{{0, 0}, swapchain.extent}; - - const VkPipelineViewportStateCreateInfo viewportState{ - VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, - nullptr, - {}, - SK_COUNTED(1, &viewport), - SK_COUNTED(1, &scissor)}; - - const VkPipelineColorBlendStateCreateInfo colorBlending{ - VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, - nullptr, - {}, - false, - VK_LOGIC_OP_COPY, - SK_COUNTED(1, &colorBlendAttachment), - {1.0f, 0.0f, 0.0f, 0.0f}}; - - const VkPipelineLayoutCreateInfo layoutInfo{ - VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - nullptr, - {}, - SK_COUNTED(1, &rectData.descriptorSetLayout.get()), - SK_COUNTED(0, nullptr)}; - - if ((r = vk.createPipelineLayout(gpu.device, layoutInfo, pipelineLayout))) { - return r; - } - - const std::array pipelineInfos{ - {{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, - nullptr, - {}, - SK_COUNTED(2, shaderStages), - &vertexInputInfo, - &inputAssembly, - nullptr, - &viewportState, - &rasterizer, - &multisampling, - nullptr, - &colorBlending, - nullptr, - pipelineLayout, - renderPass.renderPass, - 0u, - {}, - 0}}}; - - if ((r = vk.createGraphicsPipelines( - gpu.device, {}, pipelineInfos, pipelines))) { - return r; - } - - return VK_SUCCESS; -} - -VkResult -RectData::init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const size_t nRects) -{ - numRects = nRects; - - static constexpr VkDescriptorSetLayoutBinding uboLayoutBinding{ - 0, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - 1, - VK_SHADER_STAGE_VERTEX_BIT, - nullptr}; - - const VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo{ - VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - nullptr, - {}, - 1, - &uboLayoutBinding}; - - VkResult r = VK_SUCCESS; - if ((r = vk.createDescriptorSetLayout( - gpu.device, descriptorSetLayoutInfo, descriptorSetLayout)) || - (r = uniformBuffer.init(vk, - gpu, - sizeof(UniformBufferObject), - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) || - (r = vk.mapMemory(gpu.device, - uniformBuffer.deviceMemory, - 0, - sizeof(UniformBufferObject), - {}, - uniformData))) { - return r; - } - - const VkBufferUsageFlags usageFlags = - (VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | - VK_BUFFER_USAGE_TRANSFER_DST_BIT); - - const VkMemoryPropertyFlags propertyFlags = - (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - - if ((r = modelBuffer.init( - vk, gpu, sizeof(rectVertices), usageFlags, propertyFlags))) { - return r; - } - - { - // Copy model vertices (directly, we do this only once) - sk::MappedMemory modelData; - if ((r = vk.mapMemory(gpu.device, - modelBuffer.deviceMemory, - 0, - static_cast(sizeof(rectVertices)), - {}, - modelData))) { - return r; - } - - memcpy(modelData.get(), rectVertices, sizeof(rectVertices)); - } - - if ((r = instanceBuffer.init( - vk, gpu, sizeof(Rect) * numRects, usageFlags, propertyFlags))) { - return r; - } - - // Map attribute vertices (we will update them every frame) - const auto rectsSize = static_cast(sizeof(Rect) * numRects); - if ((r = vk.mapMemory(gpu.device, - instanceBuffer.deviceMemory, - 0, - rectsSize, - {}, - vertexData))) { - return r; - } - - return VK_SUCCESS; -} - -VkResult -RenderSync::init(const sk::VulkanApi& vk, - const sk::Device& device, - const uint32_t numImages) -{ - const auto maxInFlight = std::max(1u, numImages - 1u); - VkResult r = VK_SUCCESS; - - imageAvailable = std::vector(numImages); - renderFinished = std::vector(numImages); - for (uint32_t i = 0; i < numImages; ++i) { - static constexpr VkSemaphoreCreateInfo semaphoreInfo{ - VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, {}}; - - if ((r = vk.createSemaphore(device, semaphoreInfo, imageAvailable[i])) || - (r = vk.createSemaphore(device, semaphoreInfo, renderFinished[i]))) { - return r; - } - } - - inFlight = std::vector(maxInFlight); - for (uint32_t i = 0; i < maxInFlight; ++i) { - static constexpr VkFenceCreateInfo fenceInfo{ - VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, - nullptr, - VK_FENCE_CREATE_SIGNALED_BIT}; - - if ((r = vk.createFence(device, fenceInfo, inFlight[i]))) { - return r; - } - } - - return VK_SUCCESS; -} - -VkResult -Renderer::init(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const RectData& rectData, - const RectShaders& rectShaders, - const VkExtent2D extent, - bool resizing) -{ - VkResult r = VK_SUCCESS; - VkSurfaceCapabilitiesKHR capabilities = {}; - - if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR( - gpu.physicalDevice, gpu.surface, capabilities)) || - (r = swapchain.init(vk, gpu, capabilities, extent, {}, resizing)) || - (r = renderPass.init(vk, gpu, swapchain)) || - (r = rectPipeline.init( - vk, gpu, rectData, rectShaders, swapchain, renderPass))) { - return r; - } - - const auto numFrames = static_cast(swapchain.imageViews.size()); - return sync.init(vk, gpu.device, numFrames); -} - -VkResult -Renderer::recreate(const sk::VulkanApi& vk, - const sk::SurfaceKHR& surface, - const GraphicsDevice& gpu, - const RectData& rectData, - const RectShaders& rectShaders, - const VkExtent2D extent, - bool resizing) -{ - VkResult r = VK_SUCCESS; - const auto oldNumImages = swapchain.imageViews.size(); - - VkSurfaceCapabilitiesKHR capabilities = {}; - if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR( - gpu.physicalDevice, surface, capabilities)) || - (r = swapchain.init( - vk, gpu, capabilities, extent, swapchain.swapchain, resizing)) || - (r = renderPass.init(vk, gpu, swapchain)) || - (r = rectPipeline.init( - vk, gpu, rectData, rectShaders, swapchain, renderPass))) { - return r; - } - - const auto numFrames = static_cast(swapchain.imageViews.size()); - if (swapchain.imageViews.size() != oldNumImages) { - return sync.init(vk, gpu.device, numFrames); - } - - return VK_SUCCESS; -} - -VKAPI_ATTR -VkBool32 VKAPI_CALL -debugCallback(VkDebugReportFlagsEXT flags, - VkDebugReportObjectTypeEXT, - uint64_t, - size_t, - int32_t, - const char* layerPrefix, - const char* msg, - void*) -{ - std::cerr << sk::string(static_cast(flags)) << ": " - << layerPrefix << ": " << msg << std::endl; - - return VK_FALSE; -} - -bool -hasExtension(const char* name, - const std::vector& properties) -{ - for (const auto& p : properties) { - if (!strcmp(p.extensionName, name)) { - return true; - } - } - - return false; -} - -bool -hasLayer(const char* name, const std::vector& properties) -{ - for (const auto& p : properties) { - if (!strcmp(p.layerName, name)) { - return true; - } - } - - return false; -} - -template -void -logInfo(const char* heading, const Value& value) -{ - std::cout << std::setw(26) << std::left << (std::string(heading) + ":") - << value << std::endl; -} - -VkResult -createInstance(sk::VulkanInitApi& initApi, - const PuglTestOptions& opts, - sk::Instance& instance) -{ - VkResult r = VK_SUCCESS; - - std::vector layerProps; - std::vector extProps; - if ((r = initApi.enumerateInstanceLayerProperties(layerProps)) || - (r = initApi.enumerateInstanceExtensionProperties(extProps))) { - return r; - } - - const auto puglExtensions = pugl::getInstanceExtensions(); - auto extensions = - std::vector(puglExtensions.begin(), puglExtensions.end()); - - // Add extra extensions we want to use if they are supported - if (hasExtension("VK_EXT_debug_report", extProps)) { - extensions.push_back("VK_EXT_debug_report"); - } - - // Add validation layers if error checking is enabled - std::vector layers; - if (opts.errorChecking) { - for (const char* l : {"VK_LAYER_KHRONOS_validation", - "VK_LAYER_LUNARG_standard_validation"}) { - if (hasLayer(l, layerProps)) { - layers.push_back(l); - } - } - } - - for (const auto& e : extensions) { - logInfo("Using instance extension", e); - } - - for (const auto& l : layers) { - logInfo("Using instance layer", l); - } - - static constexpr VkApplicationInfo appInfo{ - VK_STRUCTURE_TYPE_APPLICATION_INFO, - nullptr, - "Pugl Vulkan Demo", - 0, - nullptr, - 0, - VK_MAKE_VERSION(1, 0, 0), - }; - - const VkInstanceCreateInfo createInfo{ - VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - nullptr, - VkInstanceCreateFlags{}, - &appInfo, - SK_COUNTED(uint32_t(layers.size()), layers.data()), - SK_COUNTED(uint32_t(extensions.size()), extensions.data())}; - - return initApi.createInstance(createInfo, instance); -} - -VkResult -getDebugReportCallback(sk::VulkanApi& api, - sk::Instance& instance, - const bool verbose, - sk::DebugReportCallbackEXT& callback) -{ - if (api.vkCreateDebugReportCallbackEXT) { - VkDebugReportFlagsEXT flags = (VK_DEBUG_REPORT_WARNING_BIT_EXT | - VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | - VK_DEBUG_REPORT_ERROR_BIT_EXT); - - if (verbose) { - flags |= VK_DEBUG_REPORT_INFORMATION_BIT_EXT; - flags |= VK_DEBUG_REPORT_DEBUG_BIT_EXT; - } - - const VkDebugReportCallbackCreateInfoEXT createInfo{ - VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT, - nullptr, - flags, - debugCallback, - nullptr}; - - return api.createDebugReportCallbackEXT(instance, createInfo, callback); - } - - return VK_ERROR_FEATURE_NOT_PRESENT; -} - -void -recordCommandBuffer(sk::CommandScope& cmd, - const Swapchain& swapchain, - const RenderPass& renderPass, - const RectPipeline& rectPipeline, - const RectData& rectData, - const size_t imageIndex) -{ - const VkClearColorValue clearColorValue{{0.0f, 0.0f, 0.0f, 1.0f}}; - const VkClearValue clearValue{clearColorValue}; - - const VkRenderPassBeginInfo renderPassBegin{ - VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, - nullptr, - renderPass.renderPass, - renderPass.framebuffers[imageIndex], - VkRect2D{{0, 0}, swapchain.extent}, - SK_COUNTED(1, &clearValue)}; - - auto pass = cmd.beginRenderPass(renderPassBegin, VK_SUBPASS_CONTENTS_INLINE); - - pass.bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, rectPipeline.pipelines[0]); - - const std::array offsets{0}; - pass.bindVertexBuffers( - 0u, SK_COUNTED(1u, &rectData.modelBuffer.buffer.get(), offsets.data())); - - pass.bindVertexBuffers( - 1u, SK_COUNTED(1u, &rectData.instanceBuffer.buffer.get(), offsets.data())); - - pass.bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, - rectPipeline.pipelineLayout, - 0u, - SK_COUNTED(1u, rectPipeline.descriptorSets.get()), - 0u, - nullptr); - - pass.draw(4u, static_cast(rectData.numRects), 0u, 0u); -} - -VkResult -recordCommandBuffers(const sk::VulkanApi& vk, - const Swapchain& swapchain, - const RenderPass& renderPass, - const RectPipeline& rectPipeline, - const RectData& rectData) -{ - VkResult r = VK_SUCCESS; - - for (size_t i = 0; i < swapchain.imageViews.size(); ++i) { - const VkCommandBufferBeginInfo beginInfo{ - VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - nullptr, - VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, - nullptr}; - - auto* const commandBuffer = renderPass.commandBuffers[i]; - auto cmd = vk.beginCommandBuffer(commandBuffer, beginInfo); - if (!cmd) { - return cmd.error(); - } - - recordCommandBuffer(cmd, swapchain, renderPass, rectPipeline, rectData, i); - - if ((r = cmd.end())) { - return r; - } - } - - return VK_SUCCESS; -} - -class PuglVulkanDemo; - -class View : public pugl::View -{ -public: - View(pugl::World& world, PuglVulkanDemo& app) - : pugl::View{world} - , _app{app} - { - setEventHandler(*this); - } - - template - pugl::Status onEvent(const pugl::Event&) noexcept - { - return pugl::Status::success; - } - - pugl::Status onEvent(const pugl::ConfigureEvent& event); - pugl::Status onEvent(const pugl::UpdateEvent& event); - pugl::Status onEvent(const pugl::ExposeEvent& event); - pugl::Status onEvent(const pugl::LoopEnterEvent& event); - pugl::Status onEvent(const pugl::TimerEvent& event); - pugl::Status onEvent(const pugl::LoopLeaveEvent& event); - pugl::Status onEvent(const pugl::KeyPressEvent& event); - pugl::Status onEvent(const pugl::CloseEvent& event); - -private: - PuglVulkanDemo& _app; -}; - -class PuglVulkanDemo -{ -public: - PuglVulkanDemo(const char* executablePath, - const PuglTestOptions& o, - size_t numRects); - - const char* programPath; - PuglTestOptions opts; - pugl::World world; - pugl::VulkanLoader loader; - View view; - VulkanContext vulkan; - GraphicsDevice gpu; - Renderer renderer; - RectData rectData; - RectShaders rectShaders; - uint32_t framesDrawn{0}; - VkExtent2D extent{512u, 512u}; - std::vector rects; - bool resizing{false}; - bool quit{false}; -}; - -std::vector -makeRects(const size_t numRects, const uint32_t windowWidth) -{ - std::vector rects(numRects); - for (size_t i = 0; i < numRects; ++i) { - rects[i] = makeRect(i, static_cast(windowWidth)); - } - - return rects; -} - -PuglVulkanDemo::PuglVulkanDemo(const char* const executablePath, - const PuglTestOptions& o, - const size_t numRects) - : programPath{executablePath} - , opts{o} - , world{pugl::WorldType::program, pugl::WorldFlag::threads} - , loader{world} - , view{world, *this} - , rects{makeRects(numRects, extent.width)} -{} - -VkResult -recreateRenderer(PuglVulkanDemo& app, - const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const VkExtent2D extent, - const RectData& rectData, - const RectShaders& rectShaders) -{ - VkResult r = VK_SUCCESS; - VkSurfaceCapabilitiesKHR capabilities = {}; - if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR( - gpu.physicalDevice, gpu.surface, capabilities))) { - return r; - } - - // There is a known race issue here, so we clamp and hope for the best - const VkExtent2D clampedExtent{ - std::min(capabilities.maxImageExtent.width, - std::max(capabilities.minImageExtent.width, extent.width)), - std::min(capabilities.maxImageExtent.height, - std::max(capabilities.minImageExtent.height, extent.height))}; - - if ((r = vk.deviceWaitIdle(gpu.device)) || - (r = app.renderer.recreate(vk, - gpu.surface, - gpu, - rectData, - rectShaders, - clampedExtent, - app.resizing))) { - return r; - } - - // Reset current (initially signaled) fence because we already waited - vk.resetFence(gpu.device, - app.renderer.sync.inFlight[app.renderer.sync.currentFrame]); - - // Record new command buffers - return recordCommandBuffers(vk, - app.renderer.swapchain, - app.renderer.renderPass, - app.renderer.rectPipeline, - rectData); -} - -pugl::Status -View::onEvent(const pugl::ConfigureEvent& event) -{ - // We just record the size here and lazily resize the surface when exposed - _app.extent = {static_cast(event.width), - static_cast(event.height)}; - - return pugl::Status::success; -} - -pugl::Status -View::onEvent(const pugl::UpdateEvent&) -{ - return postRedisplay(); -} - -VkResult -beginFrame(PuglVulkanDemo& app, const sk::Device& device, uint32_t& imageIndex) -{ - const auto& vk = app.vulkan.vk; - - VkResult r = VK_SUCCESS; - - // Wait until we can start rendering the next frame - if ((r = vk.waitForFence( - device, app.renderer.sync.inFlight[app.renderer.sync.currentFrame])) || - (r = vk.resetFence( - device, app.renderer.sync.inFlight[app.renderer.sync.currentFrame]))) { - return r; - } - - // Rebuild the renderer first if the window size has changed - if (app.extent.width != app.renderer.swapchain.extent.width || - app.extent.height != app.renderer.swapchain.extent.height) { - if ((r = recreateRenderer( - app, vk, app.gpu, app.extent, app.rectData, app.rectShaders))) { - return r; - } - } - - // Acquire the next image to render, rebuilding if necessary - while ((r = vk.acquireNextImageKHR( - device, - app.renderer.swapchain.swapchain, - UINT64_MAX, - app.renderer.sync.imageAvailable[app.renderer.sync.currentFrame], - {}, - &imageIndex))) { - switch (r) { - case VK_SUBOPTIMAL_KHR: - case VK_ERROR_OUT_OF_DATE_KHR: - if ((r = recreateRenderer(app, - vk, - app.gpu, - app.renderer.swapchain.extent, - app.rectData, - app.rectShaders))) { - return r; - } - continue; - default: - return r; - } - } - - return VK_SUCCESS; -} - -void -update(PuglVulkanDemo& app, const double time) -{ - // Animate rectangles - for (size_t i = 0; i < app.rects.size(); ++i) { - moveRect(&app.rects[i], - i, - app.rects.size(), - static_cast(app.extent.width), - static_cast(app.extent.height), - time); - } - - // Update vertex buffer - memcpy(app.rectData.vertexData.get(), - app.rects.data(), - sizeof(Rect) * app.rects.size()); - - // Update uniform buffer - UniformBufferObject ubo = {{}}; - mat4Ortho(ubo.projection, - 0.0f, - float(app.renderer.swapchain.extent.width), - 0.0f, - float(app.renderer.swapchain.extent.height), - -1.0f, - 1.0f); - - memcpy(app.rectData.uniformData.get(), &ubo, sizeof(ubo)); -} - -VkResult -endFrame(const sk::VulkanApi& vk, - const GraphicsDevice& gpu, - const Renderer& renderer, - const uint32_t imageIndex) -{ - const auto currentFrame = renderer.sync.currentFrame; - VkResult r = VK_SUCCESS; - - static constexpr VkPipelineStageFlags waitStage = - VK_PIPELINE_STAGE_TRANSFER_BIT; - - const VkSubmitInfo submitInfo{ - VK_STRUCTURE_TYPE_SUBMIT_INFO, - nullptr, - SK_COUNTED(1, &renderer.sync.imageAvailable[currentFrame].get()), - &waitStage, - SK_COUNTED(1, &renderer.renderPass.commandBuffers[imageIndex]), - SK_COUNTED(1, &renderer.sync.renderFinished[imageIndex].get())}; - - if ((r = vk.queueSubmit(gpu.graphicsQueue, - submitInfo, - renderer.sync.inFlight[currentFrame]))) { - return r; - } - - const VkPresentInfoKHR presentInfo{ - VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - nullptr, - SK_COUNTED(1, &renderer.sync.renderFinished[imageIndex].get()), - SK_COUNTED(1, &renderer.swapchain.swapchain.get(), &imageIndex), - nullptr}; - - switch ((r = vk.queuePresentKHR(gpu.graphicsQueue, presentInfo))) { - case VK_SUCCESS: // Sucessfully presented - case VK_SUBOPTIMAL_KHR: // Probably a resize race, ignore - case VK_ERROR_OUT_OF_DATE_KHR: // Probably a resize race, ignore - break; - default: - return r; - } - - return VK_SUCCESS; -} - -pugl::Status -View::onEvent(const pugl::ExposeEvent&) -{ - const auto& vk = _app.vulkan.vk; - const auto& gpu = _app.gpu; - - // Acquire the next image, waiting and/or rebuilding if necessary - auto nextImageIndex = 0u; - if (beginFrame(_app, gpu.device, nextImageIndex)) { - return pugl::Status::unknownError; - } - - // Ready to go, update the data to the current time - update(_app, world().time()); - - // Submit the frame to the queue and present it - endFrame(vk, gpu, _app.renderer, nextImageIndex); - - ++_app.framesDrawn; - ++_app.renderer.sync.currentFrame; - _app.renderer.sync.currentFrame %= _app.renderer.sync.inFlight.size(); - - return pugl::Status::success; -} - -pugl::Status -View::onEvent(const pugl::LoopEnterEvent&) -{ - _app.resizing = true; - startTimer(resizeTimerId, - 1.0 / static_cast(getHint(pugl::ViewHint::refreshRate))); - - return pugl::Status::success; -} - -pugl::Status -View::onEvent(const pugl::TimerEvent&) -{ - return postRedisplay(); -} - -pugl::Status -View::onEvent(const pugl::LoopLeaveEvent&) -{ - stopTimer(resizeTimerId); - - // Trigger a swapchain recreation with the normal present mode - _app.renderer.swapchain.extent = {}; - _app.resizing = false; - - return pugl::Status::success; -} - -pugl::Status -View::onEvent(const pugl::KeyPressEvent& event) -{ - if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') { - _app.quit = true; - } - - return pugl::Status::success; -} - -pugl::Status -View::onEvent(const pugl::CloseEvent&) -{ - _app.quit = true; - - return pugl::Status::success; -} - -VkResult -VulkanContext::init(pugl::VulkanLoader& loader, const PuglTestOptions& opts) -{ - VkResult r = VK_SUCCESS; - - sk::VulkanInitApi initApi{}; - - // Load Vulkan API and set up the fundamentals - if ((r = initApi.init(loader.getInstanceProcAddrFunc())) || - (r = createInstance(initApi, opts, instance)) || - (r = vk.init(initApi, instance)) || - (r = getDebugReportCallback(vk, instance, opts.verbose, debugCallback))) { - return r; - } - - return VK_SUCCESS; -} - -int -run(const char* const programPath, - const PuglTestOptions opts, - const size_t numRects) -{ - PuglVulkanDemo app{programPath, opts, numRects}; - - VkResult r = VK_SUCCESS; - const auto width = static_cast(app.extent.width); - const auto height = static_cast(app.extent.height); - - // Realize window so we can set up Vulkan - app.world.setClassName("PuglVulkanDemo"); - app.view.setWindowTitle("Pugl Vulkan Demo"); - app.view.setAspectRatio(1, 1, 16, 9); - app.view.setDefaultSize(width, height); - app.view.setMinSize(width / 4, height / 4); - app.view.setMaxSize(width * 4, height * 4); - app.view.setBackend(pugl::vulkanBackend()); - app.view.setHint(pugl::ViewHint::resizable, opts.resizable); - const pugl::Status st = app.view.realize(); - if (st != pugl::Status::success) { - return logError("Failed to create window (%s)\n", pugl::strerror(st)); - } - - if (!app.loader) { - return logError("Failed to load Vulkan library\n"); - } - - // Load Vulkan for the view - if ((r = app.vulkan.init(app.loader, opts))) { - return logError("Failed to set up Vulkan API (%s)\n", sk::string(r)); - } - - const auto& vk = app.vulkan.vk; - - // Set up the graphics device - if ((r = app.gpu.init(app.loader, app.vulkan, app.view, opts))) { - return logError("Failed to set up device (%s)\n", sk::string(r)); - } - - logInfo("Present mode", sk::string(app.gpu.presentMode)); - logInfo("Resize present mode", sk::string(app.gpu.resizePresentMode)); - - // Set up the rectangle data we will render every frame - if ((r = app.rectData.init(vk, app.gpu, app.rects.size()))) { - return logError("Failed to allocate render data (%s)\n", sk::string(r)); - } - - // Load shader modules - if ((r = app.rectShaders.init(vk, app.gpu, app.programPath))) { - return logError("Failed to load shaders (%s)\n", sk::string(r)); - } - - if ((r = app.renderer.init(app.vulkan.vk, - app.gpu, - app.rectData, - app.rectShaders, - app.extent, - false))) { - return logError("Failed to create renderer (%s)\n", sk::string(r)); - } - - logInfo("Swapchain frames", - std::to_string(app.renderer.swapchain.imageViews.size())); - logInfo("Frames in flight", - std::to_string(app.renderer.sync.inFlight.size())); - - recordCommandBuffers(app.vulkan.vk, - app.renderer.swapchain, - app.renderer.renderPass, - app.renderer.rectPipeline, - app.rectData); - - const int refreshRate = app.view.getHint(pugl::ViewHint::refreshRate); - const double frameDuration = 1.0 / static_cast(refreshRate); - const double timeout = app.opts.sync ? frameDuration : 0.0; - - PuglFpsPrinter fpsPrinter = {app.world.time()}; - app.view.show(); - while (!app.quit) { - app.world.update(timeout); - puglPrintFps(app.world.cobj(), &fpsPrinter, &app.framesDrawn); - } - - if ((r = app.vulkan.vk.deviceWaitIdle(app.gpu.device))) { - return logError("Failed to wait for device idle (%s)\n", sk::string(r)); - } - - return 0; -} - -} // namespace - -int -main(int argc, char** argv) -{ - // Parse command line options - const char* const programPath = argv[0]; - const PuglTestOptions opts = puglParseTestOptions(&argc, &argv); - if (opts.help) { - puglPrintTestUsage(programPath, ""); - return 0; - } - - // Parse number of rectangles argument, if given - int64_t numRects = 1000; - if (argc >= 1) { - char* endptr = nullptr; - numRects = strtol(argv[0], &endptr, 10); - if (endptr != argv[0] + strlen(argv[0]) || numRects < 1) { - logError("Invalid number of rectangles: %s\n", argv[0]); - return 1; - } - } - - // Run application - return run(programPath, opts, static_cast(numRects)); -} diff --git a/examples/pugl_vulkan_demo.c b/examples/pugl_vulkan_demo.c index efbf339..801b74b 100644 --- a/examples/pugl_vulkan_demo.c +++ b/examples/pugl_vulkan_demo.c @@ -19,7 +19,7 @@ A simple example of drawing with Vulkan. For a more advanced demo that actually draws something interesting, see - pugl_vulkan_cxx_demo.cpp. + pugl_vulkan_cpp_demo.cpp. */ #include "demo_utils.h" diff --git a/meson.build b/meson.build index 02fae17..8d748e6 100644 --- a/meson.build +++ b/meson.build @@ -158,12 +158,12 @@ c_headers = [ c_header_files = files(c_headers) cpp_headers = [ - 'bindings/cxx/include/pugl/pugl.hpp', + 'bindings/cpp/include/pugl/pugl.hpp', - 'bindings/cxx/include/pugl/cairo.hpp', - 'bindings/cxx/include/pugl/gl.hpp', - 'bindings/cxx/include/pugl/stub.hpp', - 'bindings/cxx/include/pugl/vulkan.hpp', + 'bindings/cpp/include/pugl/cairo.hpp', + 'bindings/cpp/include/pugl/gl.hpp', + 'bindings/cpp/include/pugl/stub.hpp', + 'bindings/cpp/include/pugl/vulkan.hpp', ] cpp_header_files = files(cpp_headers) @@ -323,7 +323,7 @@ stub_backend_dep = declare_dependency(link_with: stub_backend) pkg.generate(stub_backend, name: 'Pugl Stub', filebase: 'pugl-stub-@0@'.format(major_version), - subdirs: [name], + subdirs: [versioned_name], version: meson.project_version(), description: 'Native window pugl graphics backend') @@ -348,7 +348,7 @@ if opengl_dep.found() pkg.generate(gl_backend, name: 'Pugl OpenGL', filebase: 'pugl-gl-@0@'.format(major_version), - subdirs: [name], + subdirs: [versioned_name], version: meson.project_version(), description: 'Pugl GUI library with OpenGL backend') endif @@ -376,7 +376,7 @@ if cairo_dep.found() pkg.generate(cairo_backend, name: 'Pugl Cairo', filebase: 'pugl-cairo-@0@'.format(major_version), - subdirs: [name], + subdirs: [versioned_name], version: meson.project_version(), description: 'Pugl GUI library with Cairo backend') endif @@ -414,13 +414,19 @@ if vulkan_dep.found() pkg.generate(vulkan_backend, name: 'Pugl Vulkan', filebase: 'pugl-vulkan-@0@'.format(major_version), - subdirs: [name], + subdirs: [versioned_name], version: meson.project_version(), description: 'Pugl GUI library with Vulkan backend') endif install_headers(c_headers, subdir: versioned_name / 'pugl') -install_headers(cpp_headers, subdir: 'puglxx' + version_suffix) +install_headers(cpp_headers, subdir: 'puglpp' + version_suffix / 'pugl') + +pkg.generate(name: 'Pugl++', + filebase: 'puglpp-@0@'.format(major_version), + subdirs: ['puglpp-@0@'.format(major_version)], + version: meson.project_version(), + description: 'Pugl GUI library C++ bindings') if not get_option('docs').disabled() subdir('doc') -- cgit v1.2.1