From 503ba93a4ebf316ab01b468808e4cda2da8b863e Mon Sep 17 00:00:00 2001 From: David Robillard Date: Thu, 26 Nov 2020 16:07:41 +0100 Subject: Generate documentation with Sphinx --- doc/cpp/Doxyfile | 40 +++++ doc/cpp/c-reference.rst | 20 +++ doc/cpp/cpp-reference.rst | 18 ++ doc/cpp/index.rst | 7 + doc/cpp/overview.rst | 423 ++++++++++++++++++++++++++++++++++++++++++++++ doc/cpp/wscript | 44 +++++ 6 files changed, 552 insertions(+) create mode 100644 doc/cpp/Doxyfile create mode 100644 doc/cpp/c-reference.rst create mode 100644 doc/cpp/cpp-reference.rst create mode 100644 doc/cpp/index.rst create mode 100644 doc/cpp/overview.rst create mode 100644 doc/cpp/wscript (limited to 'doc/cpp') diff --git a/doc/cpp/Doxyfile b/doc/cpp/Doxyfile new file mode 100644 index 0000000..2bb0f9f --- /dev/null +++ b/doc/cpp/Doxyfile @@ -0,0 +1,40 @@ +PROJECT_NAME = Pugl +PROJECT_BRIEF = "A minimal portable API for embeddable GUIs" + +QUIET = YES +WARN_AS_ERROR = NO +WARN_IF_UNDOCUMENTED = NO +WARN_NO_PARAMDOC = NO + +JAVADOC_AUTOBRIEF = YES + +CASE_SENSE_NAMES = YES +EXCLUDE_SYMBOLS = pugl::detail +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_PRIVATE = NO +HIDE_IN_BODY_DOCS = YES +HIDE_UNDOC_CLASSES = YES +HIDE_UNDOC_MEMBERS = YES +REFERENCES_LINK_SOURCE = NO + +GENERATE_HTML = NO +GENERATE_LATEX = NO +GENERATE_XML = YES +XML_PROGRAMLISTING = NO +SHOW_FILES = NO + +MACRO_EXPANSION = YES +PREDEFINED = PUGL_API PUGL_DISABLE_DEPRECATED PUGL_CONST_FUNC= + +INPUT = ../../include/pugl/cairo.h \ + ../../include/pugl/gl.h \ + ../../include/pugl/pugl.h \ + ../../include/pugl/stub.h \ + ../../include/pugl/vulkan.h \ + ../../bindings/cxx/include/pugl/cairo.hpp \ + ../../bindings/cxx/include/pugl/gl.hpp \ + ../../bindings/cxx/include/pugl/pugl.hpp \ + ../../bindings/cxx/include/pugl/stub.hpp \ + ../../bindings/cxx/include/pugl/vulkan.hpp + +OUTPUT_DIRECTORY = . diff --git a/doc/cpp/c-reference.rst b/doc/cpp/c-reference.rst new file mode 100644 index 0000000..546e4d3 --- /dev/null +++ b/doc/cpp/c-reference.rst @@ -0,0 +1,20 @@ +############### +C API Reference +############### + +This section contains the generated documentation for all symbols in the public +C API. +It is included here because some C++ wrapper definitions refer to the underlying C symbols, +but direct use of the C API should not be necessary in C++ applications. + +.. toctree:: + + api/status + api/world + api/view + api/events + + api/cairo + api/gl + api/stub + api/vulkan diff --git a/doc/cpp/cpp-reference.rst b/doc/cpp/cpp-reference.rst new file mode 100644 index 0000000..96c523c --- /dev/null +++ b/doc/cpp/cpp-reference.rst @@ -0,0 +1,18 @@ +################# +C++ API Reference +################# + +This section contains the generated documentation for all symbols in the public +C++ API. + +.. toctree:: + + api/statusxx + api/worldxx + api/viewxx + api/eventsxx + + api/cairoxx + api/glxx + api/stubxx + api/vulkanxx diff --git a/doc/cpp/index.rst b/doc/cpp/index.rst new file mode 100644 index 0000000..c3d330e --- /dev/null +++ b/doc/cpp/index.rst @@ -0,0 +1,7 @@ +.. toctree:: + + pugl + deployment + overview + cpp-reference + c-reference diff --git a/doc/cpp/overview.rst b/doc/cpp/overview.rst new file mode 100644 index 0000000..5fffe37 --- /dev/null +++ b/doc/cpp/overview.rst @@ -0,0 +1,423 @@ +.. default-domain:: cpp +.. highlight:: cpp +.. namespace:: pugl + +Pugl is a C library, +but the bindings documented here provide a more idiomatic and type-safe API for C++. +If you would rather use C, +refer instead to the `C API documentation <../../c/singlehtml/index.html>`_. + +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. + +The core API (excluding backend-specific components) is declared in ``pugl.hpp``: + +.. code-block:: cpp + + #include + +The API revolves around two main objects: the `world` and the `view`. +An application creates a world to manage top-level state, +then creates one or more views to display. + +Creating a World +================ + +The world is the top-level object which represents an instance of Pugl. +It handles the connection to the window system, +and manages views and the event loop. + +An application typically has a single world, +which is constructed once on startup and used to drive the main event loop. + +Construction +------------ + +A world must be created before any views, and it must outlive all of its views. +The world constructor requires an argument to specify the application type: + +.. code-block:: cpp + + pugl::World world{pugl::WorldType::program}; + +For a plugin, specify :enumerator:`WorldType::module` instead. +In some cases, it is necessary to pass additional flags. +For example, Vulkan requires thread support: + +.. code-block:: cpp + + pugl::World world{pugl::WorldType::program, pugl::WorldFlag::threads}; + +It is a good idea to set a class name for your project with :func:`World::setClassName`. +This allows the window system to distinguish different applications and, +for example, users to set up rules to manage their windows nicely: + +.. code-block:: cpp + + world.setClassName("MyAwesomeProject"); + +Creating a View +=============== + +A `view` is a drawable region that receives events. +You may think of it as a window, +though it may be embedded and not represent a top-level system window. [#f1]_ + +Pugl communicates with views by dispatching events. +For flexibility, the event handler can be a different object than the view. +This allows using :class:`View` along with a separate event handler class. +Alternatively, a view class can inherit from :class:`View` and set itself as its event handler, +for a more object-oriented style. + +This documentation will use the latter approach, +so we will define a class for our view that contains everything needed: + +.. code-block:: cpp + + class MyView : public pugl::View + { + public: + explicit MyView(pugl::World& world) + : pugl::View{world} + { + setEventHandler(*this); + } + + pugl::Status onEvent(const pugl::ConfigureEvent& event) noexcept; + pugl::Status onEvent(const pugl::ExposeEvent& event) noexcept; + + // With other handlers here as needed... + + // Fallback handler for all other events + template + pugl::Status onEvent(const pugl::Event&) noexcept + { + return pugl::Status::success; + } + + private: + // Some data... + }; + +Pugl will call an ``onEvent`` method of the event handler (the view in this case) for every event. + +Note that Pugl uses a static dispatching mechanism rather than virtual functions to minimize overhead. +It is therefore necessary for the final class to define a handler for every event type. +A terse way to do this without writing every implementation is to define a fallback handler as a template, +as in the example above. +Alternatively, you can define an explicit handler for each event that simply returns :enumerator:`Status::success`. +This way, it will be a compile error if any event is not explicitly handled. + +Configuring the Frame +--------------------- + +Before display, +the necessary :doc:`frame ` and :doc:`window ` attributes should be set. +These allow the window system (or plugin host) to arrange the view properly. + +Derived classes can configure themselves during construction, +but we assume here that configuration is being done outside the view. +For example: + +.. code-block:: cpp + + const double defaultWidth = 1920.0; + const double defaultHeight = 1080.0; + + view.setWindowTitle("My Window"); + view.setDefaultSize(defaultWidth, defaultHeight); + view.setMinSize(defaultWidth / 4.0, defaultHeight / 4.0); + view.setAspectRatio(1, 1, 16, 9); + +There are also several :type:`hints ` for basic attributes that can be set: + +.. code-block:: cpp + + view.setHint(pugl::ViewHint::resizable, true); + view.setHint(pugl::ViewHint::ignoreKeyRepeat, true); + +Embedding +--------- + +To embed the view in another window, +you will need to somehow get the :type:`native view handle ` for the parent, +then set it with :func:`View::setParentWindow`. +If the parent is a Pugl view, +the native handle can be accessed with :func:`View::nativeWindow`. +For example: + +.. code-block:: cpp + + view.setParentWindow(view, parent.getNativeWindow()); + +Setting a Backend +----------------- + +Before being realized, the view must have a backend set with :func:`View::setBackend`. + +The backend manages the graphics API that will be used for drawing. +Pugl includes backends and supporting API for +:doc:`Cairo `, :doc:`OpenGL `, and :doc:`Vulkan `. + +Using Cairo +^^^^^^^^^^^ + +Cairo-specific API is declared in the ``cairo.hpp`` header: + +.. code-block:: cpp + + #include + +The Cairo backend is provided by :func:`cairoBackend()`: + +.. code-block:: cpp + + view.setBackend(pugl::cairoBackend()); + +No additional configuration is required for Cairo. +To draw when handling an expose event, +the `Cairo context `_ can be accessed with :func:`View::context`: + +.. code-block:: cpp + + cairo_t* cr = static_cast(view.context()); + +Using OpenGL +^^^^^^^^^^^^ + +OpenGL-specific API is declared in the ``gl.hpp`` header: + +.. code-block:: cpp + + #include + +The OpenGL backend is provided by :func:`glBackend()`: + +.. code-block:: cpp + + view.setBackend(pugl::glBackend()); + +Some hints must also be set so that the context can be set up correctly. +For example, to use OpenGL 3.3 Core Profile: + +.. code-block:: cpp + + view.setHint(pugl::ViewHint::useCompatProfile, false); + view.setHint(pugl::ViewHint::contextVersionMajor, 3); + view.setHint(pugl::ViewHint::contextVersionMinor, 3); + +If you need to perform some setup using the OpenGL API, +there are two ways to do so. + +The OpenGL context is active when +:type:`CreateEvent` and +:type:`DestroyEvent` +events are dispatched, +so things like creating and destroying shaders and textures can be done then. + +Alternatively, if it is cumbersome to set up and tear down OpenGL in the event handler, +:func:`enterContext` and :func:`leaveContext` can be used to manually activate the OpenGL context during application setup. +Note, however, that unlike many other APIs, these functions must not be used for drawing. +It is only valid to use the OpenGL API for configuration in a manually entered context, +rendering will not work. +For example: + +.. code-block:: cpp + + pugl::enterContext(view); + myApp.setupOpenGL(); + pugl::leaveContext(view); + + while (!myApp.quit()) { + world.update(0.0); + } + + pugl::enterContext(view); + myApp.teardownOpenGL(); + pugl::leaveContext(view); + +Using Vulkan +^^^^^^^^^^^^ + +Vulkan-specific API is declared in the ``vulkan.hpp`` header. +This header includes Vulkan headers, +so if you are dynamically loading Vulkan at runtime, +you should define ``VK_NO_PROTOTYPES`` before including it. + +.. code-block:: cpp + + #define VK_NO_PROTOTYPES + + #include + +The Vulkan backend is provided by :func:`vulkanBackend()`: + +.. code-block:: cpp + + view.setBackend(pugl::vulkanBackend()); + +Unlike OpenGL, almost all Vulkan configuration is done using the Vulkan API directly. +Pugl only provides a portable mechanism to load the Vulkan library and get the functions used to load the rest of the Vulkan API. + +Loading Vulkan +^^^^^^^^^^^^^^ + +For maximum compatibility, +it is best to not link to Vulkan at compile-time, +but instead load the Vulkan API at run-time. +To do so, first create a :class:`VulkanLoader`: + +.. code-block:: cpp + + pugl::VulkanLoader loader{world}; + +The loader manages the dynamically loaded Vulkan library, +so it must be kept alive for as long as the application is using Vulkan. +You can get the function used to load Vulkan functions with :func:`VulkanLoader::getInstanceProcAddrFunc`: + +.. code-block:: cpp + + auto vkGetInstanceProcAddr = loader.getInstanceProcAddrFunc(); + +It is best to use this function to load everything at run time, +rather than link to the Vulkan library at run time. +You can, for example, pass this to get the ``vkCreateInstance`` function using this, +then use that to create your Vulkan instance. +In practice, you will want to use some loader or wrapper API since there are many Vulkan functions. + +It is not necessary to use :class:`VulkanLoader`, +you can, for example, use the ``DynamicLoader`` from ``vulkan.hpp`` in the Vulkan SDK instead. + +The details of using Vulkan are far beyond the scope of this documentation, +but Pugl provides a portable function, :func:`createSurface`, +to get the Vulkan surface for a view. +Assuming you have somehow created your ``VkInstance``, +you can get the surface for a view using :func:`createSurface`: + +.. code-block:: cpp + + VkSurfaceKHR* surface = nullptr; + puglCreateSurface(loader.getDeviceProcAddrFunc(), + view, + vulkanInstance, + nullptr, + &surface); + +Pugl does not provide API that uses ``vulkan.hpp`` to avoid the onerous dependency, +but if you are using it with exceptions and unique handles, +it is straightforward to wrap the surface handle yourself. + +Showing the View +---------------- + +Once the view is configured, it can be "realized" with :func:`View::realize`. +This creates a "real" system view, for example: + +.. code-block:: cpp + + pugl::Status status = view.realize(); + if (status != pugl::Status::success) { + std::cerr << "Error realizing view: " << pugl::strerror(status) << "\n"; + } + +Note that realizing a view can fail for many reasons, +so the return code should always be checked. +This is generally the case for any function that interacts with the window system. +Most functions also return a :enum:`Status`, +but these checks are omitted for brevity in the rest of this documentation. + +A realized view is not initially visible, +but can be shown with :func:`View::show`: + +.. code-block:: cpp + + view.show(); + +To create an initially visible view, +it is also possible to simply call :func:`View::show()` right away. +The view will be automatically realized if necessary. + +Handling Events +=============== + +Events are sent to a view when it has received user input, +must be drawn, or in other situations that may need to be handled such as resizing. + +Events are sent to the ``onEvent`` method that takes the matching event type. +The application must handle at least :type:`ConfigureEvent` +and :type:`ExposeEvent` to draw anything, +but there are many other :type:`event types `. + +For example, basic event handling for our above class might look something like: + +.. code-block:: cpp + + pugl::Status + MyView::onEvent(const pugl::ConfigureEvent& event) noexcept + { + return resize(event.width, event.height); + } + + pugl::Status + MyView::onEvent(const pugl::ExposeEvent& event) noexcept + { + return drawMyAwesomeInterface(event.x, event.y, event.width, event.height); + } + +Drawing +------- + +Note that Pugl uses a different drawing model than many libraries, +particularly those designed for game-style main loops like `SDL `_ and `GLFW `_. + +In that style of code, drawing is performed imperatively in the main loop, +but with Pugl, the application must draw only while handling an expose event. +This is because Pugl supports event-driven applications that only draw the damaged region when necessary, +and handles exposure internally to provide optimized and consistent behavior across platforms. + +Driving the Event Loop +====================== + +Pugl does not contain any threads or other event loop "magic". +For flexibility, the event loop is driven manually by repeatedly calling :func:`World::update`, +which processes events from the window system and dispatches them to views when necessary. + +The exact use of :func:`World::update` depends on the application. +Plugins typically call it with a ``timeout`` of 0 in a callback driven by the host. +This avoids blocking the main loop, +since other plugins and the host itself need to run as well. + +A program can use whatever timeout is appropriate: +event-driven applications may wait forever by using a ``timeout`` of -1, +while those that draw continuously may use a significant fraction of the frame period +(with enough time left over to render). + +Redrawing +--------- + +Occasional redrawing can be requested by calling :func:`View::postRedisplay` or :func:`View::postRedisplayRect`. +After these are called, +a :type:`ExposeEvent` will be dispatched on the next call to :func:`World::update`. +Note, however, that this will not wake up a blocked :func:`World::update` call on MacOS +(which does not handle drawing via events). + +For continuous redrawing, +call :func:`View::postRedisplay` while handling a :type:`UpdateEvent`. +This event is sent just before views are redrawn, +so it can be used as a hook to expand the update region right before the view is exposed. +Anything else that needs to be done every frame can be handled similarly. + +.. _pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/ + +.. rubric:: Footnotes + +.. [#f1] MacOS has a strong distinction between + `views `_, + which may be nested, and + `windows `_, + which may not. + On Windows and X11, everything is a nestable window, + but top-level windows are configured differently. diff --git a/doc/cpp/wscript b/doc/cpp/wscript new file mode 100644 index 0000000..a7aefc1 --- /dev/null +++ b/doc/cpp/wscript @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +def build(bld): + dox_to_sphinx = bld.path.find_node("../../scripts/dox_to_sphinx.py") + index_xml = bld.path.get_bld().make_node("xml/index.xml") + + files = [ + ("../../resources/pugl.svg", "sphinx/_static/pugl.svg"), + ("../_static/custom.css", "sphinx/_static/custom.css"), + ("../_templates/about.html", "sphinx/_templates/about.html"), + ("../deployment.rst", "sphinx/deployment.rst"), + ("../pugl.rst", "sphinx/pugl.rst"), + ("c-reference.rst", "sphinx/c-reference.rst"), + ("cpp-reference.rst", "sphinx/cpp-reference.rst"), + ("index.rst", "sphinx/index.rst"), + ("overview.rst", "sphinx/overview.rst"), + ] + + # Run Doxygen to generate XML documentation + bld(features="doxygen", doxyfile="Doxyfile") + + # Substitute variables to make Sphinx configuration file + bld(features="subst", + source="../conf.py.in", + target="sphinx/conf.py", + PUGL_VERSION=bld.env.PUGL_VERSION) + + # Copy static documentation files to Sphinx build directory + for f in files: + bld(features="subst", is_copy=True, source=f[0], target=f[1]) + + # Generate Sphinx markup from Doxygen XML + bld.add_group() + bld(rule="${PYTHON} " + dox_to_sphinx.abspath() + " -l cpp -f ${SRC} ${TGT}", + source=index_xml, + target="sphinx/api/") + + # Run Sphinx to generate HTML documentation + doc_dir = bld.env.DOCDIR + "/pugl-%s/" % bld.env.PUGL_MAJOR_VERSION + bld(features="sphinx", + sphinx_source=bld.path.get_bld().make_node("sphinx"), + sphinx_output_format="singlehtml", + sphinx_options=["-E", "-q"], + install_path=doc_dir + "cpp/singlehtml/") -- cgit v1.2.1