#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_xdg_shell The xdg_shell protocol
* @section page_ifaces_xdg_shell Interfaces
* - @subpage page_iface_xdg_wm_base - create desktop-style surfaces
* - @subpage page_iface_xdg_positioner - child surface positioner
* - @subpage page_iface_xdg_surface - desktop user interface surface base interface
* - @subpage page_iface_xdg_toplevel - toplevel surface
* - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus
* @section page_copyright_xdg_shell Copyright
*
*
* Copyright © 2008-2013 Kristian Høgsberg
* Copyright © 2013 Rafael Antognolli
* Copyright © 2013 Jasper St. Pierre
* Copyright © 2010-2013 Intel Corporation
* Copyright © 2015-2017 Samsung Electronics Co., Ltd
* Copyright © 2015-2017 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
struct wl_output;
struct wl_seat;
struct wl_surface;
struct xdg_popup;
struct xdg_positioner;
struct xdg_surface;
struct xdg_toplevel;
struct xdg_wm_base;
#ifndef XDG_WM_BASE_INTERFACE
#define XDG_WM_BASE_INTERFACE
/**
* @page page_iface_xdg_wm_base xdg_wm_base
* @section page_iface_xdg_wm_base_desc Description
*
* The xdg_wm_base interface is exposed as a global object enabling clients
* to turn their wl_surfaces into windows in a desktop environment. It
* defines the basic functionality needed for clients and the compositor to
* create windows that can be dragged, resized, maximized, etc, as well as
* creating transient windows such as popup menus.
* @section page_iface_xdg_wm_base_api API
* See @ref iface_xdg_wm_base.
*/
/**
* @defgroup iface_xdg_wm_base The xdg_wm_base interface
*
* The xdg_wm_base interface is exposed as a global object enabling clients
* to turn their wl_surfaces into windows in a desktop environment. It
* defines the basic functionality needed for clients and the compositor to
* create windows that can be dragged, resized, maximized, etc, as well as
* creating transient windows such as popup menus.
*/
extern const struct wl_interface xdg_wm_base_interface;
#endif
#ifndef XDG_POSITIONER_INTERFACE
#define XDG_POSITIONER_INTERFACE
/**
* @page page_iface_xdg_positioner xdg_positioner
* @section page_iface_xdg_positioner_desc Description
*
* The xdg_positioner provides a collection of rules for the placement of a
* child surface relative to a parent surface. Rules can be defined to ensure
* the child surface remains within the visible area's borders, and to
* specify how the child surface changes its position, such as sliding along
* an axis, or flipping around a rectangle. These positioner-created rules are
* constrained by the requirement that a child surface must intersect with or
* be at least partially adjacent to its parent surface.
*
* See the various requests for details about possible rules.
*
* At the time of the request, the compositor makes a copy of the rules
* specified by the xdg_positioner. Thus, after the request is complete the
* xdg_positioner object can be destroyed or reused; further changes to the
* object will have no effect on previous usages.
*
* For an xdg_positioner object to be considered complete, it must have a
* non-zero size set by set_size, and a non-zero anchor rectangle set by
* set_anchor_rect. Passing an incomplete xdg_positioner object when
* positioning a surface raises an error.
* @section page_iface_xdg_positioner_api API
* See @ref iface_xdg_positioner.
*/
/**
* @defgroup iface_xdg_positioner The xdg_positioner interface
*
* The xdg_positioner provides a collection of rules for the placement of a
* child surface relative to a parent surface. Rules can be defined to ensure
* the child surface remains within the visible area's borders, and to
* specify how the child surface changes its position, such as sliding along
* an axis, or flipping around a rectangle. These positioner-created rules are
* constrained by the requirement that a child surface must intersect with or
* be at least partially adjacent to its parent surface.
*
* See the various requests for details about possible rules.
*
* At the time of the request, the compositor makes a copy of the rules
* specified by the xdg_positioner. Thus, after the request is complete the
* xdg_positioner object can be destroyed or reused; further changes to the
* object will have no effect on previous usages.
*
* For an xdg_positioner object to be considered complete, it must have a
* non-zero size set by set_size, and a non-zero anchor rectangle set by
* set_anchor_rect. Passing an incomplete xdg_positioner object when
* positioning a surface raises an error.
*/
extern const struct wl_interface xdg_positioner_interface;
#endif
#ifndef XDG_SURFACE_INTERFACE
#define XDG_SURFACE_INTERFACE
/**
* @page page_iface_xdg_surface xdg_surface
* @section page_iface_xdg_surface_desc Description
*
* An interface that may be implemented by a wl_surface, for
* implementations that provide a desktop-style user interface.
*
* It provides a base set of functionality required to construct user
* interface elements requiring management by the compositor, such as
* toplevel windows, menus, etc. The types of functionality are split into
* xdg_surface roles.
*
* Creating an xdg_surface does not set the role for a wl_surface. In order
* to map an xdg_surface, the client must create a role-specific object
* using, e.g., get_toplevel, get_popup. The wl_surface for any given
* xdg_surface can have at most one role, and may not be assigned any role
* not based on xdg_surface.
*
* A role must be assigned before any other requests are made to the
* xdg_surface object.
*
* The client must call wl_surface.commit on the corresponding wl_surface
* for the xdg_surface state to take effect.
*
* Creating an xdg_surface from a wl_surface which has a buffer attached or
* committed is a client error, and any attempts by a client to attach or
* manipulate a buffer prior to the first xdg_surface.configure call must
* also be treated as errors.
*
* After creating a role-specific object and setting it up, the client must
* perform an initial commit without any buffer attached. The compositor
* will reply with an xdg_surface.configure event. The client must
* acknowledge it and is then allowed to attach a buffer to map the surface.
*
* Mapping an xdg_surface-based role surface is defined as making it
* possible for the surface to be shown by the compositor. Note that
* a mapped surface is not guaranteed to be visible once it is mapped.
*
* For an xdg_surface to be mapped by the compositor, the following
* conditions must be met:
* (1) the client has assigned an xdg_surface-based role to the surface
* (2) the client has set and committed the xdg_surface state and the
* role-dependent state to the surface
* (3) the client has committed a buffer to the surface
*
* A newly-unmapped surface is considered to have met condition (1) out
* of the 3 required conditions for mapping a surface if its role surface
* has not been destroyed.
* @section page_iface_xdg_surface_api API
* See @ref iface_xdg_surface.
*/
/**
* @defgroup iface_xdg_surface The xdg_surface interface
*
* An interface that may be implemented by a wl_surface, for
* implementations that provide a desktop-style user interface.
*
* It provides a base set of functionality required to construct user
* interface elements requiring management by the compositor, such as
* toplevel windows, menus, etc. The types of functionality are split into
* xdg_surface roles.
*
* Creating an xdg_surface does not set the role for a wl_surface. In order
* to map an xdg_surface, the client must create a role-specific object
* using, e.g., get_toplevel, get_popup. The wl_surface for any given
* xdg_surface can have at most one role, and may not be assigned any role
* not based on xdg_surface.
*
* A role must be assigned before any other requests are made to the
* xdg_surface object.
*
* The client must call wl_surface.commit on the corresponding wl_surface
* for the xdg_surface state to take effect.
*
* Creating an xdg_surface from a wl_surface which has a buffer attached or
* committed is a client error, and any attempts by a client to attach or
* manipulate a buffer prior to the first xdg_surface.configure call must
* also be treated as errors.
*
* After creating a role-specific object and setting it up, the client must
* perform an initial commit without any buffer attached. The compositor
* will reply with an xdg_surface.configure event. The client must
* acknowledge it and is then allowed to attach a buffer to map the surface.
*
* Mapping an xdg_surface-based role surface is defined as making it
* possible for the surface to be shown by the compositor. Note that
* a mapped surface is not guaranteed to be visible once it is mapped.
*
* For an xdg_surface to be mapped by the compositor, the following
* conditions must be met:
* (1) the client has assigned an xdg_surface-based role to the surface
* (2) the client has set and committed the xdg_surface state and the
* role-dependent state to the surface
* (3) the client has committed a buffer to the surface
*
* A newly-unmapped surface is considered to have met condition (1) out
* of the 3 required conditions for mapping a surface if its role surface
* has not been destroyed.
*/
extern const struct wl_interface xdg_surface_interface;
#endif
#ifndef XDG_TOPLEVEL_INTERFACE
#define XDG_TOPLEVEL_INTERFACE
/**
* @page page_iface_xdg_toplevel xdg_toplevel
* @section page_iface_xdg_toplevel_desc Description
*
* This interface defines an xdg_surface role which allows a surface to,
* among other things, set window-like properties such as maximize,
* fullscreen, and minimize, set application-specific metadata like title and
* id, and well as trigger user interactive operations such as interactive
* resize and move.
*
* Unmapping an xdg_toplevel means that the surface cannot be shown
* by the compositor until it is explicitly mapped again.
* All active operations (e.g., move, resize) are canceled and all
* attributes (e.g. title, state, stacking, ...) are discarded for
* an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to
* the state it had right after xdg_surface.get_toplevel. The client
* can re-map the toplevel by perfoming a commit without any buffer
* attached, waiting for a configure event and handling it as usual (see
* xdg_surface description).
*
* Attaching a null buffer to a toplevel unmaps the surface.
* @section page_iface_xdg_toplevel_api API
* See @ref iface_xdg_toplevel.
*/
/**
* @defgroup iface_xdg_toplevel The xdg_toplevel interface
*
* This interface defines an xdg_surface role which allows a surface to,
* among other things, set window-like properties such as maximize,
* fullscreen, and minimize, set application-specific metadata like title and
* id, and well as trigger user interactive operations such as interactive
* resize and move.
*
* Unmapping an xdg_toplevel means that the surface cannot be shown
* by the compositor until it is explicitly mapped again.
* All active operations (e.g., move, resize) are canceled and all
* attributes (e.g. title, state, stacking, ...) are discarded for
* an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to
* the state it had right after xdg_surface.get_toplevel. The client
* can re-map the toplevel by perfoming a commit without any buffer
* attached, waiting for a configure event and handling it as usual (see
* xdg_surface description).
*
* Attaching a null buffer to a toplevel unmaps the surface.
*/
extern const struct wl_interface xdg_toplevel_interface;
#endif
#ifndef XDG_POPUP_INTERFACE
#define XDG_POPUP_INTERFACE
/**
* @page page_iface_xdg_popup xdg_popup
* @section page_iface_xdg_popup_desc Description
*
* A popup surface is a short-lived, temporary surface. It can be used to
* implement for example menus, popovers, tooltips and other similar user
* interface concepts.
*
* A popup can be made to take an explicit grab. See xdg_popup.grab for
* details.
*
* When the popup is dismissed, a popup_done event will be sent out, and at
* the same time the surface will be unmapped. See the xdg_popup.popup_done
* event for details.
*
* Explicitly destroying the xdg_popup object will also dismiss the popup and
* unmap the surface. Clients that want to dismiss the popup when another
* surface of their own is clicked should dismiss the popup using the destroy
* request.
*
* A newly created xdg_popup will be stacked on top of all previously created
* xdg_popup surfaces associated with the same xdg_toplevel.
*
* The parent of an xdg_popup must be mapped (see the xdg_surface
* description) before the xdg_popup itself.
*
* The client must call wl_surface.commit on the corresponding wl_surface
* for the xdg_popup state to take effect.
* @section page_iface_xdg_popup_api API
* See @ref iface_xdg_popup.
*/
/**
* @defgroup iface_xdg_popup The xdg_popup interface
*
* A popup surface is a short-lived, temporary surface. It can be used to
* implement for example menus, popovers, tooltips and other similar user
* interface concepts.
*
* A popup can be made to take an explicit grab. See xdg_popup.grab for
* details.
*
* When the popup is dismissed, a popup_done event will be sent out, and at
* the same time the surface will be unmapped. See the xdg_popup.popup_done
* event for details.
*
* Explicitly destroying the xdg_popup object will also dismiss the popup and
* unmap the surface. Clients that want to dismiss the popup when another
* surface of their own is clicked should dismiss the popup using the destroy
* request.
*
* A newly created xdg_popup will be stacked on top of all previously created
* xdg_popup surfaces associated with the same xdg_toplevel.
*
* The parent of an xdg_popup must be mapped (see the xdg_surface
* description) before the xdg_popup itself.
*
* The client must call wl_surface.commit on the corresponding wl_surface
* for the xdg_popup state to take effect.
*/
extern const struct wl_interface xdg_popup_interface;
#endif
#ifndef XDG_WM_BASE_ERROR_ENUM
#define XDG_WM_BASE_ERROR_ENUM
enum xdg_wm_base_error {
/**
* given wl_surface has another role
*/
XDG_WM_BASE_ERROR_ROLE = 0,
/**
* xdg_wm_base was destroyed before children
*/
XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1,
/**
* the client tried to map or destroy a non-topmost popup
*/
XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2,
/**
* the client specified an invalid popup parent surface
*/
XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3,
/**
* the client provided an invalid surface state
*/
XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4,
/**
* the client provided an invalid positioner
*/
XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5,
};
#endif /* XDG_WM_BASE_ERROR_ENUM */
/**
* @ingroup iface_xdg_wm_base
* @struct xdg_wm_base_listener
*/
struct xdg_wm_base_listener {
/**
* check if the client is alive
*
* The ping event asks the client if it's still alive. Pass the
* serial specified in the event back to the compositor by sending
* a "pong" request back with the specified serial. See
* xdg_wm_base.pong.
*
* Compositors can use this to determine if the client is still
* alive. It's unspecified what will happen if the client doesn't
* respond to the ping request, or in what timeframe. Clients
* should try to respond in a reasonable amount of time.
*
* A compositor is free to ping in any way it wants, but a client
* must always respond to any xdg_wm_base object it created.
* @param serial pass this to the pong request
*/
void (*ping)(void *data,
struct xdg_wm_base *xdg_wm_base,
uint32_t serial);
};
/**
* @ingroup iface_xdg_wm_base
*/
static inline int
xdg_wm_base_add_listener(struct xdg_wm_base *xdg_wm_base,
const struct xdg_wm_base_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) xdg_wm_base,
(void (**)(void)) listener, data);
}
#define XDG_WM_BASE_DESTROY 0
#define XDG_WM_BASE_CREATE_POSITIONER 1
#define XDG_WM_BASE_GET_XDG_SURFACE 2
#define XDG_WM_BASE_PONG 3
/**
* @ingroup iface_xdg_wm_base
*/
#define XDG_WM_BASE_PING_SINCE_VERSION 1
/**
* @ingroup iface_xdg_wm_base
*/
#define XDG_WM_BASE_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_xdg_wm_base
*/
#define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1
/**
* @ingroup iface_xdg_wm_base
*/
#define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_wm_base
*/
#define XDG_WM_BASE_PONG_SINCE_VERSION 1
/** @ingroup iface_xdg_wm_base */
static inline void
xdg_wm_base_set_user_data(struct xdg_wm_base *xdg_wm_base, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) xdg_wm_base, user_data);
}
/** @ingroup iface_xdg_wm_base */
static inline void *
xdg_wm_base_get_user_data(struct xdg_wm_base *xdg_wm_base)
{
return wl_proxy_get_user_data((struct wl_proxy *) xdg_wm_base);
}
static inline uint32_t
xdg_wm_base_get_version(struct xdg_wm_base *xdg_wm_base)
{
return wl_proxy_get_version((struct wl_proxy *) xdg_wm_base);
}
/**
* @ingroup iface_xdg_wm_base
*
* Destroy this xdg_wm_base object.
*
* Destroying a bound xdg_wm_base object while there are surfaces
* still alive created by this xdg_wm_base object instance is illegal
* and will result in a protocol error.
*/
static inline void
xdg_wm_base_destroy(struct xdg_wm_base *xdg_wm_base)
{
wl_proxy_marshal((struct wl_proxy *) xdg_wm_base,
XDG_WM_BASE_DESTROY);
wl_proxy_destroy((struct wl_proxy *) xdg_wm_base);
}
/**
* @ingroup iface_xdg_wm_base
*
* Create a positioner object. A positioner object is used to position
* surfaces relative to some parent surface. See the interface description
* and xdg_surface.get_popup for details.
*/
static inline struct xdg_positioner *
xdg_wm_base_create_positioner(struct xdg_wm_base *xdg_wm_base)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_wm_base,
XDG_WM_BASE_CREATE_POSITIONER, &xdg_positioner_interface, NULL);
return (struct xdg_positioner *) id;
}
/**
* @ingroup iface_xdg_wm_base
*
* This creates an xdg_surface for the given surface. While xdg_surface
* itself is not a role, the corresponding surface may only be assigned
* a role extending xdg_surface, such as xdg_toplevel or xdg_popup.
*
* This creates an xdg_surface for the given surface. An xdg_surface is
* used as basis to define a role to a given surface, such as xdg_toplevel
* or xdg_popup. It also manages functionality shared between xdg_surface
* based surface roles.
*
* See the documentation of xdg_surface for more details about what an
* xdg_surface is and how it is used.
*/
static inline struct xdg_surface *
xdg_wm_base_get_xdg_surface(struct xdg_wm_base *xdg_wm_base, struct wl_surface *surface)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_wm_base,
XDG_WM_BASE_GET_XDG_SURFACE, &xdg_surface_interface, NULL, surface);
return (struct xdg_surface *) id;
}
/**
* @ingroup iface_xdg_wm_base
*
* A client must respond to a ping event with a pong request or
* the client may be deemed unresponsive. See xdg_wm_base.ping.
*/
static inline void
xdg_wm_base_pong(struct xdg_wm_base *xdg_wm_base, uint32_t serial)
{
wl_proxy_marshal((struct wl_proxy *) xdg_wm_base,
XDG_WM_BASE_PONG, serial);
}
#ifndef XDG_POSITIONER_ERROR_ENUM
#define XDG_POSITIONER_ERROR_ENUM
enum xdg_positioner_error {
/**
* invalid input provided
*/
XDG_POSITIONER_ERROR_INVALID_INPUT = 0,
};
#endif /* XDG_POSITIONER_ERROR_ENUM */
#ifndef XDG_POSITIONER_ANCHOR_ENUM
#define XDG_POSITIONER_ANCHOR_ENUM
enum xdg_positioner_anchor {
XDG_POSITIONER_ANCHOR_NONE = 0,
XDG_POSITIONER_ANCHOR_TOP = 1,
XDG_POSITIONER_ANCHOR_BOTTOM = 2,
XDG_POSITIONER_ANCHOR_LEFT = 3,
XDG_POSITIONER_ANCHOR_RIGHT = 4,
XDG_POSITIONER_ANCHOR_TOP_LEFT = 5,
XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6,
XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7,
XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8,
};
#endif /* XDG_POSITIONER_ANCHOR_ENUM */
#ifndef XDG_POSITIONER_GRAVITY_ENUM
#define XDG_POSITIONER_GRAVITY_ENUM
enum xdg_positioner_gravity {
XDG_POSITIONER_GRAVITY_NONE = 0,
XDG_POSITIONER_GRAVITY_TOP = 1,
XDG_POSITIONER_GRAVITY_BOTTOM = 2,
XDG_POSITIONER_GRAVITY_LEFT = 3,
XDG_POSITIONER_GRAVITY_RIGHT = 4,
XDG_POSITIONER_GRAVITY_TOP_LEFT = 5,
XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6,
XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7,
XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8,
};
#endif /* XDG_POSITIONER_GRAVITY_ENUM */
#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM
#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM
/**
* @ingroup iface_xdg_positioner
* vertically resize the surface
*
* Resize the surface vertically so that it is completely unconstrained.
*/
enum xdg_positioner_constraint_adjustment {
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32,
};
#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */
#define XDG_POSITIONER_DESTROY 0
#define XDG_POSITIONER_SET_SIZE 1
#define XDG_POSITIONER_SET_ANCHOR_RECT 2
#define XDG_POSITIONER_SET_ANCHOR 3
#define XDG_POSITIONER_SET_GRAVITY 4
#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT 5
#define XDG_POSITIONER_SET_OFFSET 6
#define XDG_POSITIONER_SET_REACTIVE 7
#define XDG_POSITIONER_SET_PARENT_SIZE 8
#define XDG_POSITIONER_SET_PARENT_CONFIGURE 9
/**
* @ingroup iface_xdg_positioner
*/
#define XDG_POSITIONER_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_xdg_positioner
*/
#define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_positioner
*/
#define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1
/**
* @ingroup iface_xdg_positioner
*/
#define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1
/**
* @ingroup iface_xdg_positioner
*/
#define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1
/**
* @ingroup iface_xdg_positioner
*/
#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1
/**
* @ingroup iface_xdg_positioner
*/
#define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1
/**
* @ingroup iface_xdg_positioner
*/
#define XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION 3
/**
* @ingroup iface_xdg_positioner
*/
#define XDG_POSITIONER_SET_PARENT_SIZE_SINCE_VERSION 3
/**
* @ingroup iface_xdg_positioner
*/
#define XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION 3
/** @ingroup iface_xdg_positioner */
static inline void
xdg_positioner_set_user_data(struct xdg_positioner *xdg_positioner, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) xdg_positioner, user_data);
}
/** @ingroup iface_xdg_positioner */
static inline void *
xdg_positioner_get_user_data(struct xdg_positioner *xdg_positioner)
{
return wl_proxy_get_user_data((struct wl_proxy *) xdg_positioner);
}
static inline uint32_t
xdg_positioner_get_version(struct xdg_positioner *xdg_positioner)
{
return wl_proxy_get_version((struct wl_proxy *) xdg_positioner);
}
/**
* @ingroup iface_xdg_positioner
*
* Notify the compositor that the xdg_positioner will no longer be used.
*/
static inline void
xdg_positioner_destroy(struct xdg_positioner *xdg_positioner)
{
wl_proxy_marshal((struct wl_proxy *) xdg_positioner,
XDG_POSITIONER_DESTROY);
wl_proxy_destroy((struct wl_proxy *) xdg_positioner);
}
/**
* @ingroup iface_xdg_positioner
*
* Set the size of the surface that is to be positioned with the positioner
* object. The size is in surface-local coordinates and corresponds to the
* window geometry. See xdg_surface.set_window_geometry.
*
* If a zero or negative size is set the invalid_input error is raised.
*/
static inline void
xdg_positioner_set_size(struct xdg_positioner *xdg_positioner, int32_t width, int32_t height)
{
wl_proxy_marshal((struct wl_proxy *) xdg_positioner,
XDG_POSITIONER_SET_SIZE, width, height);
}
/**
* @ingroup iface_xdg_positioner
*
* Specify the anchor rectangle within the parent surface that the child
* surface will be placed relative to. The rectangle is relative to the
* window geometry as defined by xdg_surface.set_window_geometry of the
* parent surface.
*
* When the xdg_positioner object is used to position a child surface, the
* anchor rectangle may not extend outside the window geometry of the
* positioned child's parent surface.
*
* If a negative size is set the invalid_input error is raised.
*/
static inline void
xdg_positioner_set_anchor_rect(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y, int32_t width, int32_t height)
{
wl_proxy_marshal((struct wl_proxy *) xdg_positioner,
XDG_POSITIONER_SET_ANCHOR_RECT, x, y, width, height);
}
/**
* @ingroup iface_xdg_positioner
*
* Defines the anchor point for the anchor rectangle. The specified anchor
* is used derive an anchor point that the child surface will be
* positioned relative to. If a corner anchor is set (e.g. 'top_left' or
* 'bottom_right'), the anchor point will be at the specified corner;
* otherwise, the derived anchor point will be centered on the specified
* edge, or in the center of the anchor rectangle if no edge is specified.
*/
static inline void
xdg_positioner_set_anchor(struct xdg_positioner *xdg_positioner, uint32_t anchor)
{
wl_proxy_marshal((struct wl_proxy *) xdg_positioner,
XDG_POSITIONER_SET_ANCHOR, anchor);
}
/**
* @ingroup iface_xdg_positioner
*
* Defines in what direction a surface should be positioned, relative to
* the anchor point of the parent surface. If a corner gravity is
* specified (e.g. 'bottom_right' or 'top_left'), then the child surface
* will be placed towards the specified gravity; otherwise, the child
* surface will be centered over the anchor point on any axis that had no
* gravity specified.
*/
static inline void
xdg_positioner_set_gravity(struct xdg_positioner *xdg_positioner, uint32_t gravity)
{
wl_proxy_marshal((struct wl_proxy *) xdg_positioner,
XDG_POSITIONER_SET_GRAVITY, gravity);
}
/**
* @ingroup iface_xdg_positioner
*
* Specify how the window should be positioned if the originally intended
* position caused the surface to be constrained, meaning at least
* partially outside positioning boundaries set by the compositor. The
* adjustment is set by constructing a bitmask describing the adjustment to
* be made when the surface is constrained on that axis.
*
* If no bit for one axis is set, the compositor will assume that the child
* surface should not change its position on that axis when constrained.
*
* If more than one bit for one axis is set, the order of how adjustments
* are applied is specified in the corresponding adjustment descriptions.
*
* The default adjustment is none.
*/
static inline void
xdg_positioner_set_constraint_adjustment(struct xdg_positioner *xdg_positioner, uint32_t constraint_adjustment)
{
wl_proxy_marshal((struct wl_proxy *) xdg_positioner,
XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT, constraint_adjustment);
}
/**
* @ingroup iface_xdg_positioner
*
* Specify the surface position offset relative to the position of the
* anchor on the anchor rectangle and the anchor on the surface. For
* example if the anchor of the anchor rectangle is at (x, y), the surface
* has the gravity bottom|right, and the offset is (ox, oy), the calculated
* surface position will be (x + ox, y + oy). The offset position of the
* surface is the one used for constraint testing. See
* set_constraint_adjustment.
*
* An example use case is placing a popup menu on top of a user interface
* element, while aligning the user interface element of the parent surface
* with some user interface element placed somewhere in the popup surface.
*/
static inline void
xdg_positioner_set_offset(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y)
{
wl_proxy_marshal((struct wl_proxy *) xdg_positioner,
XDG_POSITIONER_SET_OFFSET, x, y);
}
/**
* @ingroup iface_xdg_positioner
*
* When set reactive, the surface is reconstrained if the conditions used
* for constraining changed, e.g. the parent window moved.
*
* If the conditions changed and the popup was reconstrained, an
* xdg_popup.configure event is sent with updated geometry, followed by an
* xdg_surface.configure event.
*/
static inline void
xdg_positioner_set_reactive(struct xdg_positioner *xdg_positioner)
{
wl_proxy_marshal((struct wl_proxy *) xdg_positioner,
XDG_POSITIONER_SET_REACTIVE);
}
/**
* @ingroup iface_xdg_positioner
*
* Set the parent window geometry the compositor should use when
* positioning the popup. The compositor may use this information to
* determine the future state the popup should be constrained using. If
* this doesn't match the dimension of the parent the popup is eventually
* positioned against, the behavior is undefined.
*
* The arguments are given in the surface-local coordinate space.
*/
static inline void
xdg_positioner_set_parent_size(struct xdg_positioner *xdg_positioner, int32_t parent_width, int32_t parent_height)
{
wl_proxy_marshal((struct wl_proxy *) xdg_positioner,
XDG_POSITIONER_SET_PARENT_SIZE, parent_width, parent_height);
}
/**
* @ingroup iface_xdg_positioner
*
* Set the serial of an xdg_surface.configure event this positioner will be
* used in response to. The compositor may use this information together
* with set_parent_size to determine what future state the popup should be
* constrained using.
*/
static inline void
xdg_positioner_set_parent_configure(struct xdg_positioner *xdg_positioner, uint32_t serial)
{
wl_proxy_marshal((struct wl_proxy *) xdg_positioner,
XDG_POSITIONER_SET_PARENT_CONFIGURE, serial);
}
#ifndef XDG_SURFACE_ERROR_ENUM
#define XDG_SURFACE_ERROR_ENUM
enum xdg_surface_error {
XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1,
XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2,
XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3,
};
#endif /* XDG_SURFACE_ERROR_ENUM */
/**
* @ingroup iface_xdg_surface
* @struct xdg_surface_listener
*/
struct xdg_surface_listener {
/**
* suggest a surface change
*
* The configure event marks the end of a configure sequence. A
* configure sequence is a set of one or more events configuring
* the state of the xdg_surface, including the final
* xdg_surface.configure event.
*
* Where applicable, xdg_surface surface roles will during a
* configure sequence extend this event as a latched state sent as
* events before the xdg_surface.configure event. Such events
* should be considered to make up a set of atomically applied
* configuration states, where the xdg_surface.configure commits
* the accumulated state.
*
* Clients should arrange their surface for the new states, and
* then send an ack_configure request with the serial sent in this
* configure event at some point before committing the new surface.
*
* If the client receives multiple configure events before it can
* respond to one, it is free to discard all but the last event it
* received.
* @param serial serial of the configure event
*/
void (*configure)(void *data,
struct xdg_surface *xdg_surface,
uint32_t serial);
};
/**
* @ingroup iface_xdg_surface
*/
static inline int
xdg_surface_add_listener(struct xdg_surface *xdg_surface,
const struct xdg_surface_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) xdg_surface,
(void (**)(void)) listener, data);
}
#define XDG_SURFACE_DESTROY 0
#define XDG_SURFACE_GET_TOPLEVEL 1
#define XDG_SURFACE_GET_POPUP 2
#define XDG_SURFACE_SET_WINDOW_GEOMETRY 3
#define XDG_SURFACE_ACK_CONFIGURE 4
/**
* @ingroup iface_xdg_surface
*/
#define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_surface
*/
#define XDG_SURFACE_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_xdg_surface
*/
#define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1
/**
* @ingroup iface_xdg_surface
*/
#define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1
/**
* @ingroup iface_xdg_surface
*/
#define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1
/**
* @ingroup iface_xdg_surface
*/
#define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1
/** @ingroup iface_xdg_surface */
static inline void
xdg_surface_set_user_data(struct xdg_surface *xdg_surface, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) xdg_surface, user_data);
}
/** @ingroup iface_xdg_surface */
static inline void *
xdg_surface_get_user_data(struct xdg_surface *xdg_surface)
{
return wl_proxy_get_user_data((struct wl_proxy *) xdg_surface);
}
static inline uint32_t
xdg_surface_get_version(struct xdg_surface *xdg_surface)
{
return wl_proxy_get_version((struct wl_proxy *) xdg_surface);
}
/**
* @ingroup iface_xdg_surface
*
* Destroy the xdg_surface object. An xdg_surface must only be destroyed
* after its role object has been destroyed.
*/
static inline void
xdg_surface_destroy(struct xdg_surface *xdg_surface)
{
wl_proxy_marshal((struct wl_proxy *) xdg_surface,
XDG_SURFACE_DESTROY);
wl_proxy_destroy((struct wl_proxy *) xdg_surface);
}
/**
* @ingroup iface_xdg_surface
*
* This creates an xdg_toplevel object for the given xdg_surface and gives
* the associated wl_surface the xdg_toplevel role.
*
* See the documentation of xdg_toplevel for more details about what an
* xdg_toplevel is and how it is used.
*/
static inline struct xdg_toplevel *
xdg_surface_get_toplevel(struct xdg_surface *xdg_surface)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_surface,
XDG_SURFACE_GET_TOPLEVEL, &xdg_toplevel_interface, NULL);
return (struct xdg_toplevel *) id;
}
/**
* @ingroup iface_xdg_surface
*
* This creates an xdg_popup object for the given xdg_surface and gives
* the associated wl_surface the xdg_popup role.
*
* If null is passed as a parent, a parent surface must be specified using
* some other protocol, before committing the initial state.
*
* See the documentation of xdg_popup for more details about what an
* xdg_popup is and how it is used.
*/
static inline struct xdg_popup *
xdg_surface_get_popup(struct xdg_surface *xdg_surface, struct xdg_surface *parent, struct xdg_positioner *positioner)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_surface,
XDG_SURFACE_GET_POPUP, &xdg_popup_interface, NULL, parent, positioner);
return (struct xdg_popup *) id;
}
/**
* @ingroup iface_xdg_surface
*
* The window geometry of a surface is its "visible bounds" from the
* user's perspective. Client-side decorations often have invisible
* portions like drop-shadows which should be ignored for the
* purposes of aligning, placing and constraining windows.
*
* The window geometry is double buffered, and will be applied at the
* time wl_surface.commit of the corresponding wl_surface is called.
*
* When maintaining a position, the compositor should treat the (x, y)
* coordinate of the window geometry as the top left corner of the window.
* A client changing the (x, y) window geometry coordinate should in
* general not alter the position of the window.
*
* Once the window geometry of the surface is set, it is not possible to
* unset it, and it will remain the same until set_window_geometry is
* called again, even if a new subsurface or buffer is attached.
*
* If never set, the value is the full bounds of the surface,
* including any subsurfaces. This updates dynamically on every
* commit. This unset is meant for extremely simple clients.
*
* The arguments are given in the surface-local coordinate space of
* the wl_surface associated with this xdg_surface.
*
* The width and height must be greater than zero. Setting an invalid size
* will raise an error. When applied, the effective window geometry will be
* the set window geometry clamped to the bounding rectangle of the
* combined geometry of the surface of the xdg_surface and the associated
* subsurfaces.
*/
static inline void
xdg_surface_set_window_geometry(struct xdg_surface *xdg_surface, int32_t x, int32_t y, int32_t width, int32_t height)
{
wl_proxy_marshal((struct wl_proxy *) xdg_surface,
XDG_SURFACE_SET_WINDOW_GEOMETRY, x, y, width, height);
}
/**
* @ingroup iface_xdg_surface
*
* When a configure event is received, if a client commits the
* surface in response to the configure event, then the client
* must make an ack_configure request sometime before the commit
* request, passing along the serial of the configure event.
*
* For instance, for toplevel surfaces the compositor might use this
* information to move a surface to the top left only when the client has
* drawn itself for the maximized or fullscreen state.
*
* If the client receives multiple configure events before it
* can respond to one, it only has to ack the last configure event.
*
* A client is not required to commit immediately after sending
* an ack_configure request - it may even ack_configure several times
* before its next surface commit.
*
* A client may send multiple ack_configure requests before committing, but
* only the last request sent before a commit indicates which configure
* event the client really is responding to.
*/
static inline void
xdg_surface_ack_configure(struct xdg_surface *xdg_surface, uint32_t serial)
{
wl_proxy_marshal((struct wl_proxy *) xdg_surface,
XDG_SURFACE_ACK_CONFIGURE, serial);
}
#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM
#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM
/**
* @ingroup iface_xdg_toplevel
* edge values for resizing
*
* These values are used to indicate which edge of a surface
* is being dragged in a resize operation.
*/
enum xdg_toplevel_resize_edge {
XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0,
XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1,
XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2,
XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4,
XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5,
XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6,
XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8,
XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9,
XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10,
};
#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */
#ifndef XDG_TOPLEVEL_STATE_ENUM
#define XDG_TOPLEVEL_STATE_ENUM
/**
* @ingroup iface_xdg_toplevel
* the surface is tiled
*
* The window is currently in a tiled layout and the bottom edge is
* considered to be adjacent to another part of the tiling grid.
*/
enum xdg_toplevel_state {
/**
* the surface is maximized
*/
XDG_TOPLEVEL_STATE_MAXIMIZED = 1,
/**
* the surface is fullscreen
*/
XDG_TOPLEVEL_STATE_FULLSCREEN = 2,
/**
* the surface is being resized
*/
XDG_TOPLEVEL_STATE_RESIZING = 3,
/**
* the surface is now activated
*/
XDG_TOPLEVEL_STATE_ACTIVATED = 4,
/**
* @since 2
*/
XDG_TOPLEVEL_STATE_TILED_LEFT = 5,
/**
* @since 2
*/
XDG_TOPLEVEL_STATE_TILED_RIGHT = 6,
/**
* @since 2
*/
XDG_TOPLEVEL_STATE_TILED_TOP = 7,
/**
* @since 2
*/
XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8,
};
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2
#endif /* XDG_TOPLEVEL_STATE_ENUM */
/**
* @ingroup iface_xdg_toplevel
* @struct xdg_toplevel_listener
*/
struct xdg_toplevel_listener {
/**
* suggest a surface change
*
* This configure event asks the client to resize its toplevel
* surface or to change its state. The configured state should not
* be applied immediately. See xdg_surface.configure for details.
*
* The width and height arguments specify a hint to the window
* about how its surface should be resized in window geometry
* coordinates. See set_window_geometry.
*
* If the width or height arguments are zero, it means the client
* should decide its own window dimension. This may happen when the
* compositor needs to configure the state of the surface but
* doesn't have any information about any previous or expected
* dimension.
*
* The states listed in the event specify how the width/height
* arguments should be interpreted, and possibly how it should be
* drawn.
*
* Clients must send an ack_configure in response to this event.
* See xdg_surface.configure and xdg_surface.ack_configure for
* details.
*/
void (*configure)(void *data,
struct xdg_toplevel *xdg_toplevel,
int32_t width,
int32_t height,
struct wl_array *states);
/**
* surface wants to be closed
*
* The close event is sent by the compositor when the user wants
* the surface to be closed. This should be equivalent to the user
* clicking the close button in client-side decorations, if your
* application has any.
*
* This is only a request that the user intends to close the
* window. The client may choose to ignore this request, or show a
* dialog to ask the user to save their data, etc.
*/
void (*close)(void *data,
struct xdg_toplevel *xdg_toplevel);
};
/**
* @ingroup iface_xdg_toplevel
*/
static inline int
xdg_toplevel_add_listener(struct xdg_toplevel *xdg_toplevel,
const struct xdg_toplevel_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel,
(void (**)(void)) listener, data);
}
#define XDG_TOPLEVEL_DESTROY 0
#define XDG_TOPLEVEL_SET_PARENT 1
#define XDG_TOPLEVEL_SET_TITLE 2
#define XDG_TOPLEVEL_SET_APP_ID 3
#define XDG_TOPLEVEL_SHOW_WINDOW_MENU 4
#define XDG_TOPLEVEL_MOVE 5
#define XDG_TOPLEVEL_RESIZE 6
#define XDG_TOPLEVEL_SET_MAX_SIZE 7
#define XDG_TOPLEVEL_SET_MIN_SIZE 8
#define XDG_TOPLEVEL_SET_MAXIMIZED 9
#define XDG_TOPLEVEL_UNSET_MAXIMIZED 10
#define XDG_TOPLEVEL_SET_FULLSCREEN 11
#define XDG_TOPLEVEL_UNSET_FULLSCREEN 12
#define XDG_TOPLEVEL_SET_MINIMIZED 13
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel
*/
#define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1
/** @ingroup iface_xdg_toplevel */
static inline void
xdg_toplevel_set_user_data(struct xdg_toplevel *xdg_toplevel, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel, user_data);
}
/** @ingroup iface_xdg_toplevel */
static inline void *
xdg_toplevel_get_user_data(struct xdg_toplevel *xdg_toplevel)
{
return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel);
}
static inline uint32_t
xdg_toplevel_get_version(struct xdg_toplevel *xdg_toplevel)
{
return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel);
}
/**
* @ingroup iface_xdg_toplevel
*
* This request destroys the role surface and unmaps the surface;
* see "Unmapping" behavior in interface section for details.
*/
static inline void
xdg_toplevel_destroy(struct xdg_toplevel *xdg_toplevel)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_DESTROY);
wl_proxy_destroy((struct wl_proxy *) xdg_toplevel);
}
/**
* @ingroup iface_xdg_toplevel
*
* Set the "parent" of this surface. This surface should be stacked
* above the parent surface and all other ancestor surfaces.
*
* Parent windows should be set on dialogs, toolboxes, or other
* "auxiliary" surfaces, so that the parent is raised when the dialog
* is raised.
*
* Setting a null parent for a child window removes any parent-child
* relationship for the child. Setting a null parent for a window which
* currently has no parent is a no-op.
*
* If the parent is unmapped then its children are managed as
* though the parent of the now-unmapped parent has become the
* parent of this surface. If no parent exists for the now-unmapped
* parent then the children are managed as though they have no
* parent surface.
*/
static inline void
xdg_toplevel_set_parent(struct xdg_toplevel *xdg_toplevel, struct xdg_toplevel *parent)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_SET_PARENT, parent);
}
/**
* @ingroup iface_xdg_toplevel
*
* Set a short title for the surface.
*
* This string may be used to identify the surface in a task bar,
* window list, or other user interface elements provided by the
* compositor.
*
* The string must be encoded in UTF-8.
*/
static inline void
xdg_toplevel_set_title(struct xdg_toplevel *xdg_toplevel, const char *title)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_SET_TITLE, title);
}
/**
* @ingroup iface_xdg_toplevel
*
* Set an application identifier for the surface.
*
* The app ID identifies the general class of applications to which
* the surface belongs. The compositor can use this to group multiple
* surfaces together, or to determine how to launch a new application.
*
* For D-Bus activatable applications, the app ID is used as the D-Bus
* service name.
*
* The compositor shell will try to group application surfaces together
* by their app ID. As a best practice, it is suggested to select app
* ID's that match the basename of the application's .desktop file.
* For example, "org.freedesktop.FooViewer" where the .desktop file is
* "org.freedesktop.FooViewer.desktop".
*
* Like other properties, a set_app_id request can be sent after the
* xdg_toplevel has been mapped to update the property.
*
* See the desktop-entry specification [0] for more details on
* application identifiers and how they relate to well-known D-Bus
* names and .desktop files.
*
* [0] http://standards.freedesktop.org/desktop-entry-spec/
*/
static inline void
xdg_toplevel_set_app_id(struct xdg_toplevel *xdg_toplevel, const char *app_id)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_SET_APP_ID, app_id);
}
/**
* @ingroup iface_xdg_toplevel
*
* Clients implementing client-side decorations might want to show
* a context menu when right-clicking on the decorations, giving the
* user a menu that they can use to maximize or minimize the window.
*
* This request asks the compositor to pop up such a window menu at
* the given position, relative to the local surface coordinates of
* the parent surface. There are no guarantees as to what menu items
* the window menu contains.
*
* This request must be used in response to some sort of user action
* like a button press, key press, or touch down event.
*/
static inline void
xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, int32_t x, int32_t y)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_SHOW_WINDOW_MENU, seat, serial, x, y);
}
/**
* @ingroup iface_xdg_toplevel
*
* Start an interactive, user-driven move of the surface.
*
* This request must be used in response to some sort of user action
* like a button press, key press, or touch down event. The passed
* serial is used to determine the type of interactive move (touch,
* pointer, etc).
*
* The server may ignore move requests depending on the state of
* the surface (e.g. fullscreen or maximized), or if the passed serial
* is no longer valid.
*
* If triggered, the surface will lose the focus of the device
* (wl_pointer, wl_touch, etc) used for the move. It is up to the
* compositor to visually indicate that the move is taking place, such as
* updating a pointer cursor, during the move. There is no guarantee
* that the device focus will return when the move is completed.
*/
static inline void
xdg_toplevel_move(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_MOVE, seat, serial);
}
/**
* @ingroup iface_xdg_toplevel
*
* Start a user-driven, interactive resize of the surface.
*
* This request must be used in response to some sort of user action
* like a button press, key press, or touch down event. The passed
* serial is used to determine the type of interactive resize (touch,
* pointer, etc).
*
* The server may ignore resize requests depending on the state of
* the surface (e.g. fullscreen or maximized).
*
* If triggered, the client will receive configure events with the
* "resize" state enum value and the expected sizes. See the "resize"
* enum value for more details about what is required. The client
* must also acknowledge configure events using "ack_configure". After
* the resize is completed, the client will receive another "configure"
* event without the resize state.
*
* If triggered, the surface also will lose the focus of the device
* (wl_pointer, wl_touch, etc) used for the resize. It is up to the
* compositor to visually indicate that the resize is taking place,
* such as updating a pointer cursor, during the resize. There is no
* guarantee that the device focus will return when the resize is
* completed.
*
* The edges parameter specifies how the surface should be resized,
* and is one of the values of the resize_edge enum. The compositor
* may use this information to update the surface position for
* example when dragging the top left corner. The compositor may also
* use this information to adapt its behavior, e.g. choose an
* appropriate cursor image.
*/
static inline void
xdg_toplevel_resize(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, uint32_t edges)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_RESIZE, seat, serial, edges);
}
/**
* @ingroup iface_xdg_toplevel
*
* Set a maximum size for the window.
*
* The client can specify a maximum size so that the compositor does
* not try to configure the window beyond this size.
*
* The width and height arguments are in window geometry coordinates.
* See xdg_surface.set_window_geometry.
*
* Values set in this way are double-buffered. They will get applied
* on the next commit.
*
* The compositor can use this information to allow or disallow
* different states like maximize or fullscreen and draw accurate
* animations.
*
* Similarly, a tiling window manager may use this information to
* place and resize client windows in a more effective way.
*
* The client should not rely on the compositor to obey the maximum
* size. The compositor may decide to ignore the values set by the
* client and request a larger size.
*
* If never set, or a value of zero in the request, means that the
* client has no expected maximum size in the given dimension.
* As a result, a client wishing to reset the maximum size
* to an unspecified state can use zero for width and height in the
* request.
*
* Requesting a maximum size to be smaller than the minimum size of
* a surface is illegal and will result in a protocol error.
*
* The width and height must be greater than or equal to zero. Using
* strictly negative values for width and height will result in a
* protocol error.
*/
static inline void
xdg_toplevel_set_max_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_SET_MAX_SIZE, width, height);
}
/**
* @ingroup iface_xdg_toplevel
*
* Set a minimum size for the window.
*
* The client can specify a minimum size so that the compositor does
* not try to configure the window below this size.
*
* The width and height arguments are in window geometry coordinates.
* See xdg_surface.set_window_geometry.
*
* Values set in this way are double-buffered. They will get applied
* on the next commit.
*
* The compositor can use this information to allow or disallow
* different states like maximize or fullscreen and draw accurate
* animations.
*
* Similarly, a tiling window manager may use this information to
* place and resize client windows in a more effective way.
*
* The client should not rely on the compositor to obey the minimum
* size. The compositor may decide to ignore the values set by the
* client and request a smaller size.
*
* If never set, or a value of zero in the request, means that the
* client has no expected minimum size in the given dimension.
* As a result, a client wishing to reset the minimum size
* to an unspecified state can use zero for width and height in the
* request.
*
* Requesting a minimum size to be larger than the maximum size of
* a surface is illegal and will result in a protocol error.
*
* The width and height must be greater than or equal to zero. Using
* strictly negative values for width and height will result in a
* protocol error.
*/
static inline void
xdg_toplevel_set_min_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_SET_MIN_SIZE, width, height);
}
/**
* @ingroup iface_xdg_toplevel
*
* Maximize the surface.
*
* After requesting that the surface should be maximized, the compositor
* will respond by emitting a configure event. Whether this configure
* actually sets the window maximized is subject to compositor policies.
* The client must then update its content, drawing in the configured
* state. The client must also acknowledge the configure when committing
* the new content (see ack_configure).
*
* It is up to the compositor to decide how and where to maximize the
* surface, for example which output and what region of the screen should
* be used.
*
* If the surface was already maximized, the compositor will still emit
* a configure event with the "maximized" state.
*
* If the surface is in a fullscreen state, this request has no direct
* effect. It may alter the state the surface is returned to when
* unmaximized unless overridden by the compositor.
*/
static inline void
xdg_toplevel_set_maximized(struct xdg_toplevel *xdg_toplevel)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_SET_MAXIMIZED);
}
/**
* @ingroup iface_xdg_toplevel
*
* Unmaximize the surface.
*
* After requesting that the surface should be unmaximized, the compositor
* will respond by emitting a configure event. Whether this actually
* un-maximizes the window is subject to compositor policies.
* If available and applicable, the compositor will include the window
* geometry dimensions the window had prior to being maximized in the
* configure event. The client must then update its content, drawing it in
* the configured state. The client must also acknowledge the configure
* when committing the new content (see ack_configure).
*
* It is up to the compositor to position the surface after it was
* unmaximized; usually the position the surface had before maximizing, if
* applicable.
*
* If the surface was already not maximized, the compositor will still
* emit a configure event without the "maximized" state.
*
* If the surface is in a fullscreen state, this request has no direct
* effect. It may alter the state the surface is returned to when
* unmaximized unless overridden by the compositor.
*/
static inline void
xdg_toplevel_unset_maximized(struct xdg_toplevel *xdg_toplevel)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_UNSET_MAXIMIZED);
}
/**
* @ingroup iface_xdg_toplevel
*
* Make the surface fullscreen.
*
* After requesting that the surface should be fullscreened, the
* compositor will respond by emitting a configure event. Whether the
* client is actually put into a fullscreen state is subject to compositor
* policies. The client must also acknowledge the configure when
* committing the new content (see ack_configure).
*
* The output passed by the request indicates the client's preference as
* to which display it should be set fullscreen on. If this value is NULL,
* it's up to the compositor to choose which display will be used to map
* this surface.
*
* If the surface doesn't cover the whole output, the compositor will
* position the surface in the center of the output and compensate with
* with border fill covering the rest of the output. The content of the
* border fill is undefined, but should be assumed to be in some way that
* attempts to blend into the surrounding area (e.g. solid black).
*
* If the fullscreened surface is not opaque, the compositor must make
* sure that other screen content not part of the same surface tree (made
* up of subsurfaces, popups or similarly coupled surfaces) are not
* visible below the fullscreened surface.
*/
static inline void
xdg_toplevel_set_fullscreen(struct xdg_toplevel *xdg_toplevel, struct wl_output *output)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_SET_FULLSCREEN, output);
}
/**
* @ingroup iface_xdg_toplevel
*
* Make the surface no longer fullscreen.
*
* After requesting that the surface should be unfullscreened, the
* compositor will respond by emitting a configure event.
* Whether this actually removes the fullscreen state of the client is
* subject to compositor policies.
*
* Making a surface unfullscreen sets states for the surface based on the following:
* * the state(s) it may have had before becoming fullscreen
* * any state(s) decided by the compositor
* * any state(s) requested by the client while the surface was fullscreen
*
* The compositor may include the previous window geometry dimensions in
* the configure event, if applicable.
*
* The client must also acknowledge the configure when committing the new
* content (see ack_configure).
*/
static inline void
xdg_toplevel_unset_fullscreen(struct xdg_toplevel *xdg_toplevel)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_UNSET_FULLSCREEN);
}
/**
* @ingroup iface_xdg_toplevel
*
* Request that the compositor minimize your surface. There is no
* way to know if the surface is currently minimized, nor is there
* any way to unset minimization on this surface.
*
* If you are looking to throttle redrawing when minimized, please
* instead use the wl_surface.frame event for this, as this will
* also work with live previews on windows in Alt-Tab, Expose or
* similar compositor features.
*/
static inline void
xdg_toplevel_set_minimized(struct xdg_toplevel *xdg_toplevel)
{
wl_proxy_marshal((struct wl_proxy *) xdg_toplevel,
XDG_TOPLEVEL_SET_MINIMIZED);
}
#ifndef XDG_POPUP_ERROR_ENUM
#define XDG_POPUP_ERROR_ENUM
enum xdg_popup_error {
/**
* tried to grab after being mapped
*/
XDG_POPUP_ERROR_INVALID_GRAB = 0,
};
#endif /* XDG_POPUP_ERROR_ENUM */
/**
* @ingroup iface_xdg_popup
* @struct xdg_popup_listener
*/
struct xdg_popup_listener {
/**
* configure the popup surface
*
* This event asks the popup surface to configure itself given
* the configuration. The configured state should not be applied
* immediately. See xdg_surface.configure for details.
*
* The x and y arguments represent the position the popup was
* placed at given the xdg_positioner rule, relative to the upper
* left corner of the window geometry of the parent surface.
*
* For version 2 or older, the configure event for an xdg_popup is
* only ever sent once for the initial configuration. Starting with
* version 3, it may be sent again if the popup is setup with an
* xdg_positioner with set_reactive requested, or in response to
* xdg_popup.reposition requests.
* @param x x position relative to parent surface window geometry
* @param y y position relative to parent surface window geometry
* @param width window geometry width
* @param height window geometry height
*/
void (*configure)(void *data,
struct xdg_popup *xdg_popup,
int32_t x,
int32_t y,
int32_t width,
int32_t height);
/**
* popup interaction is done
*
* The popup_done event is sent out when a popup is dismissed by
* the compositor. The client should destroy the xdg_popup object
* at this point.
*/
void (*popup_done)(void *data,
struct xdg_popup *xdg_popup);
/**
* signal the completion of a repositioned request
*
* The repositioned event is sent as part of a popup
* configuration sequence, together with xdg_popup.configure and
* lastly xdg_surface.configure to notify the completion of a
* reposition request.
*
* The repositioned event is to notify about the completion of a
* xdg_popup.reposition request. The token argument is the token
* passed in the xdg_popup.reposition request.
*
* Immediately after this event is emitted, xdg_popup.configure and
* xdg_surface.configure will be sent with the updated size and
* position, as well as a new configure serial.
*
* The client should optionally update the content of the popup,
* but must acknowledge the new popup configuration for the new
* position to take effect. See xdg_surface.ack_configure for
* details.
* @param token reposition request token
* @since 3
*/
void (*repositioned)(void *data,
struct xdg_popup *xdg_popup,
uint32_t token);
};
/**
* @ingroup iface_xdg_popup
*/
static inline int
xdg_popup_add_listener(struct xdg_popup *xdg_popup,
const struct xdg_popup_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) xdg_popup,
(void (**)(void)) listener, data);
}
#define XDG_POPUP_DESTROY 0
#define XDG_POPUP_GRAB 1
#define XDG_POPUP_REPOSITION 2
/**
* @ingroup iface_xdg_popup
*/
#define XDG_POPUP_CONFIGURE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_popup
*/
#define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_popup
*/
#define XDG_POPUP_REPOSITIONED_SINCE_VERSION 3
/**
* @ingroup iface_xdg_popup
*/
#define XDG_POPUP_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_xdg_popup
*/
#define XDG_POPUP_GRAB_SINCE_VERSION 1
/**
* @ingroup iface_xdg_popup
*/
#define XDG_POPUP_REPOSITION_SINCE_VERSION 3
/** @ingroup iface_xdg_popup */
static inline void
xdg_popup_set_user_data(struct xdg_popup *xdg_popup, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) xdg_popup, user_data);
}
/** @ingroup iface_xdg_popup */
static inline void *
xdg_popup_get_user_data(struct xdg_popup *xdg_popup)
{
return wl_proxy_get_user_data((struct wl_proxy *) xdg_popup);
}
static inline uint32_t
xdg_popup_get_version(struct xdg_popup *xdg_popup)
{
return wl_proxy_get_version((struct wl_proxy *) xdg_popup);
}
/**
* @ingroup iface_xdg_popup
*
* This destroys the popup. Explicitly destroying the xdg_popup
* object will also dismiss the popup, and unmap the surface.
*
* If this xdg_popup is not the "topmost" popup, a protocol error
* will be sent.
*/
static inline void
xdg_popup_destroy(struct xdg_popup *xdg_popup)
{
wl_proxy_marshal((struct wl_proxy *) xdg_popup,
XDG_POPUP_DESTROY);
wl_proxy_destroy((struct wl_proxy *) xdg_popup);
}
/**
* @ingroup iface_xdg_popup
*
* This request makes the created popup take an explicit grab. An explicit
* grab will be dismissed when the user dismisses the popup, or when the
* client destroys the xdg_popup. This can be done by the user clicking
* outside the surface, using the keyboard, or even locking the screen
* through closing the lid or a timeout.
*
* If the compositor denies the grab, the popup will be immediately
* dismissed.
*
* This request must be used in response to some sort of user action like a
* button press, key press, or touch down event. The serial number of the
* event should be passed as 'serial'.
*
* The parent of a grabbing popup must either be an xdg_toplevel surface or
* another xdg_popup with an explicit grab. If the parent is another
* xdg_popup it means that the popups are nested, with this popup now being
* the topmost popup.
*
* Nested popups must be destroyed in the reverse order they were created
* in, e.g. the only popup you are allowed to destroy at all times is the
* topmost one.
*
* When compositors choose to dismiss a popup, they may dismiss every
* nested grabbing popup as well. When a compositor dismisses popups, it
* will follow the same dismissing order as required from the client.
*
* The parent of a grabbing popup must either be another xdg_popup with an
* active explicit grab, or an xdg_popup or xdg_toplevel, if there are no
* explicit grabs already taken.
*
* If the topmost grabbing popup is destroyed, the grab will be returned to
* the parent of the popup, if that parent previously had an explicit grab.
*
* If the parent is a grabbing popup which has already been dismissed, this
* popup will be immediately dismissed. If the parent is a popup that did
* not take an explicit grab, an error will be raised.
*
* During a popup grab, the client owning the grab will receive pointer
* and touch events for all their surfaces as normal (similar to an
* "owner-events" grab in X11 parlance), while the top most grabbing popup
* will always have keyboard focus.
*/
static inline void
xdg_popup_grab(struct xdg_popup *xdg_popup, struct wl_seat *seat, uint32_t serial)
{
wl_proxy_marshal((struct wl_proxy *) xdg_popup,
XDG_POPUP_GRAB, seat, serial);
}
/**
* @ingroup iface_xdg_popup
*
* Reposition an already-mapped popup. The popup will be placed given the
* details in the passed xdg_positioner object, and a
* xdg_popup.repositioned followed by xdg_popup.configure and
* xdg_surface.configure will be emitted in response. Any parameters set
* by the previous positioner will be discarded.
*
* The passed token will be sent in the corresponding
* xdg_popup.repositioned event. The new popup position will not take
* effect until the corresponding configure event is acknowledged by the
* client. See xdg_popup.repositioned for details. The token itself is
* opaque, and has no other special meaning.
*
* If multiple reposition requests are sent, the compositor may skip all
* but the last one.
*
* If the popup is repositioned in response to a configure event for its
* parent, the client should send an xdg_positioner.set_parent_configure
* and possibly an xdg_positioner.set_parent_size request to allow the
* compositor to properly constrain the popup.
*
* If the popup is repositioned together with a parent that is being
* resized, but not in response to a configure event, the client should
* send an xdg_positioner.set_parent_size request.
*/
static inline void
xdg_popup_reposition(struct xdg_popup *xdg_popup, struct xdg_positioner *positioner, uint32_t token)
{
wl_proxy_marshal((struct wl_proxy *) xdg_popup,
XDG_POPUP_REPOSITION, positioner, token);
}
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: vendor/gioui.org/app/window.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"fmt"
"image"
"image/color"
"runtime"
"time"
"gioui.org/f32"
"gioui.org/gpu"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/io/profile"
"gioui.org/io/router"
"gioui.org/io/system"
"gioui.org/op"
"gioui.org/unit"
_ "gioui.org/app/internal/log"
)
// Option configures a window.
type Option func(unit.Metric, *Config)
// Window represents an operating system window.
type Window struct {
ctx context
gpu gpu.GPU
// driverFuncs is a channel of functions to run when
// the Window has a valid driver.
driverFuncs chan func(d driver)
// wakeups wakes up the native event loop to send a
// WakeupEvent that flushes driverFuncs.
wakeups chan struct{}
// wakeupFuncs is sent wakeup functions when the driver changes.
wakeupFuncs chan func()
// redraws is notified when a redraw is requested by the client.
redraws chan struct{}
// immediateRedraws is like redraw but doesn't need a wakeup.
immediateRedraws chan struct{}
// scheduledRedraws is sent the most recent delayed redraw time.
scheduledRedraws chan time.Time
out chan event.Event
frames chan *op.Ops
frameAck chan struct{}
// dead is closed when the window is destroyed.
dead chan struct{}
stage system.Stage
animating bool
hasNextFrame bool
nextFrame time.Time
delayedDraw *time.Timer
queue queue
cursor pointer.CursorName
callbacks callbacks
nocontext bool
// semantic data, lazily evaluated if requested by a backend to speed up
// the cases where semantic data is not needed.
semantic struct {
// uptodate tracks whether the fields below are up to date.
uptodate bool
root router.SemanticID
prevTree []router.SemanticNode
tree []router.SemanticNode
ids map[router.SemanticID]router.SemanticNode
}
}
type semanticResult struct {
found bool
node router.SemanticNode
}
type callbacks struct {
w *Window
d driver
}
// queue is an event.Queue implementation that distributes system events
// to the input handlers declared in the most recent frame.
type queue struct {
q router.Router
}
// Pre-allocate the ack event to avoid garbage.
var ackEvent event.Event
// NewWindow creates a new window for a set of window
// options. The options are hints; the platform is free to
// ignore or adjust them.
//
// If the current program is running on iOS or Android,
// NewWindow returns the window previously created by the
// platform.
//
// Calling NewWindow more than once is not supported on
// iOS, Android, WebAssembly.
func NewWindow(options ...Option) *Window {
defaultOptions := []Option{
Size(unit.Dp(800), unit.Dp(600)),
Title("Gio"),
}
options = append(defaultOptions, options...)
var cnf Config
cnf.apply(unit.Metric{}, options)
w := &Window{
out: make(chan event.Event),
immediateRedraws: make(chan struct{}, 0),
redraws: make(chan struct{}, 1),
scheduledRedraws: make(chan time.Time, 1),
frames: make(chan *op.Ops),
frameAck: make(chan struct{}),
driverFuncs: make(chan func(d driver), 1),
wakeups: make(chan struct{}, 1),
wakeupFuncs: make(chan func()),
dead: make(chan struct{}),
nocontext: cnf.CustomRenderer,
}
w.semantic.ids = make(map[router.SemanticID]router.SemanticNode)
w.callbacks.w = w
go w.run(options)
return w
}
// Events returns the channel where events are delivered.
func (w *Window) Events() <-chan event.Event {
return w.out
}
// update updates the window contents, input operations declare input handlers,
// and so on. The supplied operations list completely replaces the window state
// from previous calls.
func (w *Window) update(frame *op.Ops) {
w.frames <- frame
<-w.frameAck
}
func (w *Window) validateAndProcess(d driver, frameStart time.Time, size image.Point, sync bool, frame *op.Ops) error {
for {
if w.gpu == nil && !w.nocontext {
var err error
if w.ctx == nil {
w.ctx, err = d.NewContext()
if err != nil {
return err
}
sync = true
}
}
if sync && w.ctx != nil {
if err := w.ctx.Refresh(); err != nil {
if errors.Is(err, errOutOfDate) {
// Surface couldn't be created for transient reasons. Skip
// this frame and wait for the next.
return nil
}
w.destroyGPU()
if errors.Is(err, gpu.ErrDeviceLost) {
continue
}
return err
}
}
if w.gpu == nil && !w.nocontext {
if err := w.ctx.Lock(); err != nil {
w.destroyGPU()
return err
}
gpu, err := gpu.New(w.ctx.API())
w.ctx.Unlock()
if err != nil {
w.destroyGPU()
return err
}
w.gpu = gpu
}
if w.gpu != nil {
if err := w.render(frame, size); err != nil {
if errors.Is(err, errOutOfDate) {
// GPU surface needs refreshing.
sync = true
continue
}
w.destroyGPU()
if errors.Is(err, gpu.ErrDeviceLost) {
continue
}
return err
}
}
w.processFrame(d, frameStart, frame)
return nil
}
}
func (w *Window) render(frame *op.Ops, viewport image.Point) error {
if err := w.ctx.Lock(); err != nil {
return err
}
defer w.ctx.Unlock()
if runtime.GOOS == "js" {
// Use transparent black when Gio is embedded, to allow mixing of Gio and
// foreign content below.
w.gpu.Clear(color.NRGBA{A: 0x00, R: 0x00, G: 0x00, B: 0x00})
} else {
w.gpu.Clear(color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff})
}
target, err := w.ctx.RenderTarget()
if err != nil {
return err
}
if err := w.gpu.Frame(frame, target, viewport); err != nil {
return err
}
return w.ctx.Present()
}
func (w *Window) processFrame(d driver, frameStart time.Time, frame *op.Ops) {
w.queue.q.Frame(frame)
for k := range w.semantic.ids {
delete(w.semantic.ids, k)
}
w.semantic.uptodate = false
switch w.queue.q.TextInputState() {
case router.TextInputOpen:
d.ShowTextInput(true)
case router.TextInputClose:
d.ShowTextInput(false)
}
if hint, ok := w.queue.q.TextInputHint(); ok {
d.SetInputHint(hint)
}
if txt, ok := w.queue.q.WriteClipboard(); ok {
w.WriteClipboard(txt)
}
if w.queue.q.ReadClipboard() {
w.ReadClipboard()
}
if w.queue.q.Profiling() && w.gpu != nil {
frameDur := time.Since(frameStart)
frameDur = frameDur.Truncate(100 * time.Microsecond)
q := 100 * time.Microsecond
timings := fmt.Sprintf("tot:%7s %s", frameDur.Round(q), w.gpu.Profile())
w.queue.q.Queue(profile.Event{Timings: timings})
}
if t, ok := w.queue.q.WakeupTime(); ok {
w.setNextFrame(t)
}
w.updateAnimation(d)
}
// Invalidate the window such that a FrameEvent will be generated immediately.
// If the window is inactive, the event is sent when the window becomes active.
//
// Note that Invalidate is intended for externally triggered updates, such as a
// response from a network request. InvalidateOp is more efficient for animation
// and similar internal updates.
//
// Invalidate is safe for concurrent use.
func (w *Window) Invalidate() {
select {
case w.immediateRedraws <- struct{}{}:
return
default:
}
select {
case w.redraws <- struct{}{}:
w.wakeup()
default:
}
}
// Option applies the options to the window.
func (w *Window) Option(opts ...Option) {
w.driverDefer(func(d driver) {
d.Configure(opts)
})
}
// ReadClipboard initiates a read of the clipboard in the form
// of a clipboard.Event. Multiple reads may be coalesced
// to a single event.
func (w *Window) ReadClipboard() {
w.driverDefer(func(d driver) {
d.ReadClipboard()
})
}
// WriteClipboard writes a string to the clipboard.
func (w *Window) WriteClipboard(s string) {
w.driverDefer(func(d driver) {
d.WriteClipboard(s)
})
}
// SetCursorName changes the current window cursor to name.
func (w *Window) SetCursorName(name pointer.CursorName) {
w.driverDefer(func(d driver) {
d.SetCursor(name)
})
}
// Close the window. The window's event loop should exit when it receives
// system.DestroyEvent.
//
// Currently, only macOS, Windows, X11 and Wayland drivers implement this functionality,
// all others are stubbed.
func (w *Window) Close() {
w.driverDefer(func(d driver) {
d.Close()
})
}
// Maximize the window.
// Note: only implemented on Windows, macOS and X11.
func (w *Window) Maximize() {
w.driverDefer(func(d driver) {
d.Maximize()
})
}
// Center the window.
// Note: only implemented on Windows, macOS and X11.
func (w *Window) Center() {
w.driverDefer(func(d driver) {
d.Center()
})
}
// Run f in the same thread as the native window event loop, and wait for f to
// return or the window to close. Run is guaranteed not to deadlock if it is
// invoked during the handling of a ViewEvent, system.FrameEvent,
// system.StageEvent; call Run in a separate goroutine to avoid deadlock in all
// other cases.
//
// Note that most programs should not call Run; configuring a Window with
// CustomRenderer is a notable exception.
func (w *Window) Run(f func()) {
done := make(chan struct{})
w.driverDefer(func(d driver) {
defer close(done)
f()
})
select {
case <-done:
case <-w.dead:
}
}
// driverDefer is like Run but can be run from any context. It doesn't wait
// for f to return.
func (w *Window) driverDefer(f func(d driver)) {
select {
case w.driverFuncs <- f:
w.wakeup()
case <-w.dead:
}
}
func (w *Window) updateAnimation(d driver) {
animate := false
if w.stage >= system.StageRunning && w.hasNextFrame {
if dt := time.Until(w.nextFrame); dt <= 0 {
animate = true
} else {
// Schedule redraw.
select {
case <-w.scheduledRedraws:
default:
}
w.scheduledRedraws <- w.nextFrame
}
}
if animate != w.animating {
w.animating = animate
d.SetAnimating(animate)
}
}
func (w *Window) wakeup() {
select {
case w.wakeups <- struct{}{}:
default:
}
}
func (w *Window) setNextFrame(at time.Time) {
if !w.hasNextFrame || at.Before(w.nextFrame) {
w.hasNextFrame = true
w.nextFrame = at
}
}
func (c *callbacks) SetDriver(d driver) {
c.d = d
var wakeup func()
if d != nil {
wakeup = d.Wakeup
}
c.w.wakeupFuncs <- wakeup
}
func (c *callbacks) Event(e event.Event) {
if c.d == nil {
panic("event while no driver active")
}
c.w.processEvent(c.d, e)
c.w.updateState(c.d)
}
// SemanticRoot returns the ID of the semantic root.
func (c *callbacks) SemanticRoot() router.SemanticID {
c.w.updateSemantics()
return c.w.semantic.root
}
// LookupSemantic looks up a semantic node from an ID. The zero ID denotes the root.
func (c *callbacks) LookupSemantic(semID router.SemanticID) (router.SemanticNode, bool) {
c.w.updateSemantics()
n, found := c.w.semantic.ids[semID]
return n, found
}
func (c *callbacks) AppendSemanticDiffs(diffs []router.SemanticID) []router.SemanticID {
c.w.updateSemantics()
if tree := c.w.semantic.prevTree; len(tree) > 0 {
c.w.collectSemanticDiffs(&diffs, c.w.semantic.prevTree[0])
}
return diffs
}
func (c *callbacks) SemanticAt(pos f32.Point) (router.SemanticID, bool) {
c.w.updateSemantics()
return c.w.queue.q.SemanticAt(pos)
}
func (w *Window) waitAck(d driver) {
for {
select {
case f := <-w.driverFuncs:
f(d)
case w.out <- ackEvent:
// A dummy event went through, so we know the application has processed the previous event.
return
case <-w.immediateRedraws:
// Invalidate was called during frame processing.
w.setNextFrame(time.Time{})
}
}
}
func (w *Window) destroyGPU() {
if w.gpu != nil {
w.ctx.Lock()
w.gpu.Release()
w.ctx.Unlock()
w.gpu = nil
}
if w.ctx != nil {
w.ctx.Release()
w.ctx = nil
}
}
// waitFrame waits for the client to either call FrameEvent.Frame
// or to continue event handling. It returns whether the client
// called Frame or not.
func (w *Window) waitFrame(d driver) (*op.Ops, bool) {
for {
select {
case f := <-w.driverFuncs:
f(d)
case frame := <-w.frames:
// The client called FrameEvent.Frame.
return frame, true
case w.out <- ackEvent:
// The client ignored FrameEvent and continued processing
// events.
return nil, false
case <-w.immediateRedraws:
// Invalidate was called during frame processing.
w.setNextFrame(time.Time{})
}
}
}
// updateSemantics refreshes the semantics tree, the id to node map and the ids of
// updated nodes.
func (w *Window) updateSemantics() {
if w.semantic.uptodate {
return
}
w.semantic.uptodate = true
w.semantic.prevTree, w.semantic.tree = w.semantic.tree, w.semantic.prevTree
w.semantic.tree = w.queue.q.AppendSemantics(w.semantic.tree[:0])
w.semantic.root = w.semantic.tree[0].ID
for _, n := range w.semantic.tree {
w.semantic.ids[n.ID] = n
}
}
// collectSemanticDiffs traverses the previous semantic tree, noting changed nodes.
func (w *Window) collectSemanticDiffs(diffs *[]router.SemanticID, n router.SemanticNode) {
newNode, exists := w.semantic.ids[n.ID]
// Ignore deleted nodes, as their disappearance will be reported through an
// ancestor node.
if !exists {
return
}
diff := newNode.Desc != n.Desc || len(n.Children) != len(newNode.Children)
for i, ch := range n.Children {
if !diff {
newCh := newNode.Children[i]
diff = ch.ID != newCh.ID
}
w.collectSemanticDiffs(diffs, ch)
}
if diff {
*diffs = append(*diffs, n.ID)
}
}
func (w *Window) updateState(d driver) {
for {
select {
case f := <-w.driverFuncs:
f(d)
case <-w.redraws:
w.setNextFrame(time.Time{})
w.updateAnimation(d)
default:
return
}
}
}
func (w *Window) processEvent(d driver, e event.Event) {
select {
case <-w.dead:
return
default:
}
switch e2 := e.(type) {
case system.StageEvent:
if e2.Stage < system.StageRunning {
if w.gpu != nil {
w.ctx.Lock()
w.gpu.Release()
w.gpu = nil
w.ctx.Unlock()
}
}
w.stage = e2.Stage
w.updateAnimation(d)
w.out <- e
w.waitAck(d)
case frameEvent:
if e2.Size == (image.Point{}) {
panic(errors.New("internal error: zero-sized Draw"))
}
if w.stage < system.StageRunning {
// No drawing if not visible.
break
}
frameStart := time.Now()
w.hasNextFrame = false
e2.Frame = w.update
e2.Queue = &w.queue
w.out <- e2.FrameEvent
frame, gotFrame := w.waitFrame(d)
err := w.validateAndProcess(d, frameStart, e2.Size, e2.Sync, frame)
if gotFrame {
// We're done with frame, let the client continue.
w.frameAck <- struct{}{}
}
if err != nil {
w.destroyGPU()
w.out <- system.DestroyEvent{Err: err}
close(w.dead)
close(w.out)
break
}
w.updateCursor()
case *system.CommandEvent:
w.out <- e
w.waitAck(d)
case system.DestroyEvent:
w.destroyGPU()
w.out <- e2
close(w.dead)
close(w.out)
case ViewEvent:
w.out <- e2
w.waitAck(d)
case wakeupEvent:
case event.Event:
if w.queue.q.Queue(e2) {
w.setNextFrame(time.Time{})
w.updateAnimation(d)
}
w.updateCursor()
w.out <- e
}
}
func (w *Window) run(options []Option) {
if err := newWindow(&w.callbacks, options); err != nil {
w.out <- system.DestroyEvent{Err: err}
close(w.dead)
close(w.out)
return
}
var wakeup func()
var timer *time.Timer
for {
var (
wakeups <-chan struct{}
timeC <-chan time.Time
)
if wakeup != nil {
wakeups = w.wakeups
if timer != nil {
timeC = timer.C
}
}
select {
case t := <-w.scheduledRedraws:
if timer != nil {
timer.Stop()
}
timer = time.NewTimer(time.Until(t))
case <-w.dead:
return
case <-timeC:
select {
case w.redraws <- struct{}{}:
wakeup()
default:
}
case <-wakeups:
wakeup()
case wakeup = <-w.wakeupFuncs:
}
}
}
func (w *Window) updateCursor() {
if c := w.queue.q.Cursor(); c != w.cursor {
w.cursor = c
w.SetCursorName(c)
}
}
// Raise requests that the platform bring this window to the top of all open windows.
// Some platforms do not allow this except under certain circumstances, such as when
// a window from the same application already has focus. If the platform does not
// support it, this method will do nothing.
func (w *Window) Raise() {
w.driverDefer(func(d driver) {
d.Raise()
})
}
func (q *queue) Events(k event.Tag) []event.Event {
return q.q.Events(k)
}
// Title sets the title of the window.
func Title(t string) Option {
return func(_ unit.Metric, cnf *Config) {
cnf.Title = t
}
}
// Size sets the size of the window. The option is ignored
// in Fullscreen mode.
func Size(w, h unit.Value) Option {
if w.V <= 0 {
panic("width must be larger than or equal to 0")
}
if h.V <= 0 {
panic("height must be larger than or equal to 0")
}
return func(m unit.Metric, cnf *Config) {
cnf.Size = image.Point{
X: m.Px(w),
Y: m.Px(h),
}
}
}
// MaxSize sets the maximum size of the window.
func MaxSize(w, h unit.Value) Option {
if w.V <= 0 {
panic("width must be larger than or equal to 0")
}
if h.V <= 0 {
panic("height must be larger than or equal to 0")
}
return func(m unit.Metric, cnf *Config) {
cnf.MaxSize = image.Point{
X: m.Px(w),
Y: m.Px(h),
}
}
}
// MinSize sets the minimum size of the window.
func MinSize(w, h unit.Value) Option {
if w.V <= 0 {
panic("width must be larger than or equal to 0")
}
if h.V <= 0 {
panic("height must be larger than or equal to 0")
}
return func(m unit.Metric, cnf *Config) {
cnf.MinSize = image.Point{
X: m.Px(w),
Y: m.Px(h),
}
}
}
// StatusColor sets the color of the Android status bar.
func StatusColor(color color.NRGBA) Option {
return func(_ unit.Metric, cnf *Config) {
cnf.StatusColor = color
}
}
// NavigationColor sets the color of the navigation bar on Android, or the address bar in browsers.
func NavigationColor(color color.NRGBA) Option {
return func(_ unit.Metric, cnf *Config) {
cnf.NavigationColor = color
}
}
// CustomRenderer controls whether the window contents is
// rendered by the client. If true, no GPU context is created.
func CustomRenderer(custom bool) Option {
return func(_ unit.Metric, cnf *Config) {
cnf.CustomRenderer = custom
}
}
================================================
FILE: vendor/gioui.org/cpu/LICENSE
================================================
This project is provided under the terms of the UNLICENSE or
the MIT license denoted by the following SPDX identifier:
SPDX-License-Identifier: Unlicense OR MIT
You may use the project under the terms of either license.
Both licenses are reproduced below.
----
The MIT License (MIT)
Copyright (c) 2019 The Gio authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
---
The UNLICENSE
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to
---
================================================
FILE: vendor/gioui.org/cpu/README.md
================================================
# Compile and run compute programs on CPU
This projects contains the compiler for turning Vulkan SPIR-V compute shaders
into binaries for arm64, arm or amd64, using
[SwiftShader](https://github.com/eliasnaur/swiftshader) with a few
modifications. A runtime implemented in C and Go is available for running the
resulting binaries.
The primary use is to support a CPU-based rendering fallback for
[Gio](https://gioui.org). In particular, the `gioui.org/shader/piet` package
contains arm, arm64, amd64 binaries for
[piet-gpu](https://github.com/linebender/piet-gpu).
# Compiling and running shaders
The `init.sh` script clones the modifed SwiftShader projects and builds it for
64-bit and 32-bit. SwiftShader is not designed to cross-compile which is why a
32-bit build is needed to compile shaders for arm.
The `example/run.sh` script demonstrates compiling and running a simple compute
program.
## Issues and contributions
See the [Gio contribution guide](https://gioui.org/doc/contribute).
================================================
FILE: vendor/gioui.org/cpu/abi.h
================================================
// SPDX-License-Identifier: Unlicense OR MIT
#define ALIGN(bytes, type) type __attribute__((aligned(bytes)))
typedef ALIGN(8, uint8_t) byte8[8];
typedef ALIGN(8, uint16_t) word4[4];
typedef ALIGN(4, uint32_t) dword;
typedef ALIGN(16, uint32_t) dword4[4];
typedef ALIGN(8, uint64_t) qword;
typedef ALIGN(16, uint64_t) qword2[2];
typedef ALIGN(16, unsigned int) uint4[4];
typedef ALIGN(8, uint32_t) dword2[2];
typedef ALIGN(8, unsigned short) ushort4[4];
typedef ALIGN(16, float) float4[4];
typedef ALIGN(16, int) int4[4];
typedef unsigned short half;
typedef unsigned char bool;
enum {
MAX_BOUND_DESCRIPTOR_SETS = 4,
MAX_DESCRIPTOR_SET_UNIFORM_BUFFERS_DYNAMIC = 8,
MAX_DESCRIPTOR_SET_STORAGE_BUFFERS_DYNAMIC = 4,
MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC =
MAX_DESCRIPTOR_SET_UNIFORM_BUFFERS_DYNAMIC +
MAX_DESCRIPTOR_SET_STORAGE_BUFFERS_DYNAMIC,
MAX_PUSH_CONSTANT_SIZE = 128,
MIN_STORAGE_BUFFER_OFFSET_ALIGNMENT = 256,
REQUIRED_MEMORY_ALIGNMENT = 16,
SIMD_WIDTH = 4,
};
struct image_descriptor {
ALIGN(16, void *ptr);
int width;
int height;
int depth;
int row_pitch_bytes;
int slice_pitch_bytes;
int sample_pitch_bytes;
int sample_count;
int size_in_bytes;
void *stencil_ptr;
int stencil_row_pitch_bytes;
int stencil_slice_pitch_bytes;
int stencil_sample_pitch_bytes;
// TODO: unused?
void *memoryOwner;
};
struct buffer_descriptor {
ALIGN(16, void *ptr);
int size_in_bytes;
int robustness_size;
};
struct program_data {
uint8_t *descriptor_sets[MAX_BOUND_DESCRIPTOR_SETS];
uint32_t descriptor_dynamic_offsets[MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC];
uint4 num_workgroups;
uint4 workgroup_size;
uint32_t invocations_per_subgroup;
uint32_t subgroups_per_workgroup;
uint32_t invocations_per_workgroup;
unsigned char push_constants[MAX_PUSH_CONSTANT_SIZE];
// Unused.
void *constants;
};
typedef int32_t yield_result;
typedef void * coroutine;
typedef coroutine (*routine_begin)(struct program_data *data,
int32_t workgroupX,
int32_t workgroupY,
int32_t workgroupZ,
void *workgroupMemory,
int32_t firstSubgroup,
int32_t subgroupCount);
typedef bool (*routine_await)(coroutine r, yield_result *res);
typedef void (*routine_destroy)(coroutine r);
================================================
FILE: vendor/gioui.org/cpu/driver.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
//go:build linux && (arm64 || arm || amd64)
// +build linux
// +build arm64 arm amd64
package cpu
/*
#cgo CFLAGS: -std=c11 -D_POSIX_C_SOURCE=200112L
#include
#include
#include "abi.h"
#include "runtime.h"
*/
import "C"
import (
"unsafe"
)
type (
BufferDescriptor = C.struct_buffer_descriptor
ImageDescriptor = C.struct_image_descriptor
SamplerDescriptor = C.struct_sampler_descriptor
DispatchContext = C.struct_dispatch_context
ThreadContext = C.struct_thread_context
ProgramInfo = C.struct_program_info
)
const Supported = true
func NewBuffer(size int) BufferDescriptor {
return C.alloc_buffer(C.size_t(size))
}
func (d *BufferDescriptor) Data() []byte {
return (*(*[1 << 30]byte)(d.ptr))[:d.size_in_bytes:d.size_in_bytes]
}
func (d *BufferDescriptor) Free() {
if d.ptr != nil {
C.free(d.ptr)
}
*d = BufferDescriptor{}
}
func NewImageRGBA(width, height int) ImageDescriptor {
return C.alloc_image_rgba(C.int(width), C.int(height))
}
func (d *ImageDescriptor) Data() []byte {
return (*(*[1 << 30]byte)(d.ptr))[:d.size_in_bytes:d.size_in_bytes]
}
func (d *ImageDescriptor) Free() {
if d.ptr != nil {
C.free(d.ptr)
}
*d = ImageDescriptor{}
}
func NewDispatchContext() *DispatchContext {
return C.alloc_dispatch_context()
}
func (c *DispatchContext) Free() {
C.free_dispatch_context(c)
}
func (c *DispatchContext) Prepare(numThreads int, prog *ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
C.prepare_dispatch(c, C.int(numThreads), prog, (*C.uint8_t)(descSet), C.int(x), C.int(y), C.int(z))
}
func (c *DispatchContext) Dispatch(threadIdx int, ctx *ThreadContext) {
C.dispatch_thread(c, C.int(threadIdx), ctx)
}
func NewThreadContext() *ThreadContext {
return C.alloc_thread_context()
}
func (c *ThreadContext) Free() {
C.free_thread_context(c)
}
================================================
FILE: vendor/gioui.org/cpu/driver_nosupport.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !(linux && (arm64 || arm || amd64))
// +build !linux !arm64,!arm,!amd64
package cpu
import "unsafe"
type (
BufferDescriptor struct{}
ImageDescriptor struct{}
SamplerDescriptor struct{}
DispatchContext struct{}
ThreadContext struct{}
ProgramInfo struct{}
)
const Supported = false
func NewBuffer(size int) BufferDescriptor {
panic("unsupported")
}
func (d *BufferDescriptor) Data() []byte {
panic("unsupported")
}
func (d *BufferDescriptor) Free() {
}
func NewImageRGBA(width, height int) ImageDescriptor {
panic("unsupported")
}
func (d *ImageDescriptor) Data() []byte {
panic("unsupported")
}
func (d *ImageDescriptor) Free() {
}
func NewDispatchContext() *DispatchContext {
panic("unsupported")
}
func (c *DispatchContext) Free() {
}
func (c *DispatchContext) Prepare(numThreads int, prog *ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
panic("unsupported")
}
func (c *DispatchContext) Dispatch(threadIdx int, ctx *ThreadContext) {
panic("unsupported")
}
func NewThreadContext() *ThreadContext {
panic("unsupported")
}
func (c *ThreadContext) Free() {
}
================================================
FILE: vendor/gioui.org/cpu/embed.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package cpu
import _ "embed"
//go:embed abi.h
var ABIH []byte
//go:embed runtime.h
var RuntimeH []byte
================================================
FILE: vendor/gioui.org/cpu/go.mod
================================================
module gioui.org/cpu
go 1.17
================================================
FILE: vendor/gioui.org/cpu/go.sum
================================================
================================================
FILE: vendor/gioui.org/cpu/init.sh
================================================
#!/bin/sh
# SPDX-License-Identifier: Unlicense OR MIT
set -e
cd ~/.cache
git clone https://github.com/eliasnaur/swiftshader
cd swiftshader
# 32-bit build
cp -a build build.32bit
cd build.32bit
CXX=clang++ CC=clang CFLAGS=-m32 CXXFLAGS=-m32 cmake -DREACTOR_EMIT_ASM_FILE=true -DSWIFTSHADER_BUILD_PVR=false -DSWIFTSHADER_BUILD_TESTS=false -DSWIFTSHADER_BUILD_GLESv2=false -DSWIFTSHADER_BUILD_EGL=false -DSWIFTSHADER_BUILD_ANGLE=false ..
cmake --build . --parallel 4
cd ..
# 64-bit build
cp -a build build.64bit
cd build.64bit
CXX=clang++ CC=clang cmake -DREACTOR_EMIT_ASM_FILE=true -DSWIFTSHADER_BUILD_PVR=false -DSWIFTSHADER_BUILD_TESTS=false -DSWIFTSHADER_BUILD_GLESv2=false -DSWIFTSHADER_BUILD_EGL=false -DSWIFTSHADER_BUILD_ANGLE=false ..
cmake --build . --parallel 4
cd ..
================================================
FILE: vendor/gioui.org/cpu/runtime.c
================================================
// SPDX-License-Identifier: Unlicense OR MIT
//go:build linux && (arm64 || arm || amd64)
// +build linux
// +build arm64 arm amd64
#include
#include
#include
#include
#include
#include
#include "abi.h"
#include "runtime.h"
#include "_cgo_export.h"
// coroutines is a FIFO queue of coroutines implemented as a circular
// buffer.
struct coroutines {
coroutine *routines;
// start and end indexes into routines.
unsigned int start;
unsigned int end;
// cap is the capacity of routines.
unsigned int cap;
};
struct dispatch_context {
// descriptor_set is the aligned storage for the descriptor set.
void *descriptor_set;
int desc_set_size;
int nthreads;
bool has_cbarriers;
size_t memory_size;
// Program entrypoints.
routine_begin begin;
routine_await await;
routine_destroy destroy;
struct program_data data;
};
struct thread_context {
struct coroutines routines;
size_t memory_size;
uint8_t *memory;
};
static void *malloc_align(size_t alignment, size_t size) {
void *ptr;
int ret = posix_memalign(&ptr, alignment, size);
assert(ret == 0);
return ptr;
}
static void coroutines_dump(struct coroutines *routines) {
fprintf(stderr, "s: %d e: %d c: %d [", routines->start, routines->end, routines->cap);
unsigned int i = routines->start;
while (i != routines->end) {
fprintf(stderr, "%p,", routines->routines[routines->start]);
i = (i + 1)%routines->cap;
}
fprintf(stderr, "]\n");
}
static void coroutines_push(struct coroutines *routines, coroutine r) {
unsigned int next = routines->end + 1;
if (next >= routines->cap) {
next = 0;
}
if (next == routines->start) {
unsigned int newcap = routines->cap*2;
if (newcap < 10) {
newcap = 10;
}
routines->routines = realloc(routines->routines, newcap*sizeof(coroutine));
// Move elements wrapped around the old cap to the newly allocated space.
if (routines->end < routines->start) {
unsigned int nelems = routines->end;
unsigned int max = newcap - routines->cap;
// We doubled the space above, so we can assume enough room.
assert(nelems <= max);
memmove(&routines->routines[routines->cap], &routines->routines[0], nelems*sizeof(coroutine));
routines->end += routines->cap;
}
routines->cap = newcap;
next = (routines->end + 1)%routines->cap;
}
routines->routines[routines->end] = r;
routines->end = next;
}
static bool coroutines_pop(struct coroutines *routines, coroutine *r) {
if (routines->start == routines->end) {
return 0;
}
*r = routines->routines[routines->start];
routines->start = (routines->start + 1)%routines->cap;
return 1;
}
static void coroutines_free(struct coroutines *routines) {
if (routines->routines != NULL) {
free(routines->routines);
}
struct coroutines clr = { 0 }; *routines = clr;
}
struct dispatch_context *alloc_dispatch_context(void) {
struct dispatch_context *c = malloc(sizeof(*c));
assert(c != NULL);
struct dispatch_context clr = { 0 }; *c = clr;
return c;
}
void free_dispatch_context(struct dispatch_context *c) {
if (c->descriptor_set != NULL) {
free(c->descriptor_set);
c->descriptor_set = NULL;
}
}
struct thread_context *alloc_thread_context(void) {
struct thread_context *c = malloc(sizeof(*c));
assert(c != NULL);
struct thread_context clr = { 0 }; *c = clr;
return c;
}
void free_thread_context(struct thread_context *c) {
if (c->memory != NULL) {
free(c->memory);
}
coroutines_free(&c->routines);
struct thread_context clr = { 0 }; *c = clr;
}
struct buffer_descriptor alloc_buffer(size_t size) {
void *buf = malloc_align(MIN_STORAGE_BUFFER_OFFSET_ALIGNMENT, size);
struct buffer_descriptor desc = {
.ptr = buf,
.size_in_bytes = size,
.robustness_size = size,
};
return desc;
}
struct image_descriptor alloc_image_rgba(int width, int height) {
size_t size = width*height*4;
size = (size + 16 - 1)&~(16 - 1);
void *storage = malloc_align(REQUIRED_MEMORY_ALIGNMENT, size);
struct image_descriptor desc = { 0 };
desc.ptr = storage;
desc.width = width;
desc.height = height;
desc.depth = 1;
desc.row_pitch_bytes = width*4;
desc.slice_pitch_bytes = size;
desc.sample_pitch_bytes = size;
desc.sample_count = 1;
desc.size_in_bytes = size;
return desc;
}
void prepare_dispatch(struct dispatch_context *ctx, int nthreads, struct program_info *info, uint8_t *desc_set, int ngroupx, int ngroupy, int ngroupz) {
if (ctx->desc_set_size < info->desc_set_size) {
if (ctx->descriptor_set != NULL) {
free(ctx->descriptor_set);
}
ctx->descriptor_set = malloc_align(16, info->desc_set_size);
ctx->desc_set_size = info->desc_set_size;
}
memcpy(ctx->descriptor_set, desc_set, info->desc_set_size);
int invocations_per_subgroup = SIMD_WIDTH;
int invocations_per_workgroup = info->workgroup_size_x * info->workgroup_size_y * info->workgroup_size_z;
int subgroups_per_workgroup = (invocations_per_workgroup + invocations_per_subgroup - 1) / invocations_per_subgroup;
ctx->has_cbarriers = info->has_cbarriers;
ctx->begin = info->begin;
ctx->await = info->await;
ctx->destroy = info->destroy;
ctx->nthreads = nthreads;
ctx->memory_size = info->min_memory_size;
ctx->data.workgroup_size[0] = info->workgroup_size_x;
ctx->data.workgroup_size[1] = info->workgroup_size_y;
ctx->data.workgroup_size[2] = info->workgroup_size_z;
ctx->data.num_workgroups[0] = ngroupx;
ctx->data.num_workgroups[1] = ngroupy;
ctx->data.num_workgroups[2] = ngroupz;
ctx->data.invocations_per_subgroup = invocations_per_subgroup;
ctx->data.invocations_per_workgroup = invocations_per_workgroup;
ctx->data.subgroups_per_workgroup = subgroups_per_workgroup;
ctx->data.descriptor_sets[0] = ctx->descriptor_set;
}
void dispatch_thread(struct dispatch_context *ctx, int thread_idx, struct thread_context *thread) {
if (thread->memory_size < ctx->memory_size) {
if (thread->memory != NULL) {
free(thread->memory);
}
// SwiftShader doesn't seem to align shared memory. However, better safe
// than subtle errors. Note that the program info generator pads
// memory_size to ensure space for alignment.
thread->memory = malloc_align(16, ctx->memory_size);
thread->memory_size = ctx->memory_size;
}
uint8_t *memory = thread->memory;
struct program_data *data = &ctx->data;
int sx = data->num_workgroups[0];
int sy = data->num_workgroups[1];
int sz = data->num_workgroups[2];
int ngroups = sx * sy * sz;
for (int i = thread_idx; i < ngroups; i += ctx->nthreads) {
int group_id = i;
int z = group_id / (sx * sy);
group_id -= z * sx * sy;
int y = group_id / sx;
group_id -= y * sx;
int x = group_id;
if (ctx->has_cbarriers) {
for (int subgroup = 0; subgroup < data->subgroups_per_workgroup; subgroup++) {
coroutine r = ctx->begin(data, x, y, z, memory, subgroup, 1);
coroutines_push(&thread->routines, r);
}
} else {
coroutine r = ctx->begin(data, x, y, z, memory, 0, data->subgroups_per_workgroup);
coroutines_push(&thread->routines, r);
}
coroutine r;
while (coroutines_pop(&thread->routines, &r)) {
yield_result res;
if (ctx->await(r, &res)) {
coroutines_push(&thread->routines, r);
} else {
ctx->destroy(r);
}
}
}
}
================================================
FILE: vendor/gioui.org/cpu/runtime.h
================================================
// SPDX-License-Identifier: Unlicense OR MIT
#define ATTR_HIDDEN __attribute__ ((visibility ("hidden")))
// program_info contains constant parameters for a program.
struct program_info {
// MinMemorySize is the minimum size of memory passed to dispatch.
size_t min_memory_size;
// has_cbarriers is 1 when the program contains control barriers.
bool has_cbarriers;
// desc_set_size is the size of the first descriptor set for the program.
size_t desc_set_size;
int workgroup_size_x;
int workgroup_size_y;
int workgroup_size_z;
// Program entrypoints.
routine_begin begin;
routine_await await;
routine_destroy destroy;
};
// dispatch_context contains the information a program dispatch.
struct dispatch_context;
// thread_context contains the working memory of a batch. It may be
// reused, but not concurrently.
struct thread_context;
extern struct buffer_descriptor alloc_buffer(size_t size) ATTR_HIDDEN;
extern struct image_descriptor alloc_image_rgba(int width, int height) ATTR_HIDDEN;
extern struct dispatch_context *alloc_dispatch_context(void) ATTR_HIDDEN;
extern void free_dispatch_context(struct dispatch_context *c) ATTR_HIDDEN;
extern struct thread_context *alloc_thread_context(void) ATTR_HIDDEN;
extern void free_thread_context(struct thread_context *c) ATTR_HIDDEN;
// prepare_dispatch initializes ctx to run a dispatch of a program distributed
// among nthreads threads.
extern void prepare_dispatch(struct dispatch_context *ctx, int nthreads, struct program_info *info, uint8_t *desc_set, int ngroupx, int ngroupy, int ngroupz) ATTR_HIDDEN;
// dispatch_batch executes a dispatch batch.
extern void dispatch_thread(struct dispatch_context *ctx, int thread_idx, struct thread_context *thread) ATTR_HIDDEN;
================================================
FILE: vendor/gioui.org/f32/affine.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package f32
import (
"fmt"
"math"
)
// Affine2D represents an affine 2D transformation. The zero value if Affine2D
// represents the identity transform.
type Affine2D struct {
// in order to make the zero value of Affine2D represent the identity
// transform we store it with the identity matrix subtracted, that is
// if the actual transformation matrix is:
// [sx, hx, ox]
// [hy, sy, oy]
// [ 0, 0, 1]
// we store a = sx-1 and e = sy-1
a, b, c float32
d, e, f float32
}
// NewAffine2D creates a new Affine2D transform from the matrix elements
// in row major order. The rows are: [sx, hx, ox], [hy, sy, oy], [0, 0, 1].
func NewAffine2D(sx, hx, ox, hy, sy, oy float32) Affine2D {
return Affine2D{
a: sx - 1, b: hx, c: ox,
d: hy, e: sy - 1, f: oy,
}
}
// Offset the transformation.
func (a Affine2D) Offset(offset Point) Affine2D {
return Affine2D{
a.a, a.b, a.c + offset.X,
a.d, a.e, a.f + offset.Y,
}
}
// Scale the transformation around the given origin.
func (a Affine2D) Scale(origin, factor Point) Affine2D {
if origin == (Point{}) {
return a.scale(factor)
}
a = a.Offset(origin.Mul(-1))
a = a.scale(factor)
return a.Offset(origin)
}
// Rotate the transformation by the given angle (in radians) counter clockwise around the given origin.
func (a Affine2D) Rotate(origin Point, radians float32) Affine2D {
if origin == (Point{}) {
return a.rotate(radians)
}
a = a.Offset(origin.Mul(-1))
a = a.rotate(radians)
return a.Offset(origin)
}
// Shear the transformation by the given angle (in radians) around the given origin.
func (a Affine2D) Shear(origin Point, radiansX, radiansY float32) Affine2D {
if origin == (Point{}) {
return a.shear(radiansX, radiansY)
}
a = a.Offset(origin.Mul(-1))
a = a.shear(radiansX, radiansY)
return a.Offset(origin)
}
// Mul returns A*B.
func (A Affine2D) Mul(B Affine2D) (r Affine2D) {
r.a = (A.a+1)*(B.a+1) + A.b*B.d - 1
r.b = (A.a+1)*B.b + A.b*(B.e+1)
r.c = (A.a+1)*B.c + A.b*B.f + A.c
r.d = A.d*(B.a+1) + (A.e+1)*B.d
r.e = A.d*B.b + (A.e+1)*(B.e+1) - 1
r.f = A.d*B.c + (A.e+1)*B.f + A.f
return r
}
// Invert the transformation. Note that if the matrix is close to singular
// numerical errors may become large or infinity.
func (a Affine2D) Invert() Affine2D {
if a.a == 0 && a.b == 0 && a.d == 0 && a.e == 0 {
return Affine2D{a: 0, b: 0, c: -a.c, d: 0, e: 0, f: -a.f}
}
a.a += 1
a.e += 1
det := a.a*a.e - a.b*a.d
a.a, a.e = a.e/det, a.a/det
a.b, a.d = -a.b/det, -a.d/det
temp := a.c
a.c = -a.a*a.c - a.b*a.f
a.f = -a.d*temp - a.e*a.f
a.a -= 1
a.e -= 1
return a
}
// Transform p by returning a*p.
func (a Affine2D) Transform(p Point) Point {
return Point{
X: p.X*(a.a+1) + p.Y*a.b + a.c,
Y: p.X*a.d + p.Y*(a.e+1) + a.f,
}
}
// Elems returns the matrix elements of the transform in row-major order. The
// rows are: [sx, hx, ox], [hy, sy, oy], [0, 0, 1].
func (a Affine2D) Elems() (sx, hx, ox, hy, sy, oy float32) {
return a.a + 1, a.b, a.c, a.d, a.e + 1, a.f
}
func (a Affine2D) scale(factor Point) Affine2D {
return Affine2D{
(a.a+1)*factor.X - 1, a.b * factor.X, a.c * factor.X,
a.d * factor.Y, (a.e+1)*factor.Y - 1, a.f * factor.Y,
}
}
func (a Affine2D) rotate(radians float32) Affine2D {
sin, cos := math.Sincos(float64(radians))
s, c := float32(sin), float32(cos)
return Affine2D{
(a.a+1)*c - a.d*s - 1, a.b*c - (a.e+1)*s, a.c*c - a.f*s,
(a.a+1)*s + a.d*c, a.b*s + (a.e+1)*c - 1, a.c*s + a.f*c,
}
}
func (a Affine2D) shear(radiansX, radiansY float32) Affine2D {
tx := float32(math.Tan(float64(radiansX)))
ty := float32(math.Tan(float64(radiansY)))
return Affine2D{
(a.a + 1) + a.d*tx - 1, a.b + (a.e+1)*tx, a.c + a.f*tx,
(a.a+1)*ty + a.d, a.b*ty + (a.e + 1) - 1, a.f*ty + a.f,
}
}
func (a Affine2D) String() string {
sx, hx, ox, hy, sy, oy := a.Elems()
return fmt.Sprintf("[[%f %f %f] [%f %f %f]]", sx, hx, ox, hy, sy, oy)
}
================================================
FILE: vendor/gioui.org/f32/f32.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package f32 is a float32 implementation of package image's
Point and Rectangle.
The coordinate space has the origin in the top left
corner with the axes extending right and down.
*/
package f32
import "strconv"
// A Point is a two dimensional point.
type Point struct {
X, Y float32
}
// String return a string representation of p.
func (p Point) String() string {
return "(" + strconv.FormatFloat(float64(p.X), 'f', -1, 32) +
"," + strconv.FormatFloat(float64(p.Y), 'f', -1, 32) + ")"
}
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
// Min.Y <= Y < Max.Y.
type Rectangle struct {
Min, Max Point
}
// String return a string representation of r.
func (r Rectangle) String() string {
return r.Min.String() + "-" + r.Max.String()
}
// Rect is a shorthand for Rectangle{Point{x0, y0}, Point{x1, y1}}.
// The returned Rectangle has x0 and y0 swapped if necessary so that
// it's correctly formed.
func Rect(x0, y0, x1, y1 float32) Rectangle {
if x0 > x1 {
x0, x1 = x1, x0
}
if y0 > y1 {
y0, y1 = y1, y0
}
return Rectangle{Point{x0, y0}, Point{x1, y1}}
}
// Pt is shorthand for Point{X: x, Y: y}.
func Pt(x, y float32) Point {
return Point{X: x, Y: y}
}
// Add return the point p+p2.
func (p Point) Add(p2 Point) Point {
return Point{X: p.X + p2.X, Y: p.Y + p2.Y}
}
// Sub returns the vector p-p2.
func (p Point) Sub(p2 Point) Point {
return Point{X: p.X - p2.X, Y: p.Y - p2.Y}
}
// Mul returns p scaled by s.
func (p Point) Mul(s float32) Point {
return Point{X: p.X * s, Y: p.Y * s}
}
// Div returns the vector p/s.
func (p Point) Div(s float32) Point {
return Point{X: p.X / s, Y: p.Y / s}
}
// In reports whether p is in r.
func (p Point) In(r Rectangle) bool {
return r.Min.X <= p.X && p.X < r.Max.X &&
r.Min.Y <= p.Y && p.Y < r.Max.Y
}
// Size returns r's width and height.
func (r Rectangle) Size() Point {
return Point{X: r.Dx(), Y: r.Dy()}
}
// Dx returns r's width.
func (r Rectangle) Dx() float32 {
return r.Max.X - r.Min.X
}
// Dy returns r's Height.
func (r Rectangle) Dy() float32 {
return r.Max.Y - r.Min.Y
}
// Intersect returns the intersection of r and s.
func (r Rectangle) Intersect(s Rectangle) Rectangle {
if r.Min.X < s.Min.X {
r.Min.X = s.Min.X
}
if r.Min.Y < s.Min.Y {
r.Min.Y = s.Min.Y
}
if r.Max.X > s.Max.X {
r.Max.X = s.Max.X
}
if r.Max.Y > s.Max.Y {
r.Max.Y = s.Max.Y
}
if r.Empty() {
return Rectangle{}
}
return r
}
// Union returns the union of r and s.
func (r Rectangle) Union(s Rectangle) Rectangle {
if r.Empty() {
return s
}
if s.Empty() {
return r
}
if r.Min.X > s.Min.X {
r.Min.X = s.Min.X
}
if r.Min.Y > s.Min.Y {
r.Min.Y = s.Min.Y
}
if r.Max.X < s.Max.X {
r.Max.X = s.Max.X
}
if r.Max.Y < s.Max.Y {
r.Max.Y = s.Max.Y
}
return r
}
// Canon returns the canonical version of r, where Min is to
// the upper left of Max.
func (r Rectangle) Canon() Rectangle {
if r.Max.X < r.Min.X {
r.Min.X, r.Max.X = r.Max.X, r.Min.X
}
if r.Max.Y < r.Min.Y {
r.Min.Y, r.Max.Y = r.Max.Y, r.Min.Y
}
return r
}
// Empty reports whether r represents the empty area.
func (r Rectangle) Empty() bool {
return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y
}
// Add offsets r with the vector p.
func (r Rectangle) Add(p Point) Rectangle {
return Rectangle{
Point{r.Min.X + p.X, r.Min.Y + p.Y},
Point{r.Max.X + p.X, r.Max.Y + p.Y},
}
}
// Sub offsets r with the vector -p.
func (r Rectangle) Sub(p Point) Rectangle {
return Rectangle{
Point{r.Min.X - p.X, r.Min.Y - p.Y},
Point{r.Max.X - p.X, r.Max.Y - p.Y},
}
}
================================================
FILE: vendor/gioui.org/font/opentype/opentype.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
// Package opentype implements text layout and shaping for OpenType
// files.
package opentype
import (
"bytes"
"io"
"unicode"
"unicode/utf8"
"golang.org/x/image/font"
"golang.org/x/image/font/sfnt"
"golang.org/x/image/math/fixed"
"gioui.org/f32"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/text"
)
// Font implements text.Face. Its methods are safe to use
// concurrently.
type Font struct {
font *sfnt.Font
}
// Collection is a collection of one or more fonts. When used as a text.Face,
// each rune will be assigned a glyph from the first font in the collection
// that supports it.
type Collection struct {
fonts []*opentype
}
type opentype struct {
Font *sfnt.Font
Hinting font.Hinting
}
// a glyph represents a rune and its advance according to a Font.
// TODO: remove this type and work on io.Readers directly.
type glyph struct {
Rune rune
Advance fixed.Int26_6
}
// NewFont parses an SFNT font, such as TTF or OTF data, from a []byte
// data source.
func Parse(src []byte) (*Font, error) {
fnt, err := sfnt.Parse(src)
if err != nil {
return nil, err
}
return &Font{font: fnt}, nil
}
// ParseCollection parses an SFNT font collection, such as TTC or OTC data,
// from a []byte data source.
//
// If passed data for a single font, a TTF or OTF instead of a TTC or OTC,
// it will return a collection containing 1 font.
func ParseCollection(src []byte) (*Collection, error) {
c, err := sfnt.ParseCollection(src)
if err != nil {
return nil, err
}
return newCollectionFrom(c)
}
// ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data,
// from an io.ReaderAt data source.
//
// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
// will return a collection containing 1 font.
func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
c, err := sfnt.ParseCollectionReaderAt(src)
if err != nil {
return nil, err
}
return newCollectionFrom(c)
}
func newCollectionFrom(coll *sfnt.Collection) (*Collection, error) {
fonts := make([]*opentype, coll.NumFonts())
for i := range fonts {
fnt, err := coll.Font(i)
if err != nil {
return nil, err
}
fonts[i] = &opentype{
Font: fnt,
Hinting: font.HintingFull,
}
}
return &Collection{fonts: fonts}, nil
}
// NumFonts returns the number of fonts in the collection.
func (c *Collection) NumFonts() int {
return len(c.fonts)
}
// Font returns the i'th font in the collection.
func (c *Collection) Font(i int) (*Font, error) {
if i < 0 || len(c.fonts) <= i {
return nil, sfnt.ErrNotFound
}
return &Font{font: c.fonts[i].Font}, nil
}
func (f *Font) Layout(ppem fixed.Int26_6, maxWidth int, txt io.Reader) ([]text.Line, error) {
glyphs, err := readGlyphs(txt)
if err != nil {
return nil, err
}
fonts := []*opentype{{Font: f.font, Hinting: font.HintingFull}}
var buf sfnt.Buffer
return layoutText(&buf, ppem, maxWidth, fonts, glyphs)
}
func (f *Font) Shape(ppem fixed.Int26_6, str text.Layout) clip.PathSpec {
var buf sfnt.Buffer
return textPath(&buf, ppem, []*opentype{{Font: f.font, Hinting: font.HintingFull}}, str)
}
func (f *Font) Metrics(ppem fixed.Int26_6) font.Metrics {
o := &opentype{Font: f.font, Hinting: font.HintingFull}
var buf sfnt.Buffer
return o.Metrics(&buf, ppem)
}
func (c *Collection) Layout(ppem fixed.Int26_6, maxWidth int, txt io.Reader) ([]text.Line, error) {
glyphs, err := readGlyphs(txt)
if err != nil {
return nil, err
}
var buf sfnt.Buffer
return layoutText(&buf, ppem, maxWidth, c.fonts, glyphs)
}
func (c *Collection) Shape(ppem fixed.Int26_6, str text.Layout) clip.PathSpec {
var buf sfnt.Buffer
return textPath(&buf, ppem, c.fonts, str)
}
func fontForGlyph(buf *sfnt.Buffer, fonts []*opentype, r rune) *opentype {
if len(fonts) < 1 {
return nil
}
for _, f := range fonts {
if f.HasGlyph(buf, r) {
return f
}
}
return fonts[0] // Use replacement character from the first font if necessary
}
func layoutText(sbuf *sfnt.Buffer, ppem fixed.Int26_6, maxWidth int, fonts []*opentype, glyphs []glyph) ([]text.Line, error) {
var lines []text.Line
var nextLine text.Line
updateBounds := func(f *opentype) {
m := f.Metrics(sbuf, ppem)
if m.Ascent > nextLine.Ascent {
nextLine.Ascent = m.Ascent
}
// m.Height is equal to m.Ascent + m.Descent + linegap.
// Compute the descent including the linegap.
descent := m.Height - m.Ascent
if descent > nextLine.Descent {
nextLine.Descent = descent
}
b := f.Bounds(sbuf, ppem)
nextLine.Bounds = nextLine.Bounds.Union(b)
}
maxDotX := fixed.I(maxWidth)
type state struct {
r rune
f *opentype
adv fixed.Int26_6
x fixed.Int26_6
idx int
len int
valid bool
}
var prev, word state
endLine := func() {
if prev.f == nil && len(fonts) > 0 {
prev.f = fonts[0]
}
updateBounds(prev.f)
nextLine.Layout = toLayout(glyphs[:prev.idx:prev.idx])
nextLine.Width = prev.x + prev.adv
nextLine.Bounds.Max.X += prev.x
lines = append(lines, nextLine)
glyphs = glyphs[prev.idx:]
nextLine = text.Line{}
prev = state{}
word = state{}
}
for prev.idx < len(glyphs) {
g := &glyphs[prev.idx]
next := state{
r: g.Rune,
f: fontForGlyph(sbuf, fonts, g.Rune),
idx: prev.idx + 1,
len: prev.len + utf8.RuneLen(g.Rune),
x: prev.x + prev.adv,
}
if next.f != nil {
if next.f != prev.f {
updateBounds(next.f)
}
next.adv, next.valid = next.f.GlyphAdvance(sbuf, ppem, g.Rune)
}
if g.Rune == '\n' {
// The newline is zero width; use the previous
// character for line measurements.
prev.idx = next.idx
prev.len = next.len
endLine()
continue
}
var k fixed.Int26_6
if prev.valid && next.f != nil {
k = next.f.Kern(sbuf, ppem, prev.r, next.r)
}
// Break the line if we're out of space.
if prev.idx > 0 && next.x+next.adv+k > maxDotX {
// If the line contains no word breaks, break off the last rune.
if word.idx == 0 {
word = prev
}
next.x -= word.x + word.adv
next.idx -= word.idx
next.len -= word.len
prev = word
endLine()
} else if k != 0 {
glyphs[prev.idx-1].Advance += k
next.x += k
}
g.Advance = next.adv
if unicode.IsSpace(g.Rune) {
word = next
}
prev = next
}
endLine()
return lines, nil
}
// toLayout converts a slice of glyphs to a text.Layout.
func toLayout(glyphs []glyph) text.Layout {
var buf bytes.Buffer
advs := make([]fixed.Int26_6, len(glyphs))
for i, g := range glyphs {
buf.WriteRune(g.Rune)
advs[i] = glyphs[i].Advance
}
return text.Layout{Text: buf.String(), Advances: advs}
}
func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, fonts []*opentype, str text.Layout) clip.PathSpec {
var lastPos f32.Point
var builder clip.Path
var x fixed.Int26_6
builder.Begin(new(op.Ops))
rune := 0
for _, r := range str.Text {
if !unicode.IsSpace(r) {
f := fontForGlyph(buf, fonts, r)
if f == nil {
continue
}
segs, ok := f.LoadGlyph(buf, ppem, r)
if !ok {
continue
}
// Move to glyph position.
pos := f32.Point{
X: float32(x) / 64,
}
builder.Move(pos.Sub(lastPos))
lastPos = pos
var lastArg f32.Point
// Convert sfnt.Segments to relative segments.
for _, fseg := range segs {
nargs := 1
switch fseg.Op {
case sfnt.SegmentOpQuadTo:
nargs = 2
case sfnt.SegmentOpCubeTo:
nargs = 3
}
var args [3]f32.Point
for i := 0; i < nargs; i++ {
a := f32.Point{
X: float32(fseg.Args[i].X) / 64,
Y: float32(fseg.Args[i].Y) / 64,
}
args[i] = a.Sub(lastArg)
if i == nargs-1 {
lastArg = a
}
}
switch fseg.Op {
case sfnt.SegmentOpMoveTo:
builder.Move(args[0])
case sfnt.SegmentOpLineTo:
builder.Line(args[0])
case sfnt.SegmentOpQuadTo:
builder.Quad(args[0], args[1])
case sfnt.SegmentOpCubeTo:
builder.Cube(args[0], args[1], args[2])
default:
panic("unsupported segment op")
}
}
lastPos = lastPos.Add(lastArg)
}
x += str.Advances[rune]
rune++
}
return builder.End()
}
func readGlyphs(r io.Reader) ([]glyph, error) {
var glyphs []glyph
buf := make([]byte, 0, 1024)
for {
n, err := r.Read(buf[len(buf):cap(buf)])
buf = buf[:len(buf)+n]
lim := len(buf)
// Read full runes if possible.
if err != io.EOF {
lim -= utf8.UTFMax - 1
}
i := 0
for i < lim {
c, s := utf8.DecodeRune(buf[i:])
i += s
glyphs = append(glyphs, glyph{Rune: c})
}
n = copy(buf, buf[i:])
buf = buf[:n]
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
}
return glyphs, nil
}
func (f *opentype) HasGlyph(buf *sfnt.Buffer, r rune) bool {
g, err := f.Font.GlyphIndex(buf, r)
return g != 0 && err == nil
}
func (f *opentype) GlyphAdvance(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) (advance fixed.Int26_6, ok bool) {
g, err := f.Font.GlyphIndex(buf, r)
if err != nil {
return 0, false
}
adv, err := f.Font.GlyphAdvance(buf, g, ppem, f.Hinting)
return adv, err == nil
}
func (f *opentype) Kern(buf *sfnt.Buffer, ppem fixed.Int26_6, r0, r1 rune) fixed.Int26_6 {
g0, err := f.Font.GlyphIndex(buf, r0)
if err != nil {
return 0
}
g1, err := f.Font.GlyphIndex(buf, r1)
if err != nil {
return 0
}
adv, err := f.Font.Kern(buf, g0, g1, ppem, f.Hinting)
if err != nil {
return 0
}
return adv
}
func (f *opentype) Metrics(buf *sfnt.Buffer, ppem fixed.Int26_6) font.Metrics {
m, _ := f.Font.Metrics(buf, ppem, f.Hinting)
return m
}
func (f *opentype) Bounds(buf *sfnt.Buffer, ppem fixed.Int26_6) fixed.Rectangle26_6 {
r, _ := f.Font.Bounds(buf, ppem, f.Hinting)
return r
}
func (f *opentype) LoadGlyph(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) ([]sfnt.Segment, bool) {
g, err := f.Font.GlyphIndex(buf, r)
if err != nil {
return nil, false
}
segs, err := f.Font.LoadGlyph(buf, g, ppem, nil)
if err != nil {
return nil, false
}
return segs, true
}
================================================
FILE: vendor/gioui.org/gesture/gesture.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package gesture implements common pointer gestures.
Gestures accept low level pointer Events from an event
Queue and detect higher level actions such as clicks
and scrolling.
*/
package gesture
import (
"image"
"math"
"runtime"
"time"
"gioui.org/f32"
"gioui.org/internal/fling"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/op"
"gioui.org/unit"
)
// The duration is somewhat arbitrary.
const doubleClickDuration = 200 * time.Millisecond
// Hover detects the hover gesture for a pointer area.
type Hover struct {
// entered tracks whether the pointer is inside the gesture.
entered bool
// pid is the pointer.ID.
pid pointer.ID
}
// Add the gesture to detect hovering over the current pointer area.
func (h *Hover) Add(ops *op.Ops) {
pointer.InputOp{
Tag: h,
Types: pointer.Enter | pointer.Leave,
}.Add(ops)
}
// Hovered returns whether a pointer is inside the area.
func (h *Hover) Hovered(q event.Queue) bool {
for _, ev := range q.Events(h) {
e, ok := ev.(pointer.Event)
if !ok {
continue
}
switch e.Type {
case pointer.Leave:
if h.entered && h.pid == e.PointerID {
h.entered = false
}
case pointer.Enter:
if !h.entered {
h.pid = e.PointerID
}
if h.pid == e.PointerID {
h.entered = true
}
}
}
return h.entered
}
// Click detects click gestures in the form
// of ClickEvents.
type Click struct {
// clickedAt is the timestamp at which
// the last click occurred.
clickedAt time.Duration
// clicks is incremented if successive clicks
// are performed within a fixed duration.
clicks int
// pressed tracks whether the pointer is pressed.
pressed bool
// entered tracks whether the pointer is inside the gesture.
entered bool
// pid is the pointer.ID.
pid pointer.ID
}
// ClickEvent represent a click action, either a
// TypePress for the beginning of a click or a
// TypeClick for a completed click.
type ClickEvent struct {
Type ClickType
Position f32.Point
Source pointer.Source
Modifiers key.Modifiers
// NumClicks records successive clicks occurring
// within a short duration of each other.
NumClicks int
}
type ClickType uint8
// Drag detects drag gestures in the form of pointer.Drag events.
type Drag struct {
dragging bool
pressed bool
pid pointer.ID
start f32.Point
grab bool
}
// Scroll detects scroll gestures and reduces them to
// scroll distances. Scroll recognizes mouse wheel
// movements as well as drag and fling touch gestures.
type Scroll struct {
dragging bool
axis Axis
estimator fling.Extrapolation
flinger fling.Animation
pid pointer.ID
grab bool
last int
// Leftover scroll.
scroll float32
}
type ScrollState uint8
type Axis uint8
const (
Horizontal Axis = iota
Vertical
Both
)
const (
// TypePress is reported for the first pointer
// press.
TypePress ClickType = iota
// TypeClick is reported when a click action
// is complete.
TypeClick
// TypeCancel is reported when the gesture is
// cancelled.
TypeCancel
)
const (
// StateIdle is the default scroll state.
StateIdle ScrollState = iota
// StateDragging is reported during drag gestures.
StateDragging
// StateFlinging is reported when a fling is
// in progress.
StateFlinging
)
var touchSlop = unit.Dp(3)
// Add the handler to the operation list to receive click events.
func (c *Click) Add(ops *op.Ops) {
pointer.InputOp{
Tag: c,
Types: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave,
}.Add(ops)
}
// Hovered returns whether a pointer is inside the area.
func (c *Click) Hovered() bool {
return c.entered
}
// Pressed returns whether a pointer is pressing.
func (c *Click) Pressed() bool {
return c.pressed
}
// Events returns the next click events, if any.
func (c *Click) Events(q event.Queue) []ClickEvent {
var events []ClickEvent
for _, evt := range q.Events(c) {
e, ok := evt.(pointer.Event)
if !ok {
continue
}
switch e.Type {
case pointer.Release:
if !c.pressed || c.pid != e.PointerID {
break
}
c.pressed = false
if c.entered {
if e.Time-c.clickedAt < doubleClickDuration {
c.clicks++
} else {
c.clicks = 1
}
c.clickedAt = e.Time
events = append(events, ClickEvent{Type: TypeClick, Position: e.Position, Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
} else {
events = append(events, ClickEvent{Type: TypeCancel})
}
case pointer.Cancel:
wasPressed := c.pressed
c.pressed = false
c.entered = false
if wasPressed {
events = append(events, ClickEvent{Type: TypeCancel})
}
case pointer.Press:
if c.pressed {
break
}
if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonPrimary {
break
}
if !c.entered {
c.pid = e.PointerID
}
if c.pid != e.PointerID {
break
}
c.pressed = true
events = append(events, ClickEvent{Type: TypePress, Position: e.Position, Source: e.Source, Modifiers: e.Modifiers})
case pointer.Leave:
if !c.pressed {
c.pid = e.PointerID
}
if c.pid == e.PointerID {
c.entered = false
}
case pointer.Enter:
if !c.pressed {
c.pid = e.PointerID
}
if c.pid == e.PointerID {
c.entered = true
}
}
}
return events
}
func (ClickEvent) ImplementsEvent() {}
// Add the handler to the operation list to receive scroll events.
// The bounds variable refers to the scrolling boundaries
// as defined in io/pointer.InputOp.
func (s *Scroll) Add(ops *op.Ops, bounds image.Rectangle) {
oph := pointer.InputOp{
Tag: s,
Grab: s.grab,
Types: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
ScrollBounds: bounds,
}
oph.Add(ops)
if s.flinger.Active() {
op.InvalidateOp{}.Add(ops)
}
}
// Stop any remaining fling movement.
func (s *Scroll) Stop() {
s.flinger = fling.Animation{}
}
// Scroll detects the scrolling distance from the available events and
// ongoing fling gestures.
func (s *Scroll) Scroll(cfg unit.Metric, q event.Queue, t time.Time, axis Axis) int {
if s.axis != axis {
s.axis = axis
return 0
}
total := 0
for _, evt := range q.Events(s) {
e, ok := evt.(pointer.Event)
if !ok {
continue
}
switch e.Type {
case pointer.Press:
if s.dragging {
break
}
// Only scroll on touch drags, or on Android where mice
// drags also scroll by convention.
if e.Source != pointer.Touch && runtime.GOOS != "android" {
break
}
s.Stop()
s.estimator = fling.Extrapolation{}
v := s.val(e.Position)
s.last = int(math.Round(float64(v)))
s.estimator.Sample(e.Time, v)
s.dragging = true
s.pid = e.PointerID
case pointer.Release:
if s.pid != e.PointerID {
break
}
fling := s.estimator.Estimate()
if slop, d := float32(cfg.Px(touchSlop)), fling.Distance; d < -slop || d > slop {
s.flinger.Start(cfg, t, fling.Velocity)
}
fallthrough
case pointer.Cancel:
s.dragging = false
s.grab = false
case pointer.Scroll:
switch s.axis {
case Horizontal:
s.scroll += e.Scroll.X
case Vertical:
s.scroll += e.Scroll.Y
}
iscroll := int(s.scroll)
s.scroll -= float32(iscroll)
total += iscroll
case pointer.Drag:
if !s.dragging || s.pid != e.PointerID {
continue
}
val := s.val(e.Position)
s.estimator.Sample(e.Time, val)
v := int(math.Round(float64(val)))
dist := s.last - v
if e.Priority < pointer.Grabbed {
slop := cfg.Px(touchSlop)
if dist := dist; dist >= slop || -slop >= dist {
s.grab = true
}
} else {
s.last = v
total += dist
}
}
}
total += s.flinger.Tick(t)
return total
}
func (s *Scroll) val(p f32.Point) float32 {
if s.axis == Horizontal {
return p.X
} else {
return p.Y
}
}
// State reports the scroll state.
func (s *Scroll) State() ScrollState {
switch {
case s.flinger.Active():
return StateFlinging
case s.dragging:
return StateDragging
default:
return StateIdle
}
}
// Add the handler to the operation list to receive drag events.
func (d *Drag) Add(ops *op.Ops) {
pointer.InputOp{
Tag: d,
Grab: d.grab,
Types: pointer.Press | pointer.Drag | pointer.Release,
}.Add(ops)
}
// Events returns the next drag events, if any.
func (d *Drag) Events(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event {
var events []pointer.Event
for _, e := range q.Events(d) {
e, ok := e.(pointer.Event)
if !ok {
continue
}
switch e.Type {
case pointer.Press:
if !(e.Buttons == pointer.ButtonPrimary || e.Source == pointer.Touch) {
continue
}
d.pressed = true
if d.dragging {
continue
}
d.dragging = true
d.pid = e.PointerID
d.start = e.Position
case pointer.Drag:
if !d.dragging || e.PointerID != d.pid {
continue
}
switch axis {
case Horizontal:
e.Position.Y = d.start.Y
case Vertical:
e.Position.X = d.start.X
case Both:
// Do nothing
}
if e.Priority < pointer.Grabbed {
diff := e.Position.Sub(d.start)
slop := cfg.Px(touchSlop)
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
d.grab = true
}
}
case pointer.Release, pointer.Cancel:
d.pressed = false
if !d.dragging || e.PointerID != d.pid {
continue
}
d.dragging = false
d.grab = false
}
events = append(events, e)
}
return events
}
// Dragging reports whether it is currently in use.
func (d *Drag) Dragging() bool { return d.dragging }
// Pressed returns whether a pointer is pressing.
func (d *Drag) Pressed() bool { return d.pressed }
func (a Axis) String() string {
switch a {
case Horizontal:
return "Horizontal"
case Vertical:
return "Vertical"
default:
panic("invalid Axis")
}
}
func (ct ClickType) String() string {
switch ct {
case TypePress:
return "TypePress"
case TypeClick:
return "TypeClick"
case TypeCancel:
return "TypeCancel"
default:
panic("invalid ClickType")
}
}
func (s ScrollState) String() string {
switch s {
case StateIdle:
return "StateIdle"
case StateDragging:
return "StateDragging"
case StateFlinging:
return "StateFlinging"
default:
panic("unreachable")
}
}
================================================
FILE: vendor/gioui.org/gpu/api.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import "gioui.org/gpu/internal/driver"
// An API carries the necessary GPU API specific resources to create a Device.
// There is an API type for each supported GPU API such as OpenGL and Direct3D.
type API = driver.API
// A RenderTarget denotes the destination framebuffer for a frame.
type RenderTarget = driver.RenderTarget
// OpenGLRenderTarget is a render target suitable for the OpenGL backend.
type OpenGLRenderTarget = driver.OpenGLRenderTarget
// Direct3D11RenderTarget is a render target suitable for the Direct3D 11 backend.
type Direct3D11RenderTarget = driver.Direct3D11RenderTarget
// MetalRenderTarget is a render target suitable for the Metal backend.
type MetalRenderTarget = driver.MetalRenderTarget
// VulkanRenderTarget is a render target suitable for the Vulkan backend.
type VulkanRenderTarget = driver.VulkanRenderTarget
// OpenGL denotes the OpenGL or OpenGL ES API.
type OpenGL = driver.OpenGL
// Direct3D11 denotes the Direct3D API.
type Direct3D11 = driver.Direct3D11
// Metal denotes the Apple Metal API.
type Metal = driver.Metal
// Vulkan denotes the Vulkan API.
type Vulkan = driver.Vulkan
// ErrDeviceLost is returned from GPU operations when the underlying GPU device
// is lost and should be recreated.
var ErrDeviceLost = driver.ErrDeviceLost
================================================
FILE: vendor/gioui.org/gpu/caches.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"fmt"
"gioui.org/f32"
)
type resourceCache struct {
res map[interface{}]resource
newRes map[interface{}]resource
}
// opCache is like a resourceCache but using concrete types and a
// freelist instead of two maps to avoid runtime.mapaccess2 calls
// since benchmarking showed them as a bottleneck.
type opCache struct {
// store the index + 1 in cache this key is stored in
index map[opKey]int
// list of indexes in cache that are free and can be used
freelist []int
cache []opCacheValue
}
type opCacheValue struct {
data pathData
bounds f32.Rectangle
// the fields below are handled by opCache
key opKey
keep bool
}
func newResourceCache() *resourceCache {
return &resourceCache{
res: make(map[interface{}]resource),
newRes: make(map[interface{}]resource),
}
}
func (r *resourceCache) get(key interface{}) (resource, bool) {
v, exists := r.res[key]
if exists {
r.newRes[key] = v
}
return v, exists
}
func (r *resourceCache) put(key interface{}, val resource) {
if _, exists := r.newRes[key]; exists {
panic(fmt.Errorf("key exists, %p", key))
}
r.res[key] = val
r.newRes[key] = val
}
func (r *resourceCache) frame() {
for k, v := range r.res {
if _, exists := r.newRes[k]; !exists {
delete(r.res, k)
v.release()
}
}
for k, v := range r.newRes {
delete(r.newRes, k)
r.res[k] = v
}
}
func (r *resourceCache) release() {
r.frame()
for _, v := range r.res {
v.release()
}
r.newRes = nil
r.res = nil
}
func newOpCache() *opCache {
return &opCache{
index: make(map[opKey]int),
freelist: make([]int, 0),
cache: make([]opCacheValue, 0),
}
}
func (r *opCache) get(key opKey) (o opCacheValue, exist bool) {
v := r.index[key]
if v == 0 {
return
}
r.cache[v-1].keep = true
return r.cache[v-1], true
}
func (r *opCache) put(key opKey, val opCacheValue) {
v := r.index[key]
val.keep = true
val.key = key
if v == 0 {
// not in cache
i := len(r.cache)
if len(r.freelist) > 0 {
i = r.freelist[len(r.freelist)-1]
r.freelist = r.freelist[:len(r.freelist)-1]
r.cache[i] = val
} else {
r.cache = append(r.cache, val)
}
r.index[key] = i + 1
} else {
r.cache[v-1] = val
}
}
func (r *opCache) frame() {
r.freelist = r.freelist[:0]
for i, v := range r.cache {
r.cache[i].keep = false
if v.keep {
continue
}
if v.data.data != nil {
v.data.release()
r.cache[i].data.data = nil
}
delete(r.index, v.key)
r.freelist = append(r.freelist, i)
}
}
func (r *opCache) release() {
for i := range r.cache {
r.cache[i].keep = false
}
r.frame()
r.index = nil
r.freelist = nil
r.cache = nil
}
================================================
FILE: vendor/gioui.org/gpu/clip.go
================================================
package gpu
import (
"gioui.org/f32"
"gioui.org/internal/stroke"
)
type quadSplitter struct {
bounds f32.Rectangle
contour uint32
d *drawOps
}
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
// NW.
encodeVertex(data, meta, -1, 1, from, ctrl, to)
// NE.
encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
// SW.
encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
// SE.
encodeVertex(data[vertStride*3:], meta, 1, -1, from, ctrl, to)
}
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
var corner float32
if cornerx == 1 {
corner += .5
}
if cornery == 1 {
corner += .25
}
v := vertex{
Corner: corner,
FromX: from.X,
FromY: from.Y,
CtrlX: ctrl.X,
CtrlY: ctrl.Y,
ToX: to.X,
ToY: to.Y,
}
v.encode(data, meta)
}
func (qs *quadSplitter) encodeQuadTo(from, ctrl, to f32.Point) {
data := qs.d.writeVertCache(vertStride * 4)
encodeQuadTo(data, qs.contour, from, ctrl, to)
}
func (qs *quadSplitter) splitAndEncode(quad stroke.QuadSegment) {
cbnd := f32.Rectangle{
Min: quad.From,
Max: quad.To,
}.Canon()
from, ctrl, to := quad.From, quad.Ctrl, quad.To
// If the curve contain areas where a vertical line
// intersects it twice, split the curve in two x monotone
// lower and upper curves. The stencil fragment program
// expects only one intersection per curve.
// Find the t where the derivative in x is 0.
v0 := ctrl.Sub(from)
v1 := to.Sub(ctrl)
d := v0.X - v1.X
// t = v0 / d. Split if t is in ]0;1[.
if v0.X > 0 && d > v0.X || v0.X < 0 && d < v0.X {
t := v0.X / d
ctrl0 := from.Mul(1 - t).Add(ctrl.Mul(t))
ctrl1 := ctrl.Mul(1 - t).Add(to.Mul(t))
mid := ctrl0.Mul(1 - t).Add(ctrl1.Mul(t))
qs.encodeQuadTo(from, ctrl0, mid)
qs.encodeQuadTo(mid, ctrl1, to)
if mid.X > cbnd.Max.X {
cbnd.Max.X = mid.X
}
if mid.X < cbnd.Min.X {
cbnd.Min.X = mid.X
}
} else {
qs.encodeQuadTo(from, ctrl, to)
}
// Find the y extremum, if any.
d = v0.Y - v1.Y
if v0.Y > 0 && d > v0.Y || v0.Y < 0 && d < v0.Y {
t := v0.Y / d
y := (1-t)*(1-t)*from.Y + 2*(1-t)*t*ctrl.Y + t*t*to.Y
if y > cbnd.Max.Y {
cbnd.Max.Y = y
}
if y < cbnd.Min.Y {
cbnd.Min.Y = y
}
}
qs.bounds = unionRect(qs.bounds, cbnd)
}
// Union is like f32.Rectangle.Union but ignores empty rectangles.
func unionRect(r, s f32.Rectangle) f32.Rectangle {
if r.Min.X > s.Min.X {
r.Min.X = s.Min.X
}
if r.Min.Y > s.Min.Y {
r.Min.Y = s.Min.Y
}
if r.Max.X < s.Max.X {
r.Max.X = s.Max.X
}
if r.Max.Y < s.Max.Y {
r.Max.Y = s.Max.Y
}
return r
}
================================================
FILE: vendor/gioui.org/gpu/compute.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"hash/maphash"
"image"
"image/color"
"image/draw"
"image/png"
"io/ioutil"
"math"
"math/bits"
"runtime"
"sort"
"time"
"unsafe"
"gioui.org/cpu"
"gioui.org/f32"
"gioui.org/gpu/internal/driver"
"gioui.org/internal/byteslice"
"gioui.org/internal/f32color"
"gioui.org/internal/ops"
"gioui.org/internal/scene"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/shader"
"gioui.org/shader/gio"
"gioui.org/shader/piet"
)
type compute struct {
ctx driver.Device
collector collector
enc encoder
texOps []textureOp
viewport image.Point
maxTextureDim int
srgb bool
atlases []*textureAtlas
frameCount uint
moves []atlasMove
programs struct {
elements computeProgram
tileAlloc computeProgram
pathCoarse computeProgram
backdrop computeProgram
binning computeProgram
coarse computeProgram
kernel4 computeProgram
}
buffers struct {
config sizedBuffer
scene sizedBuffer
state sizedBuffer
memory sizedBuffer
}
output struct {
blitPipeline driver.Pipeline
buffer sizedBuffer
uniforms *copyUniforms
uniBuf driver.Buffer
layerVertices []layerVertex
descriptors *piet.Kernel4DescriptorSetLayout
nullMaterials driver.Texture
}
// imgAllocs maps imageOpData.handles to allocs.
imgAllocs map[interface{}]*atlasAlloc
// materials contains the pre-processed materials (transformed images for
// now, gradients etc. later) packed in a texture atlas. The atlas is used
// as source in kernel4.
materials struct {
// allocs maps texture ops the their atlases and FillImage offsets.
allocs map[textureKey]materialAlloc
pipeline driver.Pipeline
buffer sizedBuffer
quads []materialVertex
uniforms struct {
u *materialUniforms
buf driver.Buffer
}
}
timers struct {
profile string
t *timers
compact *timer
render *timer
blit *timer
}
// CPU fallback fields.
useCPU bool
dispatcher *dispatcher
// The following fields hold scratch space to avoid garbage.
zeroSlice []byte
memHeader *memoryHeader
conf *config
}
type materialAlloc struct {
alloc *atlasAlloc
offset image.Point
}
type layer struct {
rect image.Rectangle
alloc *atlasAlloc
ops []paintOp
materials *textureAtlas
}
type allocQuery struct {
atlas *textureAtlas
size image.Point
empty bool
format driver.TextureFormat
bindings driver.BufferBinding
nocompact bool
}
type atlasAlloc struct {
atlas *textureAtlas
rect image.Rectangle
cpu bool
dead bool
frameCount uint
}
type atlasMove struct {
src *textureAtlas
dstPos image.Point
srcRect image.Rectangle
cpu bool
}
type textureAtlas struct {
image driver.Texture
format driver.TextureFormat
bindings driver.BufferBinding
hasCPU bool
cpuImage cpu.ImageDescriptor
size image.Point
allocs []*atlasAlloc
packer packer
realized bool
lastFrame uint
compact bool
}
type copyUniforms struct {
scale [2]float32
pos [2]float32
uvScale [2]float32
_ [8]byte // Pad to 16 bytes.
}
type materialUniforms struct {
scale [2]float32
pos [2]float32
emulatesRGB float32
_ [12]byte // Pad to 16 bytes
}
type collector struct {
hasher maphash.Hash
profile bool
reader ops.Reader
states []f32.Affine2D
clear bool
clearColor f32color.RGBA
clipStates []clipState
order []hashIndex
transStack []transEntry
prevFrame opsCollector
frame opsCollector
}
type transEntry struct {
t f32.Affine2D
relTrans f32.Affine2D
}
type hashIndex struct {
index int
hash uint64
}
type opsCollector struct {
paths []byte
clipCmds []clipCmd
ops []paintOp
layers []layer
}
type paintOp struct {
clipStack []clipCmd
offset image.Point
state paintKey
intersect f32.Rectangle
hash uint64
layer int
texOpIdx int
}
// clipCmd describes a clipping command ready to be used for the compute
// pipeline.
type clipCmd struct {
// union of the bounds of the operations that are clipped.
union f32.Rectangle
state clipKey
path []byte
pathKey ops.Key
absBounds f32.Rectangle
}
type encoderState struct {
relTrans f32.Affine2D
clip *clipState
paintKey
}
// clipKey completely describes a clip operation (along with its path) and is appropriate
// for hashing and equality checks.
type clipKey struct {
bounds f32.Rectangle
strokeWidth float32
relTrans f32.Affine2D
pathHash uint64
}
// paintKey completely defines a paint operation. It is suitable for hashing and
// equality checks.
type paintKey struct {
t f32.Affine2D
matType materialType
// Current paint.ImageOp
image imageOpData
// Current paint.ColorOp, if any.
color color.NRGBA
// Current paint.LinearGradientOp.
stop1 f32.Point
stop2 f32.Point
color1 color.NRGBA
color2 color.NRGBA
}
type clipState struct {
absBounds f32.Rectangle
parent *clipState
path []byte
pathKey ops.Key
intersect f32.Rectangle
push bool
clipKey
}
type layerVertex struct {
posX, posY float32
u, v float32
}
// materialVertex describes a vertex of a quad used to render a transformed
// material.
type materialVertex struct {
posX, posY float32
u, v float32
}
// textureKey identifies textureOp.
type textureKey struct {
handle interface{}
transform f32.Affine2D
bounds image.Rectangle
}
// textureOp represents an paintOp that requires texture space.
type textureOp struct {
img imageOpData
key textureKey
// offset is the integer offset separated from key.transform to increase cache hit rate.
off image.Point
// matAlloc is the atlas placement for material.
matAlloc materialAlloc
// imgAlloc is the atlas placement for the source image
imgAlloc *atlasAlloc
}
type encoder struct {
scene []scene.Command
npath int
npathseg int
ntrans int
}
type encodeState struct {
trans f32.Affine2D
clip f32.Rectangle
}
// sizedBuffer holds a GPU buffer, or its equivalent CPU memory.
type sizedBuffer struct {
size int
buffer driver.Buffer
// cpuBuf is initialized when useCPU is true.
cpuBuf cpu.BufferDescriptor
}
// computeProgram holds a compute program, or its equivalent CPU implementation.
type computeProgram struct {
prog driver.Program
// CPU fields.
progInfo *cpu.ProgramInfo
descriptors unsafe.Pointer
buffers []*cpu.BufferDescriptor
}
// config matches Config in setup.h
type config struct {
n_elements uint32 // paths
n_pathseg uint32
width_in_tiles uint32
height_in_tiles uint32
tile_alloc memAlloc
bin_alloc memAlloc
ptcl_alloc memAlloc
pathseg_alloc memAlloc
anno_alloc memAlloc
trans_alloc memAlloc
}
// memAlloc matches Alloc in mem.h
type memAlloc struct {
offset uint32
//size uint32
}
// memoryHeader matches the header of Memory in mem.h.
type memoryHeader struct {
mem_offset uint32
mem_error uint32
}
// rect is a oriented rectangle.
type rectangle [4]f32.Point
const (
layersBindings = driver.BufferBindingShaderStorageWrite | driver.BufferBindingTexture
materialsBindings = driver.BufferBindingFramebuffer | driver.BufferBindingShaderStorageRead
// Materials and layers can share texture storage if their bindings match.
combinedBindings = layersBindings | materialsBindings
)
// GPU structure sizes and constants.
const (
tileWidthPx = 32
tileHeightPx = 32
ptclInitialAlloc = 1024
kernel4OutputUnit = 2
kernel4AtlasUnit = 3
pathSize = 12
binSize = 8
pathsegSize = 52
annoSize = 32
transSize = 24
stateSize = 60
stateStride = 4 + 2*stateSize
)
// mem.h constants.
const (
memNoError = 0 // NO_ERROR
memMallocFailed = 1 // ERR_MALLOC_FAILED
)
func newCompute(ctx driver.Device) (*compute, error) {
caps := ctx.Caps()
maxDim := caps.MaxTextureSize
// Large atlas textures cause artifacts due to precision loss in
// shaders.
if cap := 8192; maxDim > cap {
maxDim = cap
}
// The compute programs can only span 128x64 tiles. Limit to 64 for now, and leave the
// complexity of a rectangular limit for later.
if computeCap := 4096; maxDim > computeCap {
maxDim = computeCap
}
g := &compute{
ctx: ctx,
maxTextureDim: maxDim,
srgb: caps.Features.Has(driver.FeatureSRGB),
conf: new(config),
memHeader: new(memoryHeader),
}
null, err := ctx.NewTexture(driver.TextureFormatRGBA8, 1, 1, driver.FilterNearest, driver.FilterNearest, driver.BufferBindingShaderStorageRead)
if err != nil {
g.Release()
return nil, err
}
g.output.nullMaterials = null
shaders := []struct {
prog *computeProgram
src shader.Sources
info *cpu.ProgramInfo
}{
{&g.programs.elements, piet.Shader_elements_comp, piet.ElementsProgramInfo},
{&g.programs.tileAlloc, piet.Shader_tile_alloc_comp, piet.Tile_allocProgramInfo},
{&g.programs.pathCoarse, piet.Shader_path_coarse_comp, piet.Path_coarseProgramInfo},
{&g.programs.backdrop, piet.Shader_backdrop_comp, piet.BackdropProgramInfo},
{&g.programs.binning, piet.Shader_binning_comp, piet.BinningProgramInfo},
{&g.programs.coarse, piet.Shader_coarse_comp, piet.CoarseProgramInfo},
{&g.programs.kernel4, piet.Shader_kernel4_comp, piet.Kernel4ProgramInfo},
}
if !caps.Features.Has(driver.FeatureCompute) {
if !cpu.Supported {
return nil, errors.New("gpu: missing support for compute programs")
}
g.useCPU = true
}
if g.useCPU {
g.dispatcher = newDispatcher(runtime.NumCPU())
}
copyVert, copyFrag, err := newShaders(ctx, gio.Shader_copy_vert, gio.Shader_copy_frag)
if err != nil {
g.Release()
return nil, err
}
defer copyVert.Release()
defer copyFrag.Release()
pipe, err := ctx.NewPipeline(driver.PipelineDesc{
VertexShader: copyVert,
FragmentShader: copyFrag,
VertexLayout: driver.VertexLayout{
Inputs: []driver.InputDesc{
{Type: shader.DataTypeFloat, Size: 2, Offset: 0},
{Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
},
Stride: int(unsafe.Sizeof(g.output.layerVertices[0])),
},
PixelFormat: driver.TextureFormatOutput,
BlendDesc: driver.BlendDesc{
Enable: true,
SrcFactor: driver.BlendFactorOne,
DstFactor: driver.BlendFactorOneMinusSrcAlpha,
},
Topology: driver.TopologyTriangles,
})
if err != nil {
g.Release()
return nil, err
}
g.output.blitPipeline = pipe
g.output.uniforms = new(copyUniforms)
buf, err := ctx.NewBuffer(driver.BufferBindingUniforms, int(unsafe.Sizeof(*g.output.uniforms)))
if err != nil {
g.Release()
return nil, err
}
g.output.uniBuf = buf
materialVert, materialFrag, err := newShaders(ctx, gio.Shader_material_vert, gio.Shader_material_frag)
if err != nil {
g.Release()
return nil, err
}
defer materialVert.Release()
defer materialFrag.Release()
pipe, err = ctx.NewPipeline(driver.PipelineDesc{
VertexShader: materialVert,
FragmentShader: materialFrag,
VertexLayout: driver.VertexLayout{
Inputs: []driver.InputDesc{
{Type: shader.DataTypeFloat, Size: 2, Offset: 0},
{Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
},
Stride: int(unsafe.Sizeof(g.materials.quads[0])),
},
PixelFormat: driver.TextureFormatRGBA8,
Topology: driver.TopologyTriangles,
})
if err != nil {
g.Release()
return nil, err
}
g.materials.pipeline = pipe
g.materials.uniforms.u = new(materialUniforms)
buf, err = ctx.NewBuffer(driver.BufferBindingUniforms, int(unsafe.Sizeof(*g.materials.uniforms.u)))
if err != nil {
g.Release()
return nil, err
}
g.materials.uniforms.buf = buf
for _, shader := range shaders {
if !g.useCPU {
p, err := ctx.NewComputeProgram(shader.src)
if err != nil {
g.Release()
return nil, err
}
shader.prog.prog = p
} else {
shader.prog.progInfo = shader.info
}
}
if g.useCPU {
{
desc := new(piet.ElementsDescriptorSetLayout)
g.programs.elements.descriptors = unsafe.Pointer(desc)
g.programs.elements.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1(), desc.Binding2(), desc.Binding3()}
}
{
desc := new(piet.Tile_allocDescriptorSetLayout)
g.programs.tileAlloc.descriptors = unsafe.Pointer(desc)
g.programs.tileAlloc.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
}
{
desc := new(piet.Path_coarseDescriptorSetLayout)
g.programs.pathCoarse.descriptors = unsafe.Pointer(desc)
g.programs.pathCoarse.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
}
{
desc := new(piet.BackdropDescriptorSetLayout)
g.programs.backdrop.descriptors = unsafe.Pointer(desc)
g.programs.backdrop.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
}
{
desc := new(piet.BinningDescriptorSetLayout)
g.programs.binning.descriptors = unsafe.Pointer(desc)
g.programs.binning.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
}
{
desc := new(piet.CoarseDescriptorSetLayout)
g.programs.coarse.descriptors = unsafe.Pointer(desc)
g.programs.coarse.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
}
{
desc := new(piet.Kernel4DescriptorSetLayout)
g.programs.kernel4.descriptors = unsafe.Pointer(desc)
g.programs.kernel4.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
g.output.descriptors = desc
}
}
return g, nil
}
func newShaders(ctx driver.Device, vsrc, fsrc shader.Sources) (vert driver.VertexShader, frag driver.FragmentShader, err error) {
vert, err = ctx.NewVertexShader(vsrc)
if err != nil {
return
}
frag, err = ctx.NewFragmentShader(fsrc)
if err != nil {
vert.Release()
}
return
}
func (g *compute) Frame(frameOps *op.Ops, target RenderTarget, viewport image.Point) error {
g.frameCount++
g.collect(viewport, frameOps)
return g.frame(target)
}
func (g *compute) collect(viewport image.Point, ops *op.Ops) {
g.viewport = viewport
g.collector.reset()
g.texOps = g.texOps[:0]
g.collector.collect(ops, viewport, &g.texOps)
}
func (g *compute) Clear(col color.NRGBA) {
g.collector.clear = true
g.collector.clearColor = f32color.LinearFromSRGB(col)
}
func (g *compute) frame(target RenderTarget) error {
viewport := g.viewport
defFBO := g.ctx.BeginFrame(target, g.collector.clear, viewport)
defer g.ctx.EndFrame()
t := &g.timers
if g.collector.profile && t.t == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
t.t = newTimers(g.ctx)
t.compact = t.t.newTimer()
t.render = t.t.newTimer()
t.blit = t.t.newTimer()
}
if err := g.uploadImages(); err != nil {
return err
}
if err := g.renderMaterials(); err != nil {
return err
}
g.layer(viewport, g.texOps)
t.render.begin()
if err := g.renderLayers(viewport); err != nil {
return err
}
t.render.end()
d := driver.LoadDesc{
ClearColor: g.collector.clearColor,
}
if g.collector.clear {
g.collector.clear = false
d.Action = driver.LoadActionClear
}
t.blit.begin()
g.blitLayers(d, defFBO, viewport)
t.blit.end()
t.compact.begin()
if err := g.compactAllocs(); err != nil {
return err
}
t.compact.end()
if g.collector.profile && t.t.ready() {
com, ren, blit := t.compact.Elapsed, t.render.Elapsed, t.blit.Elapsed
ft := com + ren + blit
q := 100 * time.Microsecond
ft = ft.Round(q)
com, ren, blit = com.Round(q), ren.Round(q), blit.Round(q)
t.profile = fmt.Sprintf("ft:%7s com: %7s ren:%7s blit:%7s", ft, com, ren, blit)
}
return nil
}
func (g *compute) dumpAtlases() {
for i, a := range g.atlases {
dump := image.NewRGBA(image.Rectangle{Max: a.size})
err := driver.DownloadImage(g.ctx, a.image, dump)
if err != nil {
panic(err)
}
nrgba := image.NewNRGBA(dump.Bounds())
draw.Draw(nrgba, image.Rectangle{}, dump, image.Point{}, draw.Src)
var buf bytes.Buffer
if err := png.Encode(&buf, nrgba); err != nil {
panic(err)
}
if err := ioutil.WriteFile(fmt.Sprintf("dump-%d.png", i), buf.Bytes(), 0600); err != nil {
panic(err)
}
}
}
func (g *compute) Profile() string {
return g.timers.profile
}
func (g *compute) compactAllocs() error {
const (
maxAllocAge = 3
maxAtlasAge = 10
)
atlases := g.atlases
for _, a := range atlases {
if len(a.allocs) > 0 && g.frameCount-a.lastFrame > maxAtlasAge {
a.compact = true
}
}
for len(atlases) > 0 {
var (
dstAtlas *textureAtlas
format driver.TextureFormat
bindings driver.BufferBinding
)
g.moves = g.moves[:0]
addedLayers := false
useCPU := false
fill:
for len(atlases) > 0 {
srcAtlas := atlases[0]
allocs := srcAtlas.allocs
if !srcAtlas.compact {
atlases = atlases[1:]
continue
}
if addedLayers && (format != srcAtlas.format || srcAtlas.bindings&bindings != srcAtlas.bindings) {
break
}
format = srcAtlas.format
bindings = srcAtlas.bindings
for len(srcAtlas.allocs) > 0 {
a := srcAtlas.allocs[0]
n := len(srcAtlas.allocs)
if g.frameCount-a.frameCount > maxAllocAge {
a.dead = true
srcAtlas.allocs[0] = srcAtlas.allocs[n-1]
srcAtlas.allocs = srcAtlas.allocs[:n-1]
continue
}
size := a.rect.Size()
alloc, fits := g.atlasAlloc(allocQuery{
atlas: dstAtlas,
size: size,
format: format,
bindings: bindings,
nocompact: true,
})
if !fits {
break fill
}
dstAtlas = alloc.atlas
allocs = append(allocs, a)
addedLayers = true
useCPU = useCPU || a.cpu
dstAtlas.allocs = append(dstAtlas.allocs, a)
pos := alloc.rect.Min
g.moves = append(g.moves, atlasMove{
src: srcAtlas, dstPos: pos, srcRect: a.rect, cpu: a.cpu,
})
a.atlas = dstAtlas
a.rect = image.Rectangle{Min: pos, Max: pos.Add(a.rect.Size())}
srcAtlas.allocs[0] = srcAtlas.allocs[n-1]
srcAtlas.allocs = srcAtlas.allocs[:n-1]
}
srcAtlas.compact = false
srcAtlas.realized = false
srcAtlas.packer.clear()
srcAtlas.packer.newPage()
srcAtlas.packer.maxDims = image.Pt(g.maxTextureDim, g.maxTextureDim)
atlases = atlases[1:]
}
if !addedLayers {
break
}
outputSize := dstAtlas.packer.sizes[0]
if err := g.realizeAtlas(dstAtlas, useCPU, outputSize); err != nil {
return err
}
for _, move := range g.moves {
if !move.cpu {
g.ctx.CopyTexture(dstAtlas.image, move.dstPos, move.src.image, move.srcRect)
} else {
src := move.src.cpuImage.Data()
dst := dstAtlas.cpuImage.Data()
sstride := move.src.size.X * 4
dstride := dstAtlas.size.X * 4
copyImage(dst, dstride, move.dstPos, src, sstride, move.srcRect)
}
}
}
for i := len(g.atlases) - 1; i >= 0; i-- {
a := g.atlases[i]
if len(a.allocs) == 0 && g.frameCount-a.lastFrame > maxAtlasAge {
a.Release()
n := len(g.atlases)
g.atlases[i] = g.atlases[n-1]
g.atlases = g.atlases[:n-1]
}
}
return nil
}
func copyImage(dst []byte, dstStride int, dstPos image.Point, src []byte, srcStride int, srcRect image.Rectangle) {
sz := srcRect.Size()
soff := srcRect.Min.Y*srcStride + srcRect.Min.X*4
doff := dstPos.Y*dstStride + dstPos.X*4
rowLen := sz.X * 4
for y := 0; y < sz.Y; y++ {
srow := src[soff : soff+rowLen]
drow := dst[doff : doff+rowLen]
copy(drow, srow)
soff += srcStride
doff += dstStride
}
}
func (g *compute) renderLayers(viewport image.Point) error {
layers := g.collector.frame.layers
for len(layers) > 0 {
var materials, dst *textureAtlas
addedLayers := false
g.enc.reset()
for len(layers) > 0 {
l := &layers[0]
if l.alloc != nil {
layers = layers[1:]
continue
}
if materials != nil {
if l.materials != nil && materials != l.materials {
// Only one materials texture per compute pass.
break
}
} else {
materials = l.materials
}
size := l.rect.Size()
alloc, fits := g.atlasAlloc(allocQuery{
atlas: dst,
empty: true,
format: driver.TextureFormatRGBA8,
bindings: combinedBindings,
// Pad to avoid overlap.
size: size.Add(image.Pt(1, 1)),
})
if !fits {
// Only one output atlas per compute pass.
break
}
dst = alloc.atlas
dst.compact = true
addedLayers = true
l.alloc = &alloc
dst.allocs = append(dst.allocs, l.alloc)
encodeLayer(*l, alloc.rect.Min, viewport, &g.enc, g.texOps)
layers = layers[1:]
}
if !addedLayers {
break
}
outputSize := dst.packer.sizes[0]
tileDims := image.Point{
X: (outputSize.X + tileWidthPx - 1) / tileWidthPx,
Y: (outputSize.Y + tileHeightPx - 1) / tileHeightPx,
}
w, h := tileDims.X*tileWidthPx, tileDims.Y*tileHeightPx
if err := g.realizeAtlas(dst, g.useCPU, image.Pt(w, h)); err != nil {
return err
}
if err := g.render(materials, dst.image, dst.cpuImage, tileDims, dst.size.X*4); err != nil {
return err
}
}
return nil
}
func (g *compute) blitLayers(d driver.LoadDesc, fbo driver.Texture, viewport image.Point) {
layers := g.collector.frame.layers
g.output.layerVertices = g.output.layerVertices[:0]
for _, l := range layers {
placef := layout.FPt(l.alloc.rect.Min)
sizef := layout.FPt(l.rect.Size())
r := layout.FRect(l.rect)
quad := [4]layerVertex{
{posX: float32(r.Min.X), posY: float32(r.Min.Y), u: placef.X, v: placef.Y},
{posX: float32(r.Max.X), posY: float32(r.Min.Y), u: placef.X + sizef.X, v: placef.Y},
{posX: float32(r.Max.X), posY: float32(r.Max.Y), u: placef.X + sizef.X, v: placef.Y + sizef.Y},
{posX: float32(r.Min.X), posY: float32(r.Max.Y), u: placef.X, v: placef.Y + sizef.Y},
}
g.output.layerVertices = append(g.output.layerVertices, quad[0], quad[1], quad[3], quad[3], quad[2], quad[1])
g.ctx.PrepareTexture(l.alloc.atlas.image)
}
if len(g.output.layerVertices) > 0 {
vertexData := byteslice.Slice(g.output.layerVertices)
g.output.buffer.ensureCapacity(false, g.ctx, driver.BufferBindingVertices, len(vertexData))
g.output.buffer.buffer.Upload(vertexData)
}
g.ctx.BeginRenderPass(fbo, d)
defer g.ctx.EndRenderPass()
if len(layers) == 0 {
return
}
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
g.ctx.BindPipeline(g.output.blitPipeline)
g.ctx.BindVertexBuffer(g.output.buffer.buffer, 0)
start := 0
for len(layers) > 0 {
count := 0
atlas := layers[0].alloc.atlas
for len(layers) > 0 {
l := layers[0]
if l.alloc.atlas != atlas {
break
}
layers = layers[1:]
const verticesPerQuad = 6
count += verticesPerQuad
}
// Transform positions to clip space: [-1, -1] - [1, 1], and texture
// coordinates to texture space: [0, 0] - [1, 1].
clip := f32.Affine2D{}.Scale(f32.Pt(0, 0), f32.Pt(2/float32(viewport.X), 2/float32(viewport.Y))).Offset(f32.Pt(-1, -1))
sx, _, ox, _, sy, oy := clip.Elems()
g.output.uniforms.scale = [2]float32{sx, sy}
g.output.uniforms.pos = [2]float32{ox, oy}
g.output.uniforms.uvScale = [2]float32{1 / float32(atlas.size.X), 1 / float32(atlas.size.Y)}
g.output.uniBuf.Upload(byteslice.Struct(g.output.uniforms))
g.ctx.BindUniforms(g.output.uniBuf)
g.ctx.BindTexture(0, atlas.image)
g.ctx.DrawArrays(start, count)
start += count
}
}
func (g *compute) renderMaterials() error {
m := &g.materials
for k, place := range m.allocs {
if place.alloc.dead {
delete(m.allocs, k)
}
}
texOps := g.texOps
for len(texOps) > 0 {
m.quads = m.quads[:0]
var (
atlas *textureAtlas
imgAtlas *textureAtlas
)
// A material is clipped to avoid drawing outside its atlas bounds.
// However, imprecision in the clipping may cause a single pixel
// overflow.
var padding = image.Pt(1, 1)
var allocStart int
for len(texOps) > 0 {
op := &texOps[0]
if a, exists := m.allocs[op.key]; exists {
g.touchAlloc(a.alloc)
op.matAlloc = a
texOps = texOps[1:]
continue
}
if imgAtlas != nil && op.imgAlloc.atlas != imgAtlas {
// Only one image atlas per render pass.
break
}
imgAtlas = op.imgAlloc.atlas
quad := g.materialQuad(imgAtlas.size, op.key.transform, op.img, op.imgAlloc.rect.Min)
boundsf := quadBounds(quad)
bounds := boundRectF(boundsf)
bounds = bounds.Intersect(op.key.bounds)
size := bounds.Size()
alloc, fits := g.atlasAlloc(allocQuery{
atlas: atlas,
size: size.Add(padding),
format: driver.TextureFormatRGBA8,
bindings: combinedBindings,
})
if !fits {
break
}
if atlas == nil {
allocStart = len(alloc.atlas.allocs)
}
atlas = alloc.atlas
alloc.cpu = g.useCPU
offsetf := layout.FPt(bounds.Min.Mul(-1))
scale := f32.Pt(float32(size.X), float32(size.Y))
for i := range quad {
// Position quad to match place.
quad[i].posX += offsetf.X
quad[i].posY += offsetf.Y
// Scale to match viewport [0, 1].
quad[i].posX /= scale.X
quad[i].posY /= scale.Y
}
// Draw quad as two triangles.
m.quads = append(m.quads, quad[0], quad[1], quad[3], quad[3], quad[1], quad[2])
if m.allocs == nil {
m.allocs = make(map[textureKey]materialAlloc)
}
atlasAlloc := materialAlloc{
alloc: &alloc,
offset: bounds.Min.Mul(-1),
}
atlas.allocs = append(atlas.allocs, atlasAlloc.alloc)
m.allocs[op.key] = atlasAlloc
op.matAlloc = atlasAlloc
texOps = texOps[1:]
}
if len(m.quads) == 0 {
break
}
realized := atlas.realized
if err := g.realizeAtlas(atlas, g.useCPU, atlas.packer.sizes[0]); err != nil {
return err
}
// Transform to clip space: [-1, -1] - [1, 1].
*m.uniforms.u = materialUniforms{
scale: [2]float32{2, 2},
pos: [2]float32{-1, -1},
}
if !g.srgb {
m.uniforms.u.emulatesRGB = 1.0
}
m.uniforms.buf.Upload(byteslice.Struct(m.uniforms.u))
vertexData := byteslice.Slice(m.quads)
n := pow2Ceil(len(vertexData))
m.buffer.ensureCapacity(false, g.ctx, driver.BufferBindingVertices, n)
m.buffer.buffer.Upload(vertexData)
var d driver.LoadDesc
if !realized {
d.Action = driver.LoadActionClear
}
g.ctx.PrepareTexture(imgAtlas.image)
g.ctx.BeginRenderPass(atlas.image, d)
g.ctx.BindTexture(0, imgAtlas.image)
g.ctx.BindPipeline(m.pipeline)
g.ctx.BindUniforms(m.uniforms.buf)
g.ctx.BindVertexBuffer(m.buffer.buffer, 0)
newAllocs := atlas.allocs[allocStart:]
for i, a := range newAllocs {
sz := a.rect.Size().Sub(padding)
g.ctx.Viewport(a.rect.Min.X, a.rect.Min.Y, sz.X, sz.Y)
g.ctx.DrawArrays(i*6, 6)
}
g.ctx.EndRenderPass()
if !g.useCPU {
continue
}
src := atlas.image
data := atlas.cpuImage.Data()
for _, a := range newAllocs {
stride := atlas.size.X * 4
col := a.rect.Min.X * 4
row := stride * a.rect.Min.Y
off := col + row
src.ReadPixels(a.rect, data[off:], stride)
}
}
return nil
}
func (g *compute) uploadImages() error {
for k, a := range g.imgAllocs {
if a.dead {
delete(g.imgAllocs, k)
}
}
type upload struct {
pos image.Point
img *image.RGBA
}
var uploads []upload
format := driver.TextureFormatSRGBA
if !g.srgb {
format = driver.TextureFormatRGBA8
}
// padding is the number of pixels added to the right and below
// images, to avoid atlas filtering artifacts.
const padding = 1
texOps := g.texOps
for len(texOps) > 0 {
uploads = uploads[:0]
var atlas *textureAtlas
for len(texOps) > 0 {
op := &texOps[0]
if a, exists := g.imgAllocs[op.img.handle]; exists {
g.touchAlloc(a)
op.imgAlloc = a
texOps = texOps[1:]
continue
}
size := op.img.src.Bounds().Size().Add(image.Pt(padding, padding))
alloc, fits := g.atlasAlloc(allocQuery{
atlas: atlas,
size: size,
format: format,
bindings: driver.BufferBindingTexture | driver.BufferBindingFramebuffer,
})
if !fits {
break
}
atlas = alloc.atlas
if g.imgAllocs == nil {
g.imgAllocs = make(map[interface{}]*atlasAlloc)
}
op.imgAlloc = &alloc
atlas.allocs = append(atlas.allocs, op.imgAlloc)
g.imgAllocs[op.img.handle] = op.imgAlloc
uploads = append(uploads, upload{pos: alloc.rect.Min, img: op.img.src})
texOps = texOps[1:]
}
if len(uploads) == 0 {
break
}
if err := g.realizeAtlas(atlas, false, atlas.packer.sizes[0]); err != nil {
return err
}
for _, u := range uploads {
size := u.img.Bounds().Size()
driver.UploadImage(atlas.image, u.pos, u.img)
rightPadding := image.Pt(padding, size.Y)
atlas.image.Upload(image.Pt(u.pos.X+size.X, u.pos.Y), rightPadding, g.zeros(rightPadding.X*rightPadding.Y*4), 0)
bottomPadding := image.Pt(size.X, padding)
atlas.image.Upload(image.Pt(u.pos.X, u.pos.Y+size.Y), bottomPadding, g.zeros(bottomPadding.X*bottomPadding.Y*4), 0)
}
}
return nil
}
func pow2Ceil(v int) int {
exp := bits.Len(uint(v))
if bits.OnesCount(uint(v)) == 1 {
exp--
}
return 1 << exp
}
// materialQuad constructs a quad that represents the transformed image. It returns the quad
// and its bounds.
func (g *compute) materialQuad(imgAtlasSize image.Point, M f32.Affine2D, img imageOpData, uvPos image.Point) [4]materialVertex {
imgSize := layout.FPt(img.src.Bounds().Size())
sx, hx, ox, hy, sy, oy := M.Elems()
transOff := f32.Pt(ox, oy)
// The 4 corners of the image rectangle transformed by M, excluding its offset, are:
//
// q0: M * (0, 0) q3: M * (w, 0)
// q1: M * (0, h) q2: M * (w, h)
//
// Note that q0 = M*0 = 0, q2 = q1 + q3.
q0 := f32.Pt(0, 0)
q1 := f32.Pt(hx*imgSize.Y, sy*imgSize.Y)
q3 := f32.Pt(sx*imgSize.X, hy*imgSize.X)
q2 := q1.Add(q3)
q0 = q0.Add(transOff)
q1 = q1.Add(transOff)
q2 = q2.Add(transOff)
q3 = q3.Add(transOff)
uvPosf := layout.FPt(uvPos)
atlasScale := f32.Pt(1/float32(imgAtlasSize.X), 1/float32(imgAtlasSize.Y))
uvBounds := f32.Rectangle{
Min: uvPosf,
Max: uvPosf.Add(imgSize),
}
uvBounds.Min.X *= atlasScale.X
uvBounds.Min.Y *= atlasScale.Y
uvBounds.Max.X *= atlasScale.X
uvBounds.Max.Y *= atlasScale.Y
quad := [4]materialVertex{
{posX: q0.X, posY: q0.Y, u: uvBounds.Min.X, v: uvBounds.Min.Y},
{posX: q1.X, posY: q1.Y, u: uvBounds.Min.X, v: uvBounds.Max.Y},
{posX: q2.X, posY: q2.Y, u: uvBounds.Max.X, v: uvBounds.Max.Y},
{posX: q3.X, posY: q3.Y, u: uvBounds.Max.X, v: uvBounds.Min.Y},
}
return quad
}
func quadBounds(q [4]materialVertex) f32.Rectangle {
q0 := f32.Pt(q[0].posX, q[0].posY)
q1 := f32.Pt(q[1].posX, q[1].posY)
q2 := f32.Pt(q[2].posX, q[2].posY)
q3 := f32.Pt(q[3].posX, q[3].posY)
return f32.Rectangle{
Min: min(min(q0, q1), min(q2, q3)),
Max: max(max(q0, q1), max(q2, q3)),
}
}
func max(p1, p2 f32.Point) f32.Point {
p := p1
if p2.X > p.X {
p.X = p2.X
}
if p2.Y > p.Y {
p.Y = p2.Y
}
return p
}
func min(p1, p2 f32.Point) f32.Point {
p := p1
if p2.X < p.X {
p.X = p2.X
}
if p2.Y < p.Y {
p.Y = p2.Y
}
return p
}
func (enc *encoder) encodePath(verts []byte, fillMode int) {
for ; len(verts) >= scene.CommandSize+4; verts = verts[scene.CommandSize+4:] {
cmd := ops.DecodeCommand(verts[4:])
if cmd.Op() == scene.OpGap {
if fillMode != scene.FillModeNonzero {
// Skip gaps in strokes.
continue
}
// Replace them by a straight line in outlines.
cmd = scene.Line(scene.DecodeGap(cmd))
}
enc.scene = append(enc.scene, cmd)
enc.npathseg++
}
}
func (g *compute) render(images *textureAtlas, dst driver.Texture, cpuDst cpu.ImageDescriptor, tileDims image.Point, stride int) error {
const (
// wgSize is the largest and most common workgroup size.
wgSize = 128
// PARTITION_SIZE from elements.comp
partitionSize = 32 * 4
)
widthInBins := (tileDims.X + 15) / 16
heightInBins := (tileDims.Y + 7) / 8
if widthInBins*heightInBins > wgSize {
return fmt.Errorf("gpu: output too large (%dx%d)", tileDims.X*tileWidthPx, tileDims.Y*tileHeightPx)
}
enc := &g.enc
// Pad scene with zeroes to avoid reading garbage in elements.comp.
scenePadding := partitionSize - len(enc.scene)%partitionSize
enc.scene = append(enc.scene, make([]scene.Command, scenePadding)...)
scene := byteslice.Slice(enc.scene)
if s := len(scene); s > g.buffers.scene.size {
paddedCap := s * 11 / 10
if err := g.buffers.scene.ensureCapacity(g.useCPU, g.ctx, driver.BufferBindingShaderStorageRead, paddedCap); err != nil {
return err
}
}
g.buffers.scene.upload(scene)
// alloc is the number of allocated bytes for static buffers.
var alloc uint32
round := func(v, quantum int) int {
return (v + quantum - 1) &^ (quantum - 1)
}
malloc := func(size int) memAlloc {
size = round(size, 4)
offset := alloc
alloc += uint32(size)
return memAlloc{offset /*, uint32(size)*/}
}
*g.conf = config{
n_elements: uint32(enc.npath),
n_pathseg: uint32(enc.npathseg),
width_in_tiles: uint32(tileDims.X),
height_in_tiles: uint32(tileDims.Y),
tile_alloc: malloc(enc.npath * pathSize),
bin_alloc: malloc(round(enc.npath, wgSize) * binSize),
ptcl_alloc: malloc(tileDims.X * tileDims.Y * ptclInitialAlloc),
pathseg_alloc: malloc(enc.npathseg * pathsegSize),
anno_alloc: malloc(enc.npath * annoSize),
trans_alloc: malloc(enc.ntrans * transSize),
}
numPartitions := (enc.numElements() + 127) / 128
// clearSize is the atomic partition counter plus flag and 2 states per partition.
clearSize := 4 + numPartitions*stateStride
if clearSize > g.buffers.state.size {
paddedCap := clearSize * 11 / 10
if err := g.buffers.state.ensureCapacity(g.useCPU, g.ctx, driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite, paddedCap); err != nil {
return err
}
}
confData := byteslice.Struct(g.conf)
g.buffers.config.ensureCapacity(g.useCPU, g.ctx, driver.BufferBindingShaderStorageRead, len(confData))
g.buffers.config.upload(confData)
minSize := int(unsafe.Sizeof(memoryHeader{})) + int(alloc)
if minSize > g.buffers.memory.size {
// Add space for dynamic GPU allocations.
const sizeBump = 4 * 1024 * 1024
minSize += sizeBump
if err := g.buffers.memory.ensureCapacity(g.useCPU, g.ctx, driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite, minSize); err != nil {
return err
}
}
for {
*g.memHeader = memoryHeader{
mem_offset: alloc,
}
g.buffers.memory.upload(byteslice.Struct(g.memHeader))
g.buffers.state.upload(g.zeros(clearSize))
if !g.useCPU {
g.ctx.BeginCompute()
g.ctx.BindImageTexture(kernel4OutputUnit, dst)
img := g.output.nullMaterials
if images != nil {
img = images.image
}
g.ctx.BindImageTexture(kernel4AtlasUnit, img)
} else {
*g.output.descriptors.Binding2() = cpuDst
if images != nil {
*g.output.descriptors.Binding3() = images.cpuImage
}
}
g.bindBuffers()
g.memoryBarrier()
g.dispatch(g.programs.elements, numPartitions, 1, 1)
g.memoryBarrier()
g.dispatch(g.programs.tileAlloc, (enc.npath+wgSize-1)/wgSize, 1, 1)
g.memoryBarrier()
g.dispatch(g.programs.pathCoarse, (enc.npathseg+31)/32, 1, 1)
g.memoryBarrier()
g.dispatch(g.programs.backdrop, (enc.npath+wgSize-1)/wgSize, 1, 1)
// No barrier needed between backdrop and binning.
g.dispatch(g.programs.binning, (enc.npath+wgSize-1)/wgSize, 1, 1)
g.memoryBarrier()
g.dispatch(g.programs.coarse, widthInBins, heightInBins, 1)
g.memoryBarrier()
g.dispatch(g.programs.kernel4, tileDims.X, tileDims.Y, 1)
g.memoryBarrier()
if !g.useCPU {
g.ctx.EndCompute()
} else {
g.dispatcher.Sync()
}
if err := g.buffers.memory.download(byteslice.Struct(g.memHeader)); err != nil {
if err == driver.ErrContentLost {
continue
}
return err
}
switch errCode := g.memHeader.mem_error; errCode {
case memNoError:
if g.useCPU {
w, h := tileDims.X*tileWidthPx, tileDims.Y*tileHeightPx
dst.Upload(image.Pt(0, 0), image.Pt(w, h), cpuDst.Data(), stride)
}
return nil
case memMallocFailed:
// Resize memory and try again.
sz := g.buffers.memory.size * 15 / 10
if err := g.buffers.memory.ensureCapacity(g.useCPU, g.ctx, driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite, sz); err != nil {
return err
}
continue
default:
return fmt.Errorf("compute: shader program failed with error %d", errCode)
}
}
}
func (g *compute) memoryBarrier() {
if g.useCPU {
g.dispatcher.Barrier()
}
}
func (g *compute) dispatch(p computeProgram, x, y, z int) {
if !g.useCPU {
g.ctx.BindProgram(p.prog)
g.ctx.DispatchCompute(x, y, z)
} else {
g.dispatcher.Dispatch(p.progInfo, p.descriptors, x, y, z)
}
}
// zeros returns a byte slice with size bytes of zeros.
func (g *compute) zeros(size int) []byte {
if cap(g.zeroSlice) < size {
g.zeroSlice = append(g.zeroSlice, make([]byte, size)...)
}
return g.zeroSlice[:size]
}
func (g *compute) touchAlloc(a *atlasAlloc) {
if a.dead {
panic("re-use of dead allocation")
}
a.frameCount = g.frameCount
a.atlas.lastFrame = a.frameCount
}
func (g *compute) atlasAlloc(q allocQuery) (atlasAlloc, bool) {
var (
place placement
fits bool
atlas = q.atlas
)
if atlas != nil {
place, fits = atlas.packer.tryAdd(q.size)
if !fits {
atlas.compact = true
}
}
if atlas == nil {
// Look for matching atlas to re-use.
for _, a := range g.atlases {
if q.empty && len(a.allocs) > 0 {
continue
}
if q.nocompact && a.compact {
continue
}
if a.format != q.format || a.bindings&q.bindings != q.bindings {
continue
}
place, fits = a.packer.tryAdd(q.size)
if !fits {
a.compact = true
continue
}
atlas = a
break
}
}
if atlas == nil {
atlas = &textureAtlas{
format: q.format,
bindings: q.bindings,
}
atlas.packer.maxDims = image.Pt(g.maxTextureDim, g.maxTextureDim)
atlas.packer.newPage()
g.atlases = append(g.atlases, atlas)
place, fits = atlas.packer.tryAdd(q.size)
if !fits {
panic(fmt.Errorf("compute: atlas allocation too large (%v)", q.size))
}
}
if !fits {
return atlasAlloc{}, false
}
atlas.lastFrame = g.frameCount
return atlasAlloc{
frameCount: g.frameCount,
atlas: atlas,
rect: image.Rectangle{Min: place.Pos, Max: place.Pos.Add(q.size)},
}, true
}
func (g *compute) realizeAtlas(atlas *textureAtlas, useCPU bool, size image.Point) error {
defer func() {
atlas.packer.maxDims = atlas.size
atlas.realized = true
atlas.ensureCPUImage(useCPU)
}()
if atlas.size.X >= size.X && atlas.size.Y >= size.Y {
return nil
}
if atlas.realized {
panic("resizing a realized atlas")
}
if err := atlas.resize(g.ctx, size); err != nil {
return err
}
return nil
}
func (a *textureAtlas) resize(ctx driver.Device, size image.Point) error {
a.Release()
img, err := ctx.NewTexture(a.format, size.X, size.Y,
driver.FilterNearest,
driver.FilterNearest,
a.bindings)
if err != nil {
return err
}
a.image = img
a.size = size
return nil
}
func (a *textureAtlas) ensureCPUImage(useCPU bool) {
if !useCPU || a.hasCPU {
return
}
a.hasCPU = true
a.cpuImage = cpu.NewImageRGBA(a.size.X, a.size.Y)
}
func (g *compute) Release() {
if g.useCPU {
g.dispatcher.Stop()
}
type resource interface {
Release()
}
res := []resource{
g.output.nullMaterials,
&g.programs.elements,
&g.programs.tileAlloc,
&g.programs.pathCoarse,
&g.programs.backdrop,
&g.programs.binning,
&g.programs.coarse,
&g.programs.kernel4,
g.output.blitPipeline,
&g.output.buffer,
g.output.uniBuf,
&g.buffers.scene,
&g.buffers.state,
&g.buffers.memory,
&g.buffers.config,
g.materials.pipeline,
&g.materials.buffer,
g.materials.uniforms.buf,
g.timers.t,
}
for _, r := range res {
if r != nil {
r.Release()
}
}
for _, a := range g.atlases {
a.Release()
}
g.ctx.Release()
*g = compute{}
}
func (a *textureAtlas) Release() {
if a.image != nil {
a.image.Release()
a.image = nil
}
a.cpuImage.Free()
a.hasCPU = false
}
func (g *compute) bindBuffers() {
g.bindStorageBuffers(g.programs.elements, g.buffers.memory, g.buffers.config, g.buffers.scene, g.buffers.state)
g.bindStorageBuffers(g.programs.tileAlloc, g.buffers.memory, g.buffers.config)
g.bindStorageBuffers(g.programs.pathCoarse, g.buffers.memory, g.buffers.config)
g.bindStorageBuffers(g.programs.backdrop, g.buffers.memory, g.buffers.config)
g.bindStorageBuffers(g.programs.binning, g.buffers.memory, g.buffers.config)
g.bindStorageBuffers(g.programs.coarse, g.buffers.memory, g.buffers.config)
g.bindStorageBuffers(g.programs.kernel4, g.buffers.memory, g.buffers.config)
}
func (p *computeProgram) Release() {
if p.prog != nil {
p.prog.Release()
}
*p = computeProgram{}
}
func (b *sizedBuffer) Release() {
if b.buffer != nil {
b.buffer.Release()
}
b.cpuBuf.Free()
*b = sizedBuffer{}
}
func (b *sizedBuffer) ensureCapacity(useCPU bool, ctx driver.Device, binding driver.BufferBinding, size int) error {
if b.size >= size {
return nil
}
if b.buffer != nil {
b.Release()
}
b.cpuBuf.Free()
if !useCPU {
buf, err := ctx.NewBuffer(binding, size)
if err != nil {
return err
}
b.buffer = buf
} else {
b.cpuBuf = cpu.NewBuffer(size)
}
b.size = size
return nil
}
func (b *sizedBuffer) download(data []byte) error {
if b.buffer != nil {
return b.buffer.Download(data)
} else {
copy(data, b.cpuBuf.Data())
return nil
}
}
func (b *sizedBuffer) upload(data []byte) {
if b.buffer != nil {
b.buffer.Upload(data)
} else {
copy(b.cpuBuf.Data(), data)
}
}
func (g *compute) bindStorageBuffers(prog computeProgram, buffers ...sizedBuffer) {
for i, buf := range buffers {
if !g.useCPU {
g.ctx.BindStorageBuffer(i, buf.buffer)
} else {
*prog.buffers[i] = buf.cpuBuf
}
}
}
var bo = binary.LittleEndian
func (e *encoder) reset() {
e.scene = e.scene[:0]
e.npath = 0
e.npathseg = 0
e.ntrans = 0
}
func (e *encoder) numElements() int {
return len(e.scene)
}
func (e *encoder) append(e2 encoder) {
e.scene = append(e.scene, e2.scene...)
e.npath += e2.npath
e.npathseg += e2.npathseg
e.ntrans += e2.ntrans
}
func (e *encoder) transform(m f32.Affine2D) {
e.scene = append(e.scene, scene.Transform(m))
e.ntrans++
}
func (e *encoder) lineWidth(width float32) {
e.scene = append(e.scene, scene.SetLineWidth(width))
}
func (e *encoder) fillMode(mode scene.FillMode) {
e.scene = append(e.scene, scene.SetFillMode(mode))
}
func (e *encoder) beginClip(bbox f32.Rectangle) {
e.scene = append(e.scene, scene.BeginClip(bbox))
e.npath++
}
func (e *encoder) endClip(bbox f32.Rectangle) {
e.scene = append(e.scene, scene.EndClip(bbox))
e.npath++
}
func (e *encoder) rect(r f32.Rectangle) {
// Rectangle corners, clock-wise.
c0, c1, c2, c3 := r.Min, f32.Pt(r.Min.X, r.Max.Y), r.Max, f32.Pt(r.Max.X, r.Min.Y)
e.line(c0, c1)
e.line(c1, c2)
e.line(c2, c3)
e.line(c3, c0)
}
func (e *encoder) fillColor(col color.RGBA) {
e.scene = append(e.scene, scene.FillColor(col))
e.npath++
}
func (e *encoder) fillImage(index int, offset image.Point) {
e.scene = append(e.scene, scene.FillImage(index, offset))
e.npath++
}
func (e *encoder) line(start, end f32.Point) {
e.scene = append(e.scene, scene.Line(start, end))
e.npathseg++
}
func (e *encoder) quad(start, ctrl, end f32.Point) {
e.scene = append(e.scene, scene.Quad(start, ctrl, end))
e.npathseg++
}
func (c *collector) reset() {
c.prevFrame, c.frame = c.frame, c.prevFrame
c.profile = false
c.clipStates = c.clipStates[:0]
c.transStack = c.transStack[:0]
c.frame.reset()
}
func (c *opsCollector) reset() {
c.paths = c.paths[:0]
c.clipCmds = c.clipCmds[:0]
c.ops = c.ops[:0]
c.layers = c.layers[:0]
}
func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle, path []byte, key ops.Key, hash uint64, strokeWidth float32, push bool) {
// Rectangle clip regions.
if len(path) == 0 && !push {
// If the rectangular clip region contains a previous path it can be discarded.
p := state.clip
t := state.relTrans.Invert()
for p != nil {
// rect is the parent bounds transformed relative to the rectangle.
rect := transformBounds(t, p.bounds)
if rect.In(bounds) {
return
}
t = p.relTrans.Invert().Mul(t)
p = p.parent
}
}
absBounds := transformBounds(state.t, bounds).Bounds()
intersect := absBounds
if state.clip != nil {
intersect = state.clip.intersect.Intersect(intersect)
}
c.clipStates = append(c.clipStates, clipState{
parent: state.clip,
absBounds: absBounds,
path: path,
pathKey: key,
intersect: intersect,
clipKey: clipKey{
bounds: bounds,
relTrans: state.relTrans,
strokeWidth: strokeWidth,
pathHash: hash,
},
})
state.clip = &c.clipStates[len(c.clipStates)-1]
state.relTrans = f32.Affine2D{}
}
func (c *collector) collect(root *op.Ops, viewport image.Point, texOps *[]textureOp) {
fview := f32.Rectangle{Max: layout.FPt(viewport)}
var intOps *ops.Ops
if root != nil {
intOps = &root.Internal
}
c.reader.Reset(intOps)
var state encoderState
reset := func() {
state = encoderState{
paintKey: paintKey{
color: color.NRGBA{A: 0xff},
},
}
}
reset()
r := &c.reader
var (
pathData struct {
data []byte
key ops.Key
hash uint64
}
strWidth float32
)
c.addClip(&state, fview, fview, nil, ops.Key{}, 0, 0, false)
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
switch ops.OpType(encOp.Data[0]) {
case ops.TypeProfile:
c.profile = true
case ops.TypeTransform:
dop, push := ops.DecodeTransform(encOp.Data)
if push {
c.transStack = append(c.transStack, transEntry{t: state.t, relTrans: state.relTrans})
}
state.t = state.t.Mul(dop)
state.relTrans = state.relTrans.Mul(dop)
case ops.TypePopTransform:
n := len(c.transStack)
st := c.transStack[n-1]
c.transStack = c.transStack[:n-1]
state.t = st.t
state.relTrans = st.relTrans
case ops.TypeStroke:
strWidth = decodeStrokeOp(encOp.Data)
case ops.TypePath:
hash := bo.Uint64(encOp.Data[1:])
encOp, ok = r.Decode()
if !ok {
panic("unexpected end of path operation")
}
pathData.data = encOp.Data[ops.TypeAuxLen:]
pathData.key = encOp.Key
pathData.hash = hash
case ops.TypeClip:
var op ops.ClipOp
op.Decode(encOp.Data)
bounds := layout.FRect(op.Bounds)
c.addClip(&state, fview, bounds, pathData.data, pathData.key, pathData.hash, strWidth, true)
pathData.data = nil
strWidth = 0
case ops.TypePopClip:
state.relTrans = state.clip.relTrans.Mul(state.relTrans)
state.clip = state.clip.parent
case ops.TypeColor:
state.matType = materialColor
state.color = decodeColorOp(encOp.Data)
case ops.TypeLinearGradient:
state.matType = materialLinearGradient
op := decodeLinearGradientOp(encOp.Data)
state.stop1 = op.stop1
state.stop2 = op.stop2
state.color1 = op.color1
state.color2 = op.color2
case ops.TypeImage:
state.matType = materialTexture
state.image = decodeImageOp(encOp.Data, encOp.Refs)
case ops.TypePaint:
paintState := state
if paintState.matType == materialTexture {
// Clip to the bounds of the image, to hide other images in the atlas.
sz := state.image.src.Rect.Size()
bounds := f32.Rectangle{Max: layout.FPt(sz)}
c.addClip(&paintState, fview, bounds, nil, ops.Key{}, 0, 0, false)
}
intersect := paintState.clip.intersect
if intersect.Empty() {
break
}
// If the paint is a uniform opaque color that takes up the whole
// screen, it covers all previous paints and we can discard all
// rendering commands recorded so far.
if paintState.clip == nil && paintState.matType == materialColor && paintState.color.A == 255 {
c.clearColor = f32color.LinearFromSRGB(paintState.color).Opaque()
c.clear = true
c.frame.reset()
break
}
// Flatten clip stack.
p := paintState.clip
startIdx := len(c.frame.clipCmds)
for p != nil {
idx := len(c.frame.paths)
c.frame.paths = append(c.frame.paths, make([]byte, len(p.path))...)
path := c.frame.paths[idx:]
copy(path, p.path)
c.frame.clipCmds = append(c.frame.clipCmds, clipCmd{
state: p.clipKey,
path: path,
pathKey: p.pathKey,
absBounds: p.absBounds,
})
p = p.parent
}
clipStack := c.frame.clipCmds[startIdx:]
c.frame.ops = append(c.frame.ops, paintOp{
clipStack: clipStack,
state: paintState.paintKey,
intersect: intersect,
})
case ops.TypeSave:
id := ops.DecodeSave(encOp.Data)
c.save(id, state.t)
case ops.TypeLoad:
reset()
id := ops.DecodeLoad(encOp.Data)
state.t = c.states[id]
state.relTrans = state.t
}
}
for i := range c.frame.ops {
op := &c.frame.ops[i]
// For each clip, cull rectangular clip regions that contain its
// (transformed) bounds. addClip already handled the converse case.
// TODO: do better than O(n²) to efficiently deal with deep stacks.
for j := 0; j < len(op.clipStack)-1; j++ {
cl := op.clipStack[j]
p := cl.state
r := transformBounds(p.relTrans, p.bounds)
for k := j + 1; k < len(op.clipStack); k++ {
cl2 := op.clipStack[k]
p2 := cl2.state
if len(cl2.path) == 0 && r.In(cl2.state.bounds) {
op.clipStack = append(op.clipStack[:k], op.clipStack[k+1:]...)
k--
op.clipStack[k].state.relTrans = p2.relTrans.Mul(op.clipStack[k].state.relTrans)
}
r = transformRect(p2.relTrans, r)
}
}
// Separate the integer offset from the first transform. Two ops that differ
// only in integer offsets may share backing storage.
if len(op.clipStack) > 0 {
c := &op.clipStack[len(op.clipStack)-1]
t := c.state.relTrans
t, off := separateTransform(t)
c.state.relTrans = t
op.offset = off
op.state.t = op.state.t.Offset(layout.FPt(off.Mul(-1)))
}
op.hash = c.hashOp(*op)
op.texOpIdx = -1
switch op.state.matType {
case materialTexture:
op.texOpIdx = len(*texOps)
// Separate integer offset from transformation. TextureOps that have identical transforms
// except for their integer offsets can share a transformed image.
t := op.state.t.Offset(layout.FPt(op.offset))
t, off := separateTransform(t)
bounds := boundRectF(op.intersect).Sub(off)
*texOps = append(*texOps, textureOp{
img: op.state.image,
off: off,
key: textureKey{
bounds: bounds,
transform: t,
handle: op.state.image.handle,
},
})
}
}
}
func (c *collector) hashOp(op paintOp) uint64 {
c.hasher.Reset()
for _, cl := range op.clipStack {
k := cl.state
keyBytes := (*[unsafe.Sizeof(k)]byte)(unsafe.Pointer(unsafe.Pointer(&k)))
c.hasher.Write(keyBytes[:])
}
k := op.state
keyBytes := (*[unsafe.Sizeof(k)]byte)(unsafe.Pointer(unsafe.Pointer(&k)))
c.hasher.Write(keyBytes[:])
return c.hasher.Sum64()
}
func (g *compute) layer(viewport image.Point, texOps []textureOp) {
// Sort ops from previous frames by hash.
c := &g.collector
prevOps := c.prevFrame.ops
c.order = c.order[:0]
for i, op := range prevOps {
c.order = append(c.order, hashIndex{
index: i,
hash: op.hash,
})
}
sort.Slice(c.order, func(i, j int) bool {
return c.order[i].hash < c.order[j].hash
})
// Split layers with different materials atlas; the compute stage has only
// one materials slot.
splitLayer := func(ops []paintOp, prevLayerIdx int) {
for len(ops) > 0 {
var materials *textureAtlas
idx := 0
for idx < len(ops) {
if i := ops[idx].texOpIdx; i != -1 {
omats := texOps[i].matAlloc.alloc.atlas
if materials != nil && omats != nil && omats != materials {
break
}
materials = omats
}
idx++
}
l := layer{ops: ops[:idx], materials: materials}
if prevLayerIdx != -1 {
prev := c.prevFrame.layers[prevLayerIdx]
if !prev.alloc.dead && len(prev.ops) == len(l.ops) {
l.alloc = prev.alloc
l.materials = prev.materials
g.touchAlloc(l.alloc)
}
}
for i, op := range l.ops {
l.rect = l.rect.Union(boundRectF(op.intersect))
l.ops[i].layer = len(c.frame.layers)
}
c.frame.layers = append(c.frame.layers, l)
ops = ops[idx:]
}
}
ops := c.frame.ops
idx := 0
for idx < len(ops) {
op := ops[idx]
// Search for longest matching op sequence.
// start is the earliest index of a match.
start := searchOp(c.order, op.hash)
layerOps, prevLayerIdx := longestLayer(prevOps, c.order[start:], ops[idx:])
if len(layerOps) == 0 {
idx++
continue
}
if unmatched := ops[:idx]; len(unmatched) > 0 {
// Flush layer of unmatched ops.
splitLayer(unmatched, -1)
ops = ops[idx:]
idx = 0
}
splitLayer(layerOps, prevLayerIdx)
ops = ops[len(layerOps):]
}
if len(ops) > 0 {
splitLayer(ops, -1)
}
}
func longestLayer(prev []paintOp, order []hashIndex, ops []paintOp) ([]paintOp, int) {
longest := 0
longestIdx := -1
outer:
for len(order) > 0 {
first := order[0]
order = order[1:]
match := prev[first.index:]
// Potential match found. Now find longest matching sequence.
end := 0
layer := match[0].layer
off := match[0].offset.Sub(ops[0].offset)
for end < len(match) && end < len(ops) {
m := match[end]
o := ops[end]
// End layers on previous match.
if m.layer != layer {
break
}
// End layer when the next op doesn't match.
if m.hash != o.hash {
if end == 0 {
// Hashes are sorted so if the first op doesn't match, no
// more matches are possible.
break outer
}
break
}
if !opEqual(off, m, o) {
break
}
end++
}
if end > longest {
longest = end
longestIdx = layer
}
}
return ops[:longest], longestIdx
}
func searchOp(order []hashIndex, hash uint64) int {
lo, hi := 0, len(order)
for lo < hi {
mid := (lo + hi) / 2
if order[mid].hash < hash {
lo = mid + 1
} else {
hi = mid
}
}
return lo
}
func opEqual(off image.Point, o1 paintOp, o2 paintOp) bool {
if len(o1.clipStack) != len(o2.clipStack) {
return false
}
if o1.state != o2.state {
return false
}
if o1.offset.Sub(o2.offset) != off {
return false
}
for i, cl1 := range o1.clipStack {
cl2 := o2.clipStack[i]
if len(cl1.path) != len(cl2.path) {
return false
}
if cl1.state != cl2.state {
return false
}
if cl1.pathKey != cl2.pathKey && !bytes.Equal(cl1.path, cl2.path) {
return false
}
}
return true
}
func encodeLayer(l layer, pos image.Point, viewport image.Point, enc *encoder, texOps []textureOp) {
off := pos.Sub(l.rect.Min)
offf := layout.FPt(off)
enc.transform(f32.Affine2D{}.Offset(offf))
for _, op := range l.ops {
encodeOp(viewport, off, enc, texOps, op)
}
enc.transform(f32.Affine2D{}.Offset(offf.Mul(-1)))
}
func encodeOp(viewport image.Point, absOff image.Point, enc *encoder, texOps []textureOp, op paintOp) {
// Fill in clip bounds, which the shaders expect to be the union
// of all affected bounds.
var union f32.Rectangle
for i, cl := range op.clipStack {
union = union.Union(cl.absBounds)
op.clipStack[i].union = union
}
absOfff := layout.FPt(absOff)
fillMode := scene.FillModeNonzero
opOff := layout.FPt(op.offset)
inv := f32.Affine2D{}.Offset(opOff)
enc.transform(inv)
for i := len(op.clipStack) - 1; i >= 0; i-- {
cl := op.clipStack[i]
if w := cl.state.strokeWidth; w > 0 {
enc.fillMode(scene.FillModeStroke)
enc.lineWidth(w)
fillMode = scene.FillModeStroke
} else if fillMode != scene.FillModeNonzero {
enc.fillMode(scene.FillModeNonzero)
fillMode = scene.FillModeNonzero
}
enc.transform(cl.state.relTrans)
inv = inv.Mul(cl.state.relTrans)
if len(cl.path) == 0 {
enc.rect(cl.state.bounds)
} else {
enc.encodePath(cl.path, fillMode)
}
if i != 0 {
enc.beginClip(cl.union.Add(absOfff))
}
}
if len(op.clipStack) == 0 {
// No clipping; fill the entire view.
enc.rect(f32.Rectangle{Max: layout.FPt(viewport)})
}
switch op.state.matType {
case materialTexture:
texOp := texOps[op.texOpIdx]
off := texOp.matAlloc.alloc.rect.Min.Add(texOp.matAlloc.offset).Sub(texOp.off).Sub(absOff)
enc.fillImage(0, off)
case materialColor:
enc.fillColor(f32color.NRGBAToRGBA(op.state.color))
case materialLinearGradient:
// TODO: implement.
enc.fillColor(f32color.NRGBAToRGBA(op.state.color1))
default:
panic("not implemented")
}
enc.transform(inv.Invert())
// Pop the clip stack, except the first entry used for fill.
for i := 1; i < len(op.clipStack); i++ {
cl := op.clipStack[i]
enc.endClip(cl.union.Add(absOfff))
}
if fillMode != scene.FillModeNonzero {
enc.fillMode(scene.FillModeNonzero)
}
}
func (c *collector) save(id int, state f32.Affine2D) {
if extra := id - len(c.states) + 1; extra > 0 {
c.states = append(c.states, make([]f32.Affine2D, extra)...)
}
c.states[id] = state
}
func transformBounds(t f32.Affine2D, bounds f32.Rectangle) rectangle {
return rectangle{
t.Transform(bounds.Min), t.Transform(f32.Pt(bounds.Max.X, bounds.Min.Y)),
t.Transform(bounds.Max), t.Transform(f32.Pt(bounds.Min.X, bounds.Max.Y)),
}
}
func separateTransform(t f32.Affine2D) (f32.Affine2D, image.Point) {
sx, hx, ox, hy, sy, oy := t.Elems()
intx, fracx := math.Modf(float64(ox))
inty, fracy := math.Modf(float64(oy))
t = f32.NewAffine2D(sx, hx, float32(fracx), hy, sy, float32(fracy))
return t, image.Pt(int(intx), int(inty))
}
func transformRect(t f32.Affine2D, r rectangle) rectangle {
var tr rectangle
for i, c := range r {
tr[i] = t.Transform(c)
}
return tr
}
func (r rectangle) In(b f32.Rectangle) bool {
for _, c := range r {
inside := b.Min.X <= c.X && c.X <= b.Max.X &&
b.Min.Y <= c.Y && c.Y <= b.Max.Y
if !inside {
return false
}
}
return true
}
func (r rectangle) Contains(b f32.Rectangle) bool {
return true
}
func (r rectangle) Bounds() f32.Rectangle {
bounds := f32.Rectangle{
Min: f32.Pt(math.MaxFloat32, math.MaxFloat32),
Max: f32.Pt(-math.MaxFloat32, -math.MaxFloat32),
}
for _, c := range r {
if c.X < bounds.Min.X {
bounds.Min.X = c.X
}
if c.Y < bounds.Min.Y {
bounds.Min.Y = c.Y
}
if c.X > bounds.Max.X {
bounds.Max.X = c.X
}
if c.Y > bounds.Max.Y {
bounds.Max.Y = c.Y
}
}
return bounds
}
================================================
FILE: vendor/gioui.org/gpu/cpu.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"unsafe"
"gioui.org/cpu"
)
// This file contains code specific to running compute shaders on the CPU.
// dispatcher dispatches CPU compute programs across multiple goroutines.
type dispatcher struct {
// done is notified when a worker completes its work slice.
done chan struct{}
// work receives work slice indices. It is closed when the dispatcher is released.
work chan work
// dispatch receives compute jobs, which is then split among workers.
dispatch chan dispatch
// sync receives notification when a Sync completes.
sync chan struct{}
}
type work struct {
ctx *cpu.DispatchContext
index int
}
type dispatch struct {
_type jobType
program *cpu.ProgramInfo
descSet unsafe.Pointer
x, y, z int
}
type jobType uint8
const (
jobDispatch jobType = iota
jobBarrier
jobSync
)
func newDispatcher(workers int) *dispatcher {
d := &dispatcher{
work: make(chan work, workers),
done: make(chan struct{}, workers),
// Leave some room to avoid blocking calls to Dispatch.
dispatch: make(chan dispatch, 20),
sync: make(chan struct{}),
}
for i := 0; i < workers; i++ {
go d.worker()
}
go d.dispatcher()
return d
}
func (d *dispatcher) dispatcher() {
defer close(d.work)
var free []*cpu.DispatchContext
defer func() {
for _, ctx := range free {
ctx.Free()
}
}()
var used []*cpu.DispatchContext
for job := range d.dispatch {
switch job._type {
case jobDispatch:
if len(free) == 0 {
free = append(free, cpu.NewDispatchContext())
}
ctx := free[len(free)-1]
free = free[:len(free)-1]
used = append(used, ctx)
ctx.Prepare(cap(d.work), job.program, job.descSet, job.x, job.y, job.z)
for i := 0; i < cap(d.work); i++ {
d.work <- work{
ctx: ctx,
index: i,
}
}
case jobBarrier:
// Wait for all outstanding dispatches to complete.
for i := 0; i < len(used)*cap(d.work); i++ {
<-d.done
}
free = append(free, used...)
used = used[:0]
case jobSync:
d.sync <- struct{}{}
}
}
}
func (d *dispatcher) worker() {
thread := cpu.NewThreadContext()
defer thread.Free()
for w := range d.work {
w.ctx.Dispatch(w.index, thread)
d.done <- struct{}{}
}
}
func (d *dispatcher) Barrier() {
d.dispatch <- dispatch{_type: jobBarrier}
}
func (d *dispatcher) Sync() {
d.dispatch <- dispatch{_type: jobSync}
<-d.sync
}
func (d *dispatcher) Dispatch(program *cpu.ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
d.dispatch <- dispatch{
_type: jobDispatch,
program: program,
descSet: descSet,
x: x,
y: y,
z: z,
}
}
func (d *dispatcher) Stop() {
close(d.dispatch)
}
================================================
FILE: vendor/gioui.org/gpu/gpu.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package gpu implements the rendering of Gio drawing operations. It
is used by package app and package app/headless and is otherwise not
useful except for integrating with external window implementations.
*/
package gpu
import (
"encoding/binary"
"fmt"
"image"
"image/color"
"math"
"os"
"reflect"
"time"
"unsafe"
"gioui.org/f32"
"gioui.org/gpu/internal/driver"
"gioui.org/internal/byteslice"
"gioui.org/internal/f32color"
"gioui.org/internal/ops"
"gioui.org/internal/scene"
"gioui.org/internal/stroke"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/shader"
"gioui.org/shader/gio"
// Register backends.
_ "gioui.org/gpu/internal/d3d11"
_ "gioui.org/gpu/internal/metal"
_ "gioui.org/gpu/internal/opengl"
_ "gioui.org/gpu/internal/vulkan"
)
type GPU interface {
// Release non-Go resources. The GPU is no longer valid after Release.
Release()
// Clear sets the clear color for the next Frame.
Clear(color color.NRGBA)
// Frame draws the graphics operations from op into a viewport of target.
Frame(frame *op.Ops, target RenderTarget, viewport image.Point) error
// Profile returns the last available profiling information. Profiling
// information is requested when Frame sees an io/profile.Op, and the result
// is available through Profile at some later time.
Profile() string
}
type gpu struct {
cache *resourceCache
profile string
timers *timers
frameStart time.Time
stencilTimer, coverTimer, cleanupTimer *timer
drawOps drawOps
ctx driver.Device
renderer *renderer
}
type renderer struct {
ctx driver.Device
blitter *blitter
pather *pather
packer packer
intersections packer
}
type drawOps struct {
profile bool
reader ops.Reader
states []f32.Affine2D
transStack []f32.Affine2D
vertCache []byte
viewport image.Point
clear bool
clearColor f32color.RGBA
imageOps []imageOp
pathOps []*pathOp
pathOpCache []pathOp
qs quadSplitter
pathCache *opCache
}
type drawState struct {
t f32.Affine2D
cpath *pathOp
matType materialType
// Current paint.ImageOp
image imageOpData
// Current paint.ColorOp, if any.
color color.NRGBA
// Current paint.LinearGradientOp.
stop1 f32.Point
stop2 f32.Point
color1 color.NRGBA
color2 color.NRGBA
}
type pathOp struct {
off f32.Point
// rect tracks whether the clip stack can be represented by a
// pixel-aligned rectangle.
rect bool
// clip is the union of all
// later clip rectangles.
clip image.Rectangle
bounds f32.Rectangle
// intersect is the intersection of bounds and all
// previous clip bounds.
intersect f32.Rectangle
pathKey opKey
path bool
pathVerts []byte
parent *pathOp
place placement
}
type imageOp struct {
path *pathOp
clip image.Rectangle
material material
clipType clipType
place placement
}
func decodeStrokeOp(data []byte) float32 {
_ = data[4]
bo := binary.LittleEndian
return math.Float32frombits(bo.Uint32(data[1:]))
}
type quadsOp struct {
key opKey
aux []byte
}
type opKey struct {
outline bool
strokeWidth float32
sx, hx, sy, hy float32
ops.Key
}
type material struct {
material materialType
opaque bool
// For materialTypeColor.
color f32color.RGBA
// For materialTypeLinearGradient.
color1 f32color.RGBA
color2 f32color.RGBA
// For materialTypeTexture.
data imageOpData
uvTrans f32.Affine2D
}
// imageOpData is the shadow of paint.ImageOp.
type imageOpData struct {
src *image.RGBA
handle interface{}
}
type linearGradientOpData struct {
stop1 f32.Point
color1 color.NRGBA
stop2 f32.Point
color2 color.NRGBA
}
func decodeImageOp(data []byte, refs []interface{}) imageOpData {
handle := refs[1]
if handle == nil {
return imageOpData{}
}
return imageOpData{
src: refs[0].(*image.RGBA),
handle: handle,
}
}
func decodeColorOp(data []byte) color.NRGBA {
return color.NRGBA{
R: data[1],
G: data[2],
B: data[3],
A: data[4],
}
}
func decodeLinearGradientOp(data []byte) linearGradientOpData {
bo := binary.LittleEndian
return linearGradientOpData{
stop1: f32.Point{
X: math.Float32frombits(bo.Uint32(data[1:])),
Y: math.Float32frombits(bo.Uint32(data[5:])),
},
stop2: f32.Point{
X: math.Float32frombits(bo.Uint32(data[9:])),
Y: math.Float32frombits(bo.Uint32(data[13:])),
},
color1: color.NRGBA{
R: data[17+0],
G: data[17+1],
B: data[17+2],
A: data[17+3],
},
color2: color.NRGBA{
R: data[21+0],
G: data[21+1],
B: data[21+2],
A: data[21+3],
},
}
}
type clipType uint8
type resource interface {
release()
}
type texture struct {
src *image.RGBA
tex driver.Texture
}
type blitter struct {
ctx driver.Device
viewport image.Point
pipelines [3]*pipeline
colUniforms *blitColUniforms
texUniforms *blitTexUniforms
linearGradientUniforms *blitLinearGradientUniforms
quadVerts driver.Buffer
}
type blitColUniforms struct {
blitUniforms
_ [128 - unsafe.Sizeof(blitUniforms{}) - unsafe.Sizeof(colorUniforms{})]byte // Padding to 128 bytes.
colorUniforms
}
type blitTexUniforms struct {
blitUniforms
}
type blitLinearGradientUniforms struct {
blitUniforms
_ [128 - unsafe.Sizeof(blitUniforms{}) - unsafe.Sizeof(gradientUniforms{})]byte // Padding to 128 bytes.
gradientUniforms
}
type uniformBuffer struct {
buf driver.Buffer
ptr []byte
}
type pipeline struct {
pipeline driver.Pipeline
uniforms *uniformBuffer
}
type blitUniforms struct {
transform [4]float32
uvTransformR1 [4]float32
uvTransformR2 [4]float32
}
type colorUniforms struct {
color f32color.RGBA
}
type gradientUniforms struct {
color1 f32color.RGBA
color2 f32color.RGBA
}
type materialType uint8
const (
clipTypeNone clipType = iota
clipTypePath
clipTypeIntersection
)
const (
materialColor materialType = iota
materialLinearGradient
materialTexture
)
// New creates a GPU for the given API.
func New(api API) (GPU, error) {
d, err := driver.NewDevice(api)
if err != nil {
return nil, err
}
return NewWithDevice(d)
}
// NewWithDevice creates a GPU with a pre-existing device.
//
// Note: for internal use only.
func NewWithDevice(d driver.Device) (GPU, error) {
d.BeginFrame(nil, false, image.Point{})
defer d.EndFrame()
forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
feats := d.Caps().Features
switch {
case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
return newGPU(d)
}
return newCompute(d)
}
func newGPU(ctx driver.Device) (*gpu, error) {
g := &gpu{
cache: newResourceCache(),
}
g.drawOps.pathCache = newOpCache()
if err := g.init(ctx); err != nil {
return nil, err
}
return g, nil
}
func (g *gpu) init(ctx driver.Device) error {
g.ctx = ctx
g.renderer = newRenderer(ctx)
return nil
}
func (g *gpu) Clear(col color.NRGBA) {
g.drawOps.clear = true
g.drawOps.clearColor = f32color.LinearFromSRGB(col)
}
func (g *gpu) Release() {
g.renderer.release()
g.drawOps.pathCache.release()
g.cache.release()
if g.timers != nil {
g.timers.Release()
}
g.ctx.Release()
}
func (g *gpu) Frame(frameOps *op.Ops, target RenderTarget, viewport image.Point) error {
g.collect(viewport, frameOps)
return g.frame(target)
}
func (g *gpu) collect(viewport image.Point, frameOps *op.Ops) {
g.renderer.blitter.viewport = viewport
g.renderer.pather.viewport = viewport
g.drawOps.reset(viewport)
g.drawOps.collect(frameOps, viewport)
g.frameStart = time.Now()
if g.drawOps.profile && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
g.timers = newTimers(g.ctx)
g.stencilTimer = g.timers.newTimer()
g.coverTimer = g.timers.newTimer()
g.cleanupTimer = g.timers.newTimer()
}
}
func (g *gpu) frame(target RenderTarget) error {
viewport := g.renderer.blitter.viewport
defFBO := g.ctx.BeginFrame(target, g.drawOps.clear, viewport)
defer g.ctx.EndFrame()
g.drawOps.buildPaths(g.ctx)
for _, img := range g.drawOps.imageOps {
expandPathOp(img.path, img.clip)
}
g.stencilTimer.begin()
g.renderer.packStencils(&g.drawOps.pathOps)
g.renderer.stencilClips(g.drawOps.pathCache, g.drawOps.pathOps)
g.renderer.packIntersections(g.drawOps.imageOps)
g.renderer.prepareIntersections(g.drawOps.imageOps)
g.renderer.intersect(g.drawOps.imageOps)
g.stencilTimer.end()
g.coverTimer.begin()
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps)
d := driver.LoadDesc{
ClearColor: g.drawOps.clearColor,
}
if g.drawOps.clear {
g.drawOps.clear = false
d.Action = driver.LoadActionClear
}
g.ctx.BeginRenderPass(defFBO, d)
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
g.renderer.drawOps(g.cache, g.drawOps.imageOps)
g.coverTimer.end()
g.ctx.EndRenderPass()
g.cleanupTimer.begin()
g.cache.frame()
g.drawOps.pathCache.frame()
g.cleanupTimer.end()
if g.drawOps.profile && g.timers.ready() {
st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
ft := st + covt + cleant
q := 100 * time.Microsecond
st, covt = st.Round(q), covt.Round(q)
frameDur := time.Since(g.frameStart).Round(q)
ft = ft.Round(q)
g.profile = fmt.Sprintf("draw:%7s gpu:%7s st:%7s cov:%7s", frameDur, ft, st, covt)
}
return nil
}
func (g *gpu) Profile() string {
return g.profile
}
func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
var tex *texture
t, exists := cache.get(data.handle)
if !exists {
t = &texture{
src: data.src,
}
cache.put(data.handle, t)
}
tex = t.(*texture)
if tex.tex != nil {
return tex.tex
}
handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA, data.src.Bounds().Dx(), data.src.Bounds().Dy(), driver.FilterLinear, driver.FilterLinear, driver.BufferBindingTexture)
if err != nil {
panic(err)
}
driver.UploadImage(handle, image.Pt(0, 0), data.src)
tex.tex = handle
return tex.tex
}
func (t *texture) release() {
if t.tex != nil {
t.tex.Release()
}
}
func newRenderer(ctx driver.Device) *renderer {
r := &renderer{
ctx: ctx,
blitter: newBlitter(ctx),
pather: newPather(ctx),
}
maxDim := ctx.Caps().MaxTextureSize
// Large atlas textures cause artifacts due to precision loss in
// shaders.
if cap := 8192; maxDim > cap {
maxDim = cap
}
r.packer.maxDims = image.Pt(maxDim, maxDim)
r.intersections.maxDims = image.Pt(maxDim, maxDim)
return r
}
func (r *renderer) release() {
r.pather.release()
r.blitter.release()
}
func newBlitter(ctx driver.Device) *blitter {
quadVerts, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices,
byteslice.Slice([]float32{
-1, -1, 0, 0,
+1, -1, 1, 0,
-1, +1, 0, 1,
+1, +1, 1, 1,
}),
)
if err != nil {
panic(err)
}
b := &blitter{
ctx: ctx,
quadVerts: quadVerts,
}
b.colUniforms = new(blitColUniforms)
b.texUniforms = new(blitTexUniforms)
b.linearGradientUniforms = new(blitLinearGradientUniforms)
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
[3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
)
if err != nil {
panic(err)
}
b.pipelines = pipelines
return b
}
func (b *blitter) release() {
b.quadVerts.Release()
for _, p := range b.pipelines {
p.Release()
}
}
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) ([3]*pipeline, error) {
var pipelines [3]*pipeline
blend := driver.BlendDesc{
Enable: true,
SrcFactor: driver.BlendFactorOne,
DstFactor: driver.BlendFactorOneMinusSrcAlpha,
}
layout := driver.VertexLayout{
Inputs: []driver.InputDesc{
{Type: shader.DataTypeFloat, Size: 2, Offset: 0},
{Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
},
Stride: 4 * 4,
}
vsh, err := b.NewVertexShader(vsSrc)
if err != nil {
return pipelines, err
}
defer vsh.Release()
{
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
if err != nil {
return pipelines, err
}
defer fsh.Release()
pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
BlendDesc: blend,
VertexLayout: layout,
PixelFormat: driver.TextureFormatOutput,
Topology: driver.TopologyTriangleStrip,
})
if err != nil {
return pipelines, err
}
var vertBuffer *uniformBuffer
if u := uniforms[materialTexture]; u != nil {
vertBuffer = newUniformBuffer(b, u)
}
pipelines[materialTexture] = &pipeline{pipe, vertBuffer}
}
{
var vertBuffer *uniformBuffer
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
if err != nil {
pipelines[materialTexture].Release()
return pipelines, err
}
defer fsh.Release()
pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
BlendDesc: blend,
VertexLayout: layout,
PixelFormat: driver.TextureFormatOutput,
Topology: driver.TopologyTriangleStrip,
})
if err != nil {
pipelines[materialTexture].Release()
return pipelines, err
}
if u := uniforms[materialColor]; u != nil {
vertBuffer = newUniformBuffer(b, u)
}
pipelines[materialColor] = &pipeline{pipe, vertBuffer}
}
{
var vertBuffer *uniformBuffer
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
if err != nil {
pipelines[materialTexture].Release()
pipelines[materialColor].Release()
return pipelines, err
}
defer fsh.Release()
pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
BlendDesc: blend,
VertexLayout: layout,
PixelFormat: driver.TextureFormatOutput,
Topology: driver.TopologyTriangleStrip,
})
if err != nil {
pipelines[materialTexture].Release()
pipelines[materialColor].Release()
return pipelines, err
}
if u := uniforms[materialLinearGradient]; u != nil {
vertBuffer = newUniformBuffer(b, u)
}
pipelines[materialLinearGradient] = &pipeline{pipe, vertBuffer}
}
if err != nil {
for _, p := range pipelines {
p.Release()
}
return pipelines, err
}
return pipelines, nil
}
func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
if len(r.packer.sizes) == 0 {
return
}
fbo := -1
r.pather.begin(r.packer.sizes)
for _, p := range ops {
if fbo != p.place.Idx {
if fbo != -1 {
r.ctx.EndRenderPass()
}
fbo = p.place.Idx
f := r.pather.stenciler.cover(fbo)
r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear})
r.ctx.BindPipeline(r.pather.stenciler.pipeline.pipeline.pipeline)
r.ctx.BindIndexBuffer(r.pather.stenciler.indexBuf)
}
v, _ := pathCache.get(p.pathKey)
r.pather.stencilPath(p.clip, p.off, p.place.Pos, v.data)
}
if fbo != -1 {
r.ctx.EndRenderPass()
}
}
func (r *renderer) prepareIntersections(ops []imageOp) {
for _, img := range ops {
if img.clipType != clipTypeIntersection {
continue
}
fbo := r.pather.stenciler.cover(img.path.place.Idx)
r.ctx.PrepareTexture(fbo.tex)
}
}
func (r *renderer) intersect(ops []imageOp) {
if len(r.intersections.sizes) == 0 {
return
}
fbo := -1
r.pather.stenciler.beginIntersect(r.intersections.sizes)
for _, img := range ops {
if img.clipType != clipTypeIntersection {
continue
}
if fbo != img.place.Idx {
if fbo != -1 {
r.ctx.EndRenderPass()
}
fbo = img.place.Idx
f := r.pather.stenciler.intersections.fbos[fbo]
d := driver.LoadDesc{Action: driver.LoadActionClear}
d.ClearColor.R = 1.0
r.ctx.BeginRenderPass(f.tex, d)
r.ctx.BindPipeline(r.pather.stenciler.ipipeline.pipeline.pipeline)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
}
r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy())
r.intersectPath(img.path, img.clip)
}
if fbo != -1 {
r.ctx.EndRenderPass()
}
}
func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) {
if p.parent != nil {
r.intersectPath(p.parent, clip)
}
if !p.path {
return
}
uv := image.Rectangle{
Min: p.place.Pos,
Max: p.place.Pos.Add(p.clip.Size()),
}
o := clip.Min.Sub(p.clip.Min)
sub := image.Rectangle{
Min: o,
Max: o.Add(clip.Size()),
}
fbo := r.pather.stenciler.cover(p.place.Idx)
r.ctx.BindTexture(0, fbo.tex)
coverScale, coverOff := texSpaceTransform(layout.FRect(uv), fbo.size)
subScale, subOff := texSpaceTransform(layout.FRect(sub), p.clip.Size())
r.pather.stenciler.ipipeline.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
r.pather.stenciler.ipipeline.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y}
r.pather.stenciler.ipipeline.pipeline.UploadUniforms(r.ctx)
r.ctx.DrawArrays(0, 4)
}
func (r *renderer) packIntersections(ops []imageOp) {
r.intersections.clear()
for i, img := range ops {
var npaths int
var onePath *pathOp
for p := img.path; p != nil; p = p.parent {
if p.path {
onePath = p
npaths++
}
}
switch npaths {
case 0:
case 1:
place := onePath.place
place.Pos = place.Pos.Sub(onePath.clip.Min).Add(img.clip.Min)
ops[i].place = place
ops[i].clipType = clipTypePath
default:
sz := image.Point{X: img.clip.Dx(), Y: img.clip.Dy()}
place, ok := r.intersections.add(sz)
if !ok {
panic("internal error: if the intersection fit, the intersection should fit as well")
}
ops[i].clipType = clipTypeIntersection
ops[i].place = place
}
}
}
func (r *renderer) packStencils(pops *[]*pathOp) {
r.packer.clear()
ops := *pops
// Allocate atlas space for cover textures.
var i int
for i < len(ops) {
p := ops[i]
if p.clip.Empty() {
ops[i] = ops[len(ops)-1]
ops = ops[:len(ops)-1]
continue
}
sz := image.Point{X: p.clip.Dx(), Y: p.clip.Dy()}
place, ok := r.packer.add(sz)
if !ok {
// The clip area is at most the entire screen. Hopefully no
// screen is larger than GL_MAX_TEXTURE_SIZE.
panic(fmt.Errorf("clip area %v is larger than maximum texture size %v", p.clip, r.packer.maxDims))
}
p.place = place
i++
}
*pops = ops
}
// boundRectF returns a bounding image.Rectangle for a f32.Rectangle.
func boundRectF(r f32.Rectangle) image.Rectangle {
return image.Rectangle{
Min: image.Point{
X: int(floor(r.Min.X)),
Y: int(floor(r.Min.Y)),
},
Max: image.Point{
X: int(ceil(r.Max.X)),
Y: int(ceil(r.Max.Y)),
},
}
}
func ceil(v float32) int {
return int(math.Ceil(float64(v)))
}
func floor(v float32) int {
return int(math.Floor(float64(v)))
}
func (d *drawOps) reset(viewport image.Point) {
d.profile = false
d.viewport = viewport
d.imageOps = d.imageOps[:0]
d.pathOps = d.pathOps[:0]
d.pathOpCache = d.pathOpCache[:0]
d.vertCache = d.vertCache[:0]
d.transStack = d.transStack[:0]
}
func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
viewf := f32.Rectangle{
Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
}
var ops *ops.Ops
if root != nil {
ops = &root.Internal
}
d.reader.Reset(ops)
d.collectOps(&d.reader, viewf)
}
func (d *drawOps) buildPaths(ctx driver.Device) {
for _, p := range d.pathOps {
if v, exists := d.pathCache.get(p.pathKey); !exists || v.data.data == nil {
data := buildPath(ctx, p.pathVerts)
d.pathCache.put(p.pathKey, opCacheValue{
data: data,
bounds: p.bounds,
})
}
p.pathVerts = nil
}
}
func (d *drawOps) newPathOp() *pathOp {
d.pathOpCache = append(d.pathOpCache, pathOp{})
return &d.pathOpCache[len(d.pathOpCache)-1]
}
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) {
npath := d.newPathOp()
*npath = pathOp{
parent: state.cpath,
bounds: bounds,
off: off,
intersect: bounds.Add(off),
rect: true,
}
if npath.parent != nil {
npath.rect = npath.parent.rect
npath.intersect = npath.parent.intersect.Intersect(npath.intersect)
}
if len(aux) > 0 {
npath.rect = false
npath.pathKey = auxKey
npath.path = true
npath.pathVerts = aux
d.pathOps = append(d.pathOps, npath)
}
state.cpath = npath
}
// split a transform into two parts, one which is pure offset and the
// other representing the scaling, shearing and rotation part
func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) {
sx, hx, ox, hy, sy, oy := t.Elems()
offset = f32.Point{X: ox, Y: oy}
srs = f32.NewAffine2D(sx, hx, 0, hy, sy, 0)
return
}
func (d *drawOps) save(id int, state f32.Affine2D) {
if extra := id - len(d.states) + 1; extra > 0 {
d.states = append(d.states, make([]f32.Affine2D, extra)...)
}
d.states[id] = state
}
func (k opKey) SetTransform(t f32.Affine2D) opKey {
sx, hx, _, hy, sy, _ := t.Elems()
k.sx = sx
k.hx = hx
k.hy = hy
k.sy = sy
return k
}
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
var (
quads quadsOp
state drawState
)
reset := func() {
state = drawState{
color: color.NRGBA{A: 0xff},
}
}
reset()
loop:
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
switch ops.OpType(encOp.Data[0]) {
case ops.TypeProfile:
d.profile = true
case ops.TypeTransform:
dop, push := ops.DecodeTransform(encOp.Data)
if push {
d.transStack = append(d.transStack, state.t)
}
state.t = state.t.Mul(dop)
case ops.TypePopTransform:
n := len(d.transStack)
state.t = d.transStack[n-1]
d.transStack = d.transStack[:n-1]
case ops.TypeStroke:
quads.key.strokeWidth = decodeStrokeOp(encOp.Data)
case ops.TypePath:
encOp, ok = r.Decode()
if !ok {
break loop
}
quads.aux = encOp.Data[ops.TypeAuxLen:]
quads.key.Key = encOp.Key
case ops.TypeClip:
var op ops.ClipOp
op.Decode(encOp.Data)
quads.key.outline = op.Outline
bounds := layout.FRect(op.Bounds)
trans, off := splitTransform(state.t)
if len(quads.aux) > 0 {
// There is a clipping path, build the gpu data and update the
// cache key such that it will be equal only if the transform is the
// same also. Use cached data if we have it.
quads.key = quads.key.SetTransform(trans)
if v, ok := d.pathCache.get(quads.key); ok {
// Since the GPU data exists in the cache aux will not be used.
// Why is this not used for the offset shapes?
bounds = v.bounds
} else {
var pathData []byte
pathData, bounds = d.buildVerts(
quads.aux, trans, quads.key.outline, quads.key.strokeWidth,
)
quads.aux = pathData
// add it to the cache, without GPU data, so the transform can be
// reused.
d.pathCache.put(quads.key, opCacheValue{bounds: bounds})
}
} else {
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
quads.key = opKey{Key: encOp.Key}
}
d.addClipPath(&state, quads.aux, quads.key, bounds, off, true)
quads = quadsOp{}
case ops.TypePopClip:
state.cpath = state.cpath.parent
case ops.TypeColor:
state.matType = materialColor
state.color = decodeColorOp(encOp.Data)
case ops.TypeLinearGradient:
state.matType = materialLinearGradient
op := decodeLinearGradientOp(encOp.Data)
state.stop1 = op.stop1
state.stop2 = op.stop2
state.color1 = op.color1
state.color2 = op.color2
case ops.TypeImage:
state.matType = materialTexture
state.image = decodeImageOp(encOp.Data, encOp.Refs)
case ops.TypePaint:
// Transform (if needed) the painting rectangle and if so generate a clip path,
// for those cases also compute a partialTrans that maps texture coordinates between
// the new bounding rectangle and the transformed original paint rectangle.
t, off := splitTransform(state.t)
// Fill the clip area, unless the material is a (bounded) image.
// TODO: Find a tighter bound.
inf := float32(1e6)
dst := f32.Rect(-inf, -inf, inf, inf)
if state.matType == materialTexture {
sz := state.image.src.Rect.Size()
dst = f32.Rectangle{Max: layout.FPt(sz)}
}
clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
cl := viewport.Intersect(bnd.Add(off))
if state.cpath != nil {
cl = state.cpath.intersect.Intersect(cl)
}
if cl.Empty() {
continue
}
if clipData != nil {
// The paint operation is sheared or rotated, add a clip path representing
// this transformed rectangle.
k := opKey{Key: encOp.Key}
k.SetTransform(t) // TODO: This call has no effect.
d.addClipPath(&state, clipData, k, bnd, off, false)
}
bounds := boundRectF(cl)
mat := state.materialFor(bnd, off, partialTrans, bounds)
rect := state.cpath == nil || state.cpath.rect
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) {
// The image is a uniform opaque color and takes up the whole screen.
// Scrap images up to and including this image and set clear color.
d.imageOps = d.imageOps[:0]
d.clearColor = mat.color.Opaque()
d.clear = true
continue
}
img := imageOp{
path: state.cpath,
clip: bounds,
material: mat,
}
d.imageOps = append(d.imageOps, img)
if clipData != nil {
// we added a clip path that should not remain
state.cpath = state.cpath.parent
}
case ops.TypeSave:
id := ops.DecodeSave(encOp.Data)
d.save(id, state.t)
case ops.TypeLoad:
reset()
id := ops.DecodeLoad(encOp.Data)
state.t = d.states[id]
}
}
}
func expandPathOp(p *pathOp, clip image.Rectangle) {
for p != nil {
pclip := p.clip
if !pclip.Empty() {
clip = clip.Union(pclip)
}
p.clip = clip
p = p.parent
}
}
func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
var m material
switch d.matType {
case materialColor:
m.material = materialColor
m.color = f32color.LinearFromSRGB(d.color)
m.opaque = m.color.A == 1.0
case materialLinearGradient:
m.material = materialLinearGradient
m.color1 = f32color.LinearFromSRGB(d.color1)
m.color2 = f32color.LinearFromSRGB(d.color2)
m.opaque = m.color1.A == 1.0 && m.color2.A == 1.0
m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
case materialTexture:
m.material = materialTexture
dr := boundRectF(rect.Add(off))
sz := d.image.src.Bounds().Size()
sr := f32.Rectangle{
Max: f32.Point{
X: float32(sz.X),
Y: float32(sz.Y),
},
}
dx := float32(dr.Dx())
sdx := sr.Dx()
sr.Min.X += float32(clip.Min.X-dr.Min.X) * sdx / dx
sr.Max.X -= float32(dr.Max.X-clip.Max.X) * sdx / dx
dy := float32(dr.Dy())
sdy := sr.Dy()
sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
uvScale, uvOffset := texSpaceTransform(sr, sz)
m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
m.data = d.image
}
return m
}
func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
for _, img := range ops {
m := img.material
if m.material == materialTexture {
r.texHandle(cache, m.data)
}
}
}
func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
for _, img := range ops {
m := img.material
switch m.material {
case materialTexture:
r.ctx.PrepareTexture(r.texHandle(cache, m.data))
}
var fbo stencilFBO
switch img.clipType {
case clipTypeNone:
continue
case clipTypePath:
fbo = r.pather.stenciler.cover(img.place.Idx)
case clipTypeIntersection:
fbo = r.pather.stenciler.intersections.fbos[img.place.Idx]
}
r.ctx.PrepareTexture(fbo.tex)
}
}
func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
var coverTex driver.Texture
for _, img := range ops {
m := img.material
switch m.material {
case materialTexture:
r.ctx.BindTexture(0, r.texHandle(cache, m.data))
}
drc := img.clip
scale, off := clipSpaceTransform(drc, r.blitter.viewport)
var fbo stencilFBO
switch img.clipType {
case clipTypeNone:
p := r.blitter.pipelines[m.material]
r.ctx.BindPipeline(p.pipeline)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
r.blitter.blit(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans)
continue
case clipTypePath:
fbo = r.pather.stenciler.cover(img.place.Idx)
case clipTypeIntersection:
fbo = r.pather.stenciler.intersections.fbos[img.place.Idx]
}
if coverTex != fbo.tex {
coverTex = fbo.tex
r.ctx.BindTexture(1, coverTex)
}
uv := image.Rectangle{
Min: img.place.Pos,
Max: img.place.Pos.Add(drc.Size()),
}
coverScale, coverOff := texSpaceTransform(layout.FRect(uv), fbo.size)
p := r.pather.coverer.pipelines[m.material]
r.ctx.BindPipeline(p.pipeline)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
r.pather.cover(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
}
}
func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
p := b.pipelines[mat]
b.ctx.BindPipeline(p.pipeline)
var uniforms *blitUniforms
switch mat {
case materialColor:
b.colUniforms.color = col
uniforms = &b.colUniforms.blitUniforms
case materialTexture:
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
b.texUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
b.texUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &b.texUniforms.blitUniforms
case materialLinearGradient:
b.linearGradientUniforms.color1 = col1
b.linearGradientUniforms.color2 = col2
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
b.linearGradientUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
b.linearGradientUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &b.linearGradientUniforms.blitUniforms
}
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
p.UploadUniforms(b.ctx)
b.ctx.DrawArrays(0, 4)
}
// newUniformBuffer creates a new GPU uniform buffer backed by the
// structure uniformBlock points to.
func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer {
ref := reflect.ValueOf(uniformBlock)
// Determine the size of the uniforms structure, *uniforms.
size := ref.Elem().Type().Size()
// Map the uniforms structure as a byte slice.
ptr := (*[1 << 30]byte)(unsafe.Pointer(ref.Pointer()))[:size:size]
ubuf, err := b.NewBuffer(driver.BufferBindingUniforms, len(ptr))
if err != nil {
panic(err)
}
return &uniformBuffer{buf: ubuf, ptr: ptr}
}
func (u *uniformBuffer) Upload() {
u.buf.Upload(u.ptr)
}
func (u *uniformBuffer) Release() {
u.buf.Release()
u.buf = nil
}
func (p *pipeline) UploadUniforms(ctx driver.Device) {
if p.uniforms != nil {
p.uniforms.Upload()
ctx.BindUniforms(p.uniforms.buf)
}
}
func (p *pipeline) Release() {
p.pipeline.Release()
if p.uniforms != nil {
p.uniforms.Release()
}
*p = pipeline{}
}
// texSpaceTransform return the scale and offset that transforms the given subimage
// into quad texture coordinates.
func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Point) {
size := f32.Point{X: float32(bounds.X), Y: float32(bounds.Y)}
scale := f32.Point{X: r.Dx() / size.X, Y: r.Dy() / size.Y}
offset := f32.Point{X: r.Min.X / size.X, Y: r.Min.Y / size.Y}
return scale, offset
}
// gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)].
func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point) f32.Affine2D {
d := stop2.Sub(stop1)
l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
// TODO: optimize
zp := f32.Point{}
return f32.Affine2D{}.
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
Offset(zp.Sub(stop1)). // offset to first stop point
Rotate(zp, a). // rotate to align gradient
Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
}
// clipSpaceTransform returns the scale and offset that transforms the given
// rectangle from a viewport into GPU driver device coordinates.
func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32.Point) {
// First, transform UI coordinates to device coordinates:
//
// [(-1, -1) (+1, -1)]
// [(-1, +1) (+1, +1)]
//
x, y := float32(r.Min.X), float32(r.Min.Y)
w, h := float32(r.Dx()), float32(r.Dy())
vx, vy := 2/float32(viewport.X), 2/float32(viewport.Y)
x = x*vx - 1
y = y*vy - 1
w *= vx
h *= vy
// Then, compute the transformation from the fullscreen quad to
// the rectangle at (x, y) and dimensions (w, h).
scale := f32.Point{X: w * .5, Y: h * .5}
offset := f32.Point{X: x + w*.5, Y: y + h*.5}
return scale, offset
}
// Fill in maximal Y coordinates of the NW and NE corners.
func fillMaxY(verts []byte) {
contour := 0
bo := binary.LittleEndian
for len(verts) > 0 {
maxy := float32(math.Inf(-1))
i := 0
for ; i+vertStride*4 <= len(verts); i += vertStride * 4 {
vert := verts[i : i+vertStride]
// MaxY contains the integer contour index.
pathContour := int(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).MaxY)):]))
if contour != pathContour {
contour = pathContour
break
}
fromy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).FromY)):]))
ctrly := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).CtrlY)):]))
toy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).ToY)):]))
if fromy > maxy {
maxy = fromy
}
if ctrly > maxy {
maxy = ctrly
}
if toy > maxy {
maxy = toy
}
}
fillContourMaxY(maxy, verts[:i])
verts = verts[i:]
}
}
func fillContourMaxY(maxy float32, verts []byte) {
bo := binary.LittleEndian
for i := 0; i < len(verts); i += vertStride {
off := int(unsafe.Offsetof(((*vertex)(nil)).MaxY))
bo.PutUint32(verts[i+off:], math.Float32bits(maxy))
}
}
func (d *drawOps) writeVertCache(n int) []byte {
d.vertCache = append(d.vertCache, make([]byte, n)...)
return d.vertCache[len(d.vertCache)-n:]
}
// transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, strWidth float32) (verts []byte, bounds f32.Rectangle) {
inf := float32(math.Inf(+1))
d.qs.bounds = f32.Rectangle{
Min: f32.Point{X: inf, Y: inf},
Max: f32.Point{X: -inf, Y: -inf},
}
d.qs.d = d
startLength := len(d.vertCache)
switch {
case strWidth > 0:
// Stroke path.
ss := stroke.StrokeStyle{
Width: strWidth,
}
quads := stroke.StrokePathCommands(ss, pathData)
for _, quad := range quads {
d.qs.contour = quad.Contour
quad.Quad = quad.Quad.Transform(tr)
d.qs.splitAndEncode(quad.Quad)
}
case outline:
decodeToOutlineQuads(&d.qs, tr, pathData)
}
fillMaxY(d.vertCache[startLength:])
return d.vertCache[startLength:], d.qs.bounds
}
// decodeOutlineQuads decodes scene commands, splits them into quadratic béziers
// as needed and feeds them to the supplied splitter.
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
for len(pathData) >= scene.CommandSize+4 {
qs.contour = bo.Uint32(pathData)
cmd := ops.DecodeCommand(pathData[4:])
switch cmd.Op() {
case scene.OpLine:
var q stroke.QuadSegment
q.From, q.To = scene.DecodeLine(cmd)
q.Ctrl = q.From.Add(q.To).Mul(.5)
q = q.Transform(tr)
qs.splitAndEncode(q)
case scene.OpGap:
var q stroke.QuadSegment
q.From, q.To = scene.DecodeGap(cmd)
q.Ctrl = q.From.Add(q.To).Mul(.5)
q = q.Transform(tr)
qs.splitAndEncode(q)
case scene.OpQuad:
var q stroke.QuadSegment
q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd)
q = q.Transform(tr)
qs.splitAndEncode(q)
case scene.OpCubic:
for _, q := range stroke.SplitCubic(scene.DecodeCubic(cmd)) {
q = q.Transform(tr)
qs.splitAndEncode(q)
}
default:
panic("unsupported scene command")
}
pathData = pathData[scene.CommandSize+4:]
}
}
// create GPU vertices for transformed r, find the bounds and establish texture transform.
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
if isPureOffset(tr) {
// fast-path to allow blitting of pure rectangles
_, _, ox, _, _, oy := tr.Elems()
off := f32.Pt(ox, oy)
bnd.Min = r.Min.Add(off)
bnd.Max = r.Max.Add(off)
return
}
// transform all corners, find new bounds
corners := [4]f32.Point{
tr.Transform(r.Min), tr.Transform(f32.Pt(r.Max.X, r.Min.Y)),
tr.Transform(r.Max), tr.Transform(f32.Pt(r.Min.X, r.Max.Y)),
}
bnd.Min = f32.Pt(math.MaxFloat32, math.MaxFloat32)
bnd.Max = f32.Pt(-math.MaxFloat32, -math.MaxFloat32)
for _, c := range corners {
if c.X < bnd.Min.X {
bnd.Min.X = c.X
}
if c.Y < bnd.Min.Y {
bnd.Min.Y = c.Y
}
if c.X > bnd.Max.X {
bnd.Max.X = c.X
}
if c.Y > bnd.Max.Y {
bnd.Max.Y = c.Y
}
}
// build the GPU vertices
l := len(d.vertCache)
d.vertCache = append(d.vertCache, make([]byte, vertStride*4*4)...)
aux = d.vertCache[l:]
encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1])
encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2])
encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3])
encodeQuadTo(aux[vertStride*4*3:], 0, corners[3], corners[3].Add(corners[0]).Mul(0.5), corners[0])
fillMaxY(aux)
// establish the transform mapping from bounds rectangle to transformed corners
var P1, P2, P3 f32.Point
P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
sx, sy := P2.X-P3.X, P2.Y-P3.Y
ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
return
}
func isPureOffset(t f32.Affine2D) bool {
a, b, _, d, e, _ := t.Elems()
return a == 1 && b == 0 && d == 0 && e == 1
}
================================================
FILE: vendor/gioui.org/gpu/internal/d3d11/d3d11.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
// This file exists so this package builds on non-Windows platforms.
package d3d11
================================================
FILE: vendor/gioui.org/gpu/internal/d3d11/d3d11_windows.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package d3d11
import (
"errors"
"fmt"
"image"
"math"
"reflect"
"unsafe"
"golang.org/x/sys/windows"
"gioui.org/gpu/internal/driver"
"gioui.org/internal/d3d11"
"gioui.org/shader"
)
type Backend struct {
dev *d3d11.Device
ctx *d3d11.DeviceContext
// Temporary storage to avoid garbage.
clearColor [4]float32
viewport d3d11.VIEWPORT
pipeline *Pipeline
vert struct {
buffer *Buffer
offset int
}
program *Program
caps driver.Caps
floatFormat uint32
}
type Pipeline struct {
vert *d3d11.VertexShader
frag *d3d11.PixelShader
layout *d3d11.InputLayout
blend *d3d11.BlendState
stride int
topology driver.Topology
}
type Texture struct {
backend *Backend
format uint32
bindings driver.BufferBinding
tex *d3d11.Texture2D
sampler *d3d11.SamplerState
resView *d3d11.ShaderResourceView
uaView *d3d11.UnorderedAccessView
renderTarget *d3d11.RenderTargetView
width int
height int
foreign bool
}
type VertexShader struct {
backend *Backend
shader *d3d11.VertexShader
src shader.Sources
}
type FragmentShader struct {
backend *Backend
shader *d3d11.PixelShader
}
type Program struct {
backend *Backend
shader *d3d11.ComputeShader
}
type Buffer struct {
backend *Backend
bind uint32
buf *d3d11.Buffer
resView *d3d11.ShaderResourceView
uaView *d3d11.UnorderedAccessView
size int
immutable bool
}
func init() {
driver.NewDirect3D11Device = newDirect3D11Device
}
func detectFloatFormat(dev *d3d11.Device) (uint32, bool) {
formats := []uint32{
d3d11.DXGI_FORMAT_R16_FLOAT,
d3d11.DXGI_FORMAT_R32_FLOAT,
d3d11.DXGI_FORMAT_R16G16_FLOAT,
d3d11.DXGI_FORMAT_R32G32_FLOAT,
// These last two are really wasteful, but c'est la vie.
d3d11.DXGI_FORMAT_R16G16B16A16_FLOAT,
d3d11.DXGI_FORMAT_R32G32B32A32_FLOAT,
}
for _, format := range formats {
need := uint32(d3d11.FORMAT_SUPPORT_TEXTURE2D | d3d11.FORMAT_SUPPORT_RENDER_TARGET)
if support, _ := dev.CheckFormatSupport(format); support&need == need {
return format, true
}
}
return 0, false
}
func newDirect3D11Device(api driver.Direct3D11) (driver.Device, error) {
dev := (*d3d11.Device)(api.Device)
b := &Backend{
dev: dev,
ctx: dev.GetImmediateContext(),
caps: driver.Caps{
MaxTextureSize: 2048, // 9.1 maximum
Features: driver.FeatureSRGB,
},
}
featLvl := dev.GetFeatureLevel()
switch {
case featLvl < d3d11.FEATURE_LEVEL_9_1:
d3d11.IUnknownRelease(unsafe.Pointer(dev), dev.Vtbl.Release)
d3d11.IUnknownRelease(unsafe.Pointer(b.ctx), b.ctx.Vtbl.Release)
return nil, fmt.Errorf("d3d11: feature level too low: %d", featLvl)
case featLvl >= d3d11.FEATURE_LEVEL_11_0:
b.caps.MaxTextureSize = 16384
b.caps.Features |= driver.FeatureCompute
case featLvl >= d3d11.FEATURE_LEVEL_9_3:
b.caps.MaxTextureSize = 4096
}
if fmt, ok := detectFloatFormat(dev); ok {
b.floatFormat = fmt
b.caps.Features |= driver.FeatureFloatRenderTargets
}
// Disable backface culling to match OpenGL.
state, err := dev.CreateRasterizerState(&d3d11.RASTERIZER_DESC{
CullMode: d3d11.CULL_NONE,
FillMode: d3d11.FILL_SOLID,
})
if err != nil {
return nil, err
}
defer d3d11.IUnknownRelease(unsafe.Pointer(state), state.Vtbl.Release)
b.ctx.RSSetState(state)
return b, nil
}
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
var (
renderTarget *d3d11.RenderTargetView
)
if target != nil {
switch t := target.(type) {
case driver.Direct3D11RenderTarget:
renderTarget = (*d3d11.RenderTargetView)(t.RenderTarget)
case *Texture:
renderTarget = t.renderTarget
default:
panic(fmt.Errorf("d3d11: invalid render target type: %T", target))
}
}
b.ctx.OMSetRenderTargets(renderTarget, nil)
return &Texture{backend: b, renderTarget: renderTarget, foreign: true}
}
func (b *Backend) CopyTexture(dstTex driver.Texture, dstOrigin image.Point, srcTex driver.Texture, srcRect image.Rectangle) {
dst := (*d3d11.Resource)(unsafe.Pointer(dstTex.(*Texture).tex))
src := (*d3d11.Resource)(srcTex.(*Texture).tex)
b.ctx.CopySubresourceRegion(
dst,
0, // Destination subresource.
uint32(dstOrigin.X), uint32(dstOrigin.Y), 0, // Destination coordinates (x, y, z).
src,
0, // Source subresource.
&d3d11.BOX{
Left: uint32(srcRect.Min.X),
Top: uint32(srcRect.Min.Y),
Right: uint32(srcRect.Max.X),
Bottom: uint32(srcRect.Max.Y),
Front: 0,
Back: 1,
},
)
}
func (b *Backend) EndFrame() {
}
func (b *Backend) Caps() driver.Caps {
return b.caps
}
func (b *Backend) NewTimer() driver.Timer {
panic("timers not supported")
}
func (b *Backend) IsTimeContinuous() bool {
panic("timers not supported")
}
func (b *Backend) Release() {
d3d11.IUnknownRelease(unsafe.Pointer(b.ctx), b.ctx.Vtbl.Release)
*b = Backend{}
}
func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, bindings driver.BufferBinding) (driver.Texture, error) {
var d3dfmt uint32
switch format {
case driver.TextureFormatFloat:
d3dfmt = b.floatFormat
case driver.TextureFormatSRGBA:
d3dfmt = d3d11.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
case driver.TextureFormatRGBA8:
d3dfmt = d3d11.DXGI_FORMAT_R8G8B8A8_UNORM
default:
return nil, fmt.Errorf("unsupported texture format %d", format)
}
tex, err := b.dev.CreateTexture2D(&d3d11.TEXTURE2D_DESC{
Width: uint32(width),
Height: uint32(height),
MipLevels: 1,
ArraySize: 1,
Format: d3dfmt,
SampleDesc: d3d11.DXGI_SAMPLE_DESC{
Count: 1,
Quality: 0,
},
BindFlags: convBufferBinding(bindings),
})
if err != nil {
return nil, err
}
var (
sampler *d3d11.SamplerState
resView *d3d11.ShaderResourceView
uaView *d3d11.UnorderedAccessView
fbo *d3d11.RenderTargetView
)
if bindings&driver.BufferBindingTexture != 0 {
var filter uint32
switch {
case minFilter == driver.FilterNearest && magFilter == driver.FilterNearest:
filter = d3d11.FILTER_MIN_MAG_MIP_POINT
case minFilter == driver.FilterLinear && magFilter == driver.FilterLinear:
filter = d3d11.FILTER_MIN_MAG_LINEAR_MIP_POINT
default:
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
return nil, fmt.Errorf("unsupported texture filter combination %d, %d", minFilter, magFilter)
}
var err error
sampler, err = b.dev.CreateSamplerState(&d3d11.SAMPLER_DESC{
Filter: filter,
AddressU: d3d11.TEXTURE_ADDRESS_CLAMP,
AddressV: d3d11.TEXTURE_ADDRESS_CLAMP,
AddressW: d3d11.TEXTURE_ADDRESS_CLAMP,
MaxAnisotropy: 1,
MinLOD: -math.MaxFloat32,
MaxLOD: math.MaxFloat32,
})
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
return nil, err
}
resView, err = b.dev.CreateShaderResourceView(
(*d3d11.Resource)(unsafe.Pointer(tex)),
unsafe.Pointer(&d3d11.SHADER_RESOURCE_VIEW_DESC_TEX2D{
SHADER_RESOURCE_VIEW_DESC: d3d11.SHADER_RESOURCE_VIEW_DESC{
Format: d3dfmt,
ViewDimension: d3d11.SRV_DIMENSION_TEXTURE2D,
},
Texture2D: d3d11.TEX2D_SRV{
MostDetailedMip: 0,
MipLevels: ^uint32(0),
},
}),
)
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
d3d11.IUnknownRelease(unsafe.Pointer(sampler), sampler.Vtbl.Release)
return nil, err
}
}
if bindings&driver.BufferBindingShaderStorageWrite != 0 {
uaView, err = b.dev.CreateUnorderedAccessView(
(*d3d11.Resource)(unsafe.Pointer(tex)),
unsafe.Pointer(&d3d11.UNORDERED_ACCESS_VIEW_DESC_TEX2D{
UNORDERED_ACCESS_VIEW_DESC: d3d11.UNORDERED_ACCESS_VIEW_DESC{
Format: d3dfmt,
ViewDimension: d3d11.UAV_DIMENSION_TEXTURE2D,
},
Texture2D: d3d11.TEX2D_UAV{
MipSlice: 0,
},
}),
)
if err != nil {
if sampler != nil {
d3d11.IUnknownRelease(unsafe.Pointer(sampler), sampler.Vtbl.Release)
}
if resView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(resView), resView.Vtbl.Release)
}
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
return nil, err
}
}
if bindings&driver.BufferBindingFramebuffer != 0 {
resource := (*d3d11.Resource)(unsafe.Pointer(tex))
fbo, err = b.dev.CreateRenderTargetView(resource)
if err != nil {
if uaView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(uaView), uaView.Vtbl.Release)
}
if sampler != nil {
d3d11.IUnknownRelease(unsafe.Pointer(sampler), sampler.Vtbl.Release)
}
if resView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(resView), resView.Vtbl.Release)
}
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
return nil, err
}
}
return &Texture{backend: b, format: d3dfmt, tex: tex, sampler: sampler, resView: resView, uaView: uaView, renderTarget: fbo, bindings: bindings, width: width, height: height}, nil
}
func (b *Backend) newInputLayout(vertexShader shader.Sources, layout []driver.InputDesc) (*d3d11.InputLayout, error) {
if len(vertexShader.Inputs) != len(layout) {
return nil, fmt.Errorf("NewInputLayout: got %d inputs, expected %d", len(layout), len(vertexShader.Inputs))
}
descs := make([]d3d11.INPUT_ELEMENT_DESC, len(layout))
for i, l := range layout {
inp := vertexShader.Inputs[i]
cname, err := windows.BytePtrFromString(inp.Semantic)
if err != nil {
return nil, err
}
var format uint32
switch l.Type {
case shader.DataTypeFloat:
switch l.Size {
case 1:
format = d3d11.DXGI_FORMAT_R32_FLOAT
case 2:
format = d3d11.DXGI_FORMAT_R32G32_FLOAT
case 3:
format = d3d11.DXGI_FORMAT_R32G32B32_FLOAT
case 4:
format = d3d11.DXGI_FORMAT_R32G32B32A32_FLOAT
default:
panic("unsupported data size")
}
case shader.DataTypeShort:
switch l.Size {
case 1:
format = d3d11.DXGI_FORMAT_R16_SINT
case 2:
format = d3d11.DXGI_FORMAT_R16G16_SINT
default:
panic("unsupported data size")
}
default:
panic("unsupported data type")
}
descs[i] = d3d11.INPUT_ELEMENT_DESC{
SemanticName: cname,
SemanticIndex: uint32(inp.SemanticIndex),
Format: format,
AlignedByteOffset: uint32(l.Offset),
}
}
return b.dev.CreateInputLayout(descs, []byte(vertexShader.DXBC))
}
func (b *Backend) NewBuffer(typ driver.BufferBinding, size int) (driver.Buffer, error) {
return b.newBuffer(typ, size, nil, false)
}
func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) {
return b.newBuffer(typ, len(data), data, true)
}
func (b *Backend) newBuffer(typ driver.BufferBinding, size int, data []byte, immutable bool) (*Buffer, error) {
if typ&driver.BufferBindingUniforms != 0 {
if typ != driver.BufferBindingUniforms {
return nil, errors.New("uniform buffers cannot have other bindings")
}
if size%16 != 0 {
return nil, fmt.Errorf("constant buffer size is %d, expected a multiple of 16", size)
}
}
bind := convBufferBinding(typ)
var usage, miscFlags, cpuFlags uint32
if immutable {
usage = d3d11.USAGE_IMMUTABLE
}
if typ&driver.BufferBindingShaderStorageWrite != 0 {
cpuFlags = d3d11.CPU_ACCESS_READ
}
if typ&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0 {
miscFlags |= d3d11.RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS
}
buf, err := b.dev.CreateBuffer(&d3d11.BUFFER_DESC{
ByteWidth: uint32(size),
Usage: usage,
BindFlags: bind,
CPUAccessFlags: cpuFlags,
MiscFlags: miscFlags,
}, data)
if err != nil {
return nil, err
}
var (
resView *d3d11.ShaderResourceView
uaView *d3d11.UnorderedAccessView
)
if typ&driver.BufferBindingShaderStorageWrite != 0 {
uaView, err = b.dev.CreateUnorderedAccessView(
(*d3d11.Resource)(unsafe.Pointer(buf)),
unsafe.Pointer(&d3d11.UNORDERED_ACCESS_VIEW_DESC_BUFFER{
UNORDERED_ACCESS_VIEW_DESC: d3d11.UNORDERED_ACCESS_VIEW_DESC{
Format: d3d11.DXGI_FORMAT_R32_TYPELESS,
ViewDimension: d3d11.UAV_DIMENSION_BUFFER,
},
Buffer: d3d11.BUFFER_UAV{
FirstElement: 0,
NumElements: uint32(size / 4),
Flags: d3d11.BUFFER_UAV_FLAG_RAW,
},
}),
)
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(buf), buf.Vtbl.Release)
return nil, err
}
} else if typ&driver.BufferBindingShaderStorageRead != 0 {
resView, err = b.dev.CreateShaderResourceView(
(*d3d11.Resource)(unsafe.Pointer(buf)),
unsafe.Pointer(&d3d11.SHADER_RESOURCE_VIEW_DESC_BUFFEREX{
SHADER_RESOURCE_VIEW_DESC: d3d11.SHADER_RESOURCE_VIEW_DESC{
Format: d3d11.DXGI_FORMAT_R32_TYPELESS,
ViewDimension: d3d11.SRV_DIMENSION_BUFFEREX,
},
Buffer: d3d11.BUFFEREX_SRV{
FirstElement: 0,
NumElements: uint32(size / 4),
Flags: d3d11.BUFFEREX_SRV_FLAG_RAW,
},
}),
)
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(buf), buf.Vtbl.Release)
return nil, err
}
}
return &Buffer{backend: b, buf: buf, bind: bind, size: size, resView: resView, uaView: uaView, immutable: immutable}, nil
}
func (b *Backend) NewComputeProgram(shader shader.Sources) (driver.Program, error) {
cs, err := b.dev.CreateComputeShader([]byte(shader.DXBC))
if err != nil {
return nil, err
}
return &Program{backend: b, shader: cs}, nil
}
func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) {
vsh := desc.VertexShader.(*VertexShader)
fsh := desc.FragmentShader.(*FragmentShader)
blend, err := b.newBlendState(desc.BlendDesc)
if err != nil {
return nil, err
}
var layout *d3d11.InputLayout
if l := desc.VertexLayout; l.Stride > 0 {
var err error
layout, err = b.newInputLayout(vsh.src, l.Inputs)
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(blend), blend.Vtbl.AddRef)
return nil, err
}
}
// Retain shaders.
vshRef := vsh.shader
fshRef := fsh.shader
d3d11.IUnknownAddRef(unsafe.Pointer(vshRef), vshRef.Vtbl.AddRef)
d3d11.IUnknownAddRef(unsafe.Pointer(fshRef), fshRef.Vtbl.AddRef)
return &Pipeline{
vert: vshRef,
frag: fshRef,
layout: layout,
stride: desc.VertexLayout.Stride,
blend: blend,
topology: desc.Topology,
}, nil
}
func (b *Backend) newBlendState(desc driver.BlendDesc) (*d3d11.BlendState, error) {
var d3ddesc d3d11.BLEND_DESC
t0 := &d3ddesc.RenderTarget[0]
t0.RenderTargetWriteMask = d3d11.COLOR_WRITE_ENABLE_ALL
t0.BlendOp = d3d11.BLEND_OP_ADD
t0.BlendOpAlpha = d3d11.BLEND_OP_ADD
if desc.Enable {
t0.BlendEnable = 1
}
scol, salpha := toBlendFactor(desc.SrcFactor)
dcol, dalpha := toBlendFactor(desc.DstFactor)
t0.SrcBlend = scol
t0.SrcBlendAlpha = salpha
t0.DestBlend = dcol
t0.DestBlendAlpha = dalpha
return b.dev.CreateBlendState(&d3ddesc)
}
func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) {
vs, err := b.dev.CreateVertexShader([]byte(src.DXBC))
if err != nil {
return nil, err
}
return &VertexShader{b, vs, src}, nil
}
func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) {
fs, err := b.dev.CreatePixelShader([]byte(src.DXBC))
if err != nil {
return nil, err
}
return &FragmentShader{b, fs}, nil
}
func (b *Backend) Viewport(x, y, width, height int) {
b.viewport = d3d11.VIEWPORT{
TopLeftX: float32(x),
TopLeftY: float32(y),
Width: float32(width),
Height: float32(height),
MinDepth: 0.0,
MaxDepth: 1.0,
}
b.ctx.RSSetViewports(&b.viewport)
}
func (b *Backend) DrawArrays(off, count int) {
b.prepareDraw()
b.ctx.Draw(uint32(count), uint32(off))
}
func (b *Backend) DrawElements(off, count int) {
b.prepareDraw()
b.ctx.DrawIndexed(uint32(count), uint32(off), 0)
}
func (b *Backend) prepareDraw() {
p := b.pipeline
if p == nil {
return
}
b.ctx.VSSetShader(p.vert)
b.ctx.PSSetShader(p.frag)
b.ctx.IASetInputLayout(p.layout)
b.ctx.OMSetBlendState(p.blend, nil, 0xffffffff)
if b.vert.buffer != nil {
b.ctx.IASetVertexBuffers(b.vert.buffer.buf, uint32(p.stride), uint32(b.vert.offset))
}
var topology uint32
switch p.topology {
case driver.TopologyTriangles:
topology = d3d11.PRIMITIVE_TOPOLOGY_TRIANGLELIST
case driver.TopologyTriangleStrip:
topology = d3d11.PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
default:
panic("unsupported draw mode")
}
b.ctx.IASetPrimitiveTopology(topology)
}
func (b *Backend) BindImageTexture(unit int, tex driver.Texture) {
t := tex.(*Texture)
if t.uaView != nil {
b.ctx.CSSetUnorderedAccessViews(uint32(unit), t.uaView)
} else {
b.ctx.CSSetShaderResources(uint32(unit), t.resView)
}
}
func (b *Backend) DispatchCompute(x, y, z int) {
b.ctx.CSSetShader(b.program.shader)
b.ctx.Dispatch(uint32(x), uint32(y), uint32(z))
}
func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) {
if stride == 0 {
stride = size.X * 4
}
dst := &d3d11.BOX{
Left: uint32(offset.X),
Top: uint32(offset.Y),
Right: uint32(offset.X + size.X),
Bottom: uint32(offset.Y + size.Y),
Front: 0,
Back: 1,
}
res := (*d3d11.Resource)(unsafe.Pointer(t.tex))
t.backend.ctx.UpdateSubresource(res, dst, uint32(stride), uint32(len(pixels)), pixels)
}
func (t *Texture) Release() {
if t.foreign {
panic("texture not created by NewTexture")
}
if t.renderTarget != nil {
d3d11.IUnknownRelease(unsafe.Pointer(t.renderTarget), t.renderTarget.Vtbl.Release)
}
if t.sampler != nil {
d3d11.IUnknownRelease(unsafe.Pointer(t.sampler), t.sampler.Vtbl.Release)
}
if t.resView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(t.resView), t.resView.Vtbl.Release)
}
if t.uaView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(t.uaView), t.uaView.Vtbl.Release)
}
d3d11.IUnknownRelease(unsafe.Pointer(t.tex), t.tex.Vtbl.Release)
*t = Texture{}
}
func (b *Backend) PrepareTexture(tex driver.Texture) {}
func (b *Backend) BindTexture(unit int, tex driver.Texture) {
t := tex.(*Texture)
b.ctx.PSSetSamplers(uint32(unit), t.sampler)
b.ctx.PSSetShaderResources(uint32(unit), t.resView)
}
func (b *Backend) BindPipeline(pipe driver.Pipeline) {
b.pipeline = pipe.(*Pipeline)
}
func (b *Backend) BindProgram(prog driver.Program) {
b.program = prog.(*Program)
}
func (s *VertexShader) Release() {
d3d11.IUnknownRelease(unsafe.Pointer(s.shader), s.shader.Vtbl.Release)
*s = VertexShader{}
}
func (s *FragmentShader) Release() {
d3d11.IUnknownRelease(unsafe.Pointer(s.shader), s.shader.Vtbl.Release)
*s = FragmentShader{}
}
func (s *Program) Release() {
d3d11.IUnknownRelease(unsafe.Pointer(s.shader), s.shader.Vtbl.Release)
*s = Program{}
}
func (p *Pipeline) Release() {
d3d11.IUnknownRelease(unsafe.Pointer(p.vert), p.vert.Vtbl.Release)
d3d11.IUnknownRelease(unsafe.Pointer(p.frag), p.frag.Vtbl.Release)
d3d11.IUnknownRelease(unsafe.Pointer(p.blend), p.blend.Vtbl.Release)
if l := p.layout; l != nil {
d3d11.IUnknownRelease(unsafe.Pointer(l), l.Vtbl.Release)
}
*p = Pipeline{}
}
func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) {
buf := buffer.(*Buffer)
if buf.resView != nil {
b.ctx.CSSetShaderResources(uint32(binding), buf.resView)
} else {
b.ctx.CSSetUnorderedAccessViews(uint32(binding), buf.uaView)
}
}
func (b *Backend) BindUniforms(buffer driver.Buffer) {
buf := buffer.(*Buffer)
b.ctx.VSSetConstantBuffers(buf.buf)
b.ctx.PSSetConstantBuffers(buf.buf)
}
func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) {
b.vert.buffer = buf.(*Buffer)
b.vert.offset = offset
}
func (b *Backend) BindIndexBuffer(buf driver.Buffer) {
b.ctx.IASetIndexBuffer(buf.(*Buffer).buf, d3d11.DXGI_FORMAT_R16_UINT, 0)
}
func (b *Buffer) Download(dst []byte) error {
res := (*d3d11.Resource)(unsafe.Pointer(b.buf))
resMap, err := b.backend.ctx.Map(res, 0, d3d11.MAP_READ, 0)
if err != nil {
return fmt.Errorf("d3d11: %v", err)
}
defer b.backend.ctx.Unmap(res, 0)
data := sliceOf(resMap.PData, len(dst))
copy(dst, data)
return nil
}
func (b *Buffer) Upload(data []byte) {
var dst *d3d11.BOX
if len(data) < b.size {
dst = &d3d11.BOX{
Left: 0,
Right: uint32(len(data)),
Top: 0,
Bottom: 1,
Front: 0,
Back: 1,
}
}
b.backend.ctx.UpdateSubresource((*d3d11.Resource)(unsafe.Pointer(b.buf)), dst, 0, 0, data)
}
func (b *Buffer) Release() {
if b.resView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(b.resView), b.resView.Vtbl.Release)
}
if b.uaView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(b.uaView), b.uaView.Vtbl.Release)
}
d3d11.IUnknownRelease(unsafe.Pointer(b.buf), b.buf.Vtbl.Release)
*b = Buffer{}
}
func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
w, h := src.Dx(), src.Dy()
tex, err := t.backend.dev.CreateTexture2D(&d3d11.TEXTURE2D_DESC{
Width: uint32(w),
Height: uint32(h),
MipLevels: 1,
ArraySize: 1,
Format: t.format,
SampleDesc: d3d11.DXGI_SAMPLE_DESC{
Count: 1,
Quality: 0,
},
Usage: d3d11.USAGE_STAGING,
CPUAccessFlags: d3d11.CPU_ACCESS_READ,
})
if err != nil {
return fmt.Errorf("ReadPixels: %v", err)
}
defer d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
res := (*d3d11.Resource)(unsafe.Pointer(tex))
t.backend.ctx.CopySubresourceRegion(
res,
0, // Destination subresource.
0, 0, 0, // Destination coordinates (x, y, z).
(*d3d11.Resource)(t.tex),
0, // Source subresource.
&d3d11.BOX{
Left: uint32(src.Min.X),
Top: uint32(src.Min.Y),
Right: uint32(src.Max.X),
Bottom: uint32(src.Max.Y),
Front: 0,
Back: 1,
},
)
resMap, err := t.backend.ctx.Map(res, 0, d3d11.MAP_READ, 0)
if err != nil {
return fmt.Errorf("ReadPixels: %v", err)
}
defer t.backend.ctx.Unmap(res, 0)
srcPitch := stride
dstPitch := int(resMap.RowPitch)
mapSize := dstPitch * h
data := sliceOf(resMap.PData, mapSize)
width := w * 4
for r := 0; r < h; r++ {
pixels := pixels[r*srcPitch:]
copy(pixels[:width], data[r*dstPitch:])
}
return nil
}
func (b *Backend) BeginCompute() {
}
func (b *Backend) EndCompute() {
}
func (b *Backend) BeginRenderPass(tex driver.Texture, d driver.LoadDesc) {
t := tex.(*Texture)
b.ctx.OMSetRenderTargets(t.renderTarget, nil)
if d.Action == driver.LoadActionClear {
c := d.ClearColor
b.clearColor = [4]float32{c.R, c.G, c.B, c.A}
b.ctx.ClearRenderTargetView(t.renderTarget, &b.clearColor)
}
}
func (b *Backend) EndRenderPass() {
}
func (f *Texture) ImplementsRenderTarget() {}
func convBufferBinding(typ driver.BufferBinding) uint32 {
var bindings uint32
if typ&driver.BufferBindingVertices != 0 {
bindings |= d3d11.BIND_VERTEX_BUFFER
}
if typ&driver.BufferBindingIndices != 0 {
bindings |= d3d11.BIND_INDEX_BUFFER
}
if typ&driver.BufferBindingUniforms != 0 {
bindings |= d3d11.BIND_CONSTANT_BUFFER
}
if typ&driver.BufferBindingTexture != 0 {
bindings |= d3d11.BIND_SHADER_RESOURCE
}
if typ&driver.BufferBindingFramebuffer != 0 {
bindings |= d3d11.BIND_RENDER_TARGET
}
if typ&driver.BufferBindingShaderStorageWrite != 0 {
bindings |= d3d11.BIND_UNORDERED_ACCESS
} else if typ&driver.BufferBindingShaderStorageRead != 0 {
bindings |= d3d11.BIND_SHADER_RESOURCE
}
return bindings
}
func toBlendFactor(f driver.BlendFactor) (uint32, uint32) {
switch f {
case driver.BlendFactorOne:
return d3d11.BLEND_ONE, d3d11.BLEND_ONE
case driver.BlendFactorOneMinusSrcAlpha:
return d3d11.BLEND_INV_SRC_ALPHA, d3d11.BLEND_INV_SRC_ALPHA
case driver.BlendFactorZero:
return d3d11.BLEND_ZERO, d3d11.BLEND_ZERO
case driver.BlendFactorDstColor:
return d3d11.BLEND_DEST_COLOR, d3d11.BLEND_DEST_ALPHA
default:
panic("unsupported blend source factor")
}
}
// sliceOf returns a slice from a (native) pointer.
func sliceOf(ptr uintptr, cap int) []byte {
var data []byte
h := (*reflect.SliceHeader)(unsafe.Pointer(&data))
h.Data = ptr
h.Cap = cap
h.Len = cap
return data
}
================================================
FILE: vendor/gioui.org/gpu/internal/driver/api.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package driver
import (
"fmt"
"unsafe"
"gioui.org/internal/gl"
)
// See gpu/api.go for documentation for the API types.
type API interface {
implementsAPI()
}
type RenderTarget interface {
ImplementsRenderTarget()
}
type OpenGLRenderTarget gl.Framebuffer
type Direct3D11RenderTarget struct {
// RenderTarget is a *ID3D11RenderTargetView.
RenderTarget unsafe.Pointer
}
type MetalRenderTarget struct {
// Texture is a MTLTexture.
Texture uintptr
}
type VulkanRenderTarget struct {
// WaitSem is a VkSemaphore that must signaled before accessing Framebuffer.
WaitSem uint64
// SignalSem is a VkSemaphore that signal access to Framebuffer is complete.
SignalSem uint64
// Image is the VkImage to render into.
Image uint64
// Framebuffer is a VkFramebuffer for Image.
Framebuffer uint64
}
type OpenGL struct {
// ES forces the use of ANGLE OpenGL ES libraries on macOS. It is
// ignored on all other platforms.
ES bool
// Context contains the WebGL context for WebAssembly platforms. It is
// empty for all other platforms; an OpenGL context is assumed current when
// calling NewDevice.
Context gl.Context
// Shared instructs users of the context to restore the GL state after
// use.
Shared bool
}
type Direct3D11 struct {
// Device contains a *ID3D11Device.
Device unsafe.Pointer
}
type Metal struct {
// Device is an MTLDevice.
Device uintptr
// Queue is a MTLCommandQueue.
Queue uintptr
// PixelFormat is the MTLPixelFormat of the default framebuffer.
PixelFormat int
}
type Vulkan struct {
// PhysDevice is a VkPhysicalDevice.
PhysDevice unsafe.Pointer
// Device is a VkDevice.
Device unsafe.Pointer
// QueueFamily is the queue familily index of the queue.
QueueFamily int
// QueueIndex is the logical queue index of the queue.
QueueIndex int
// Format is a VkFormat that matches render targets.
Format int
}
// API specific device constructors.
var (
NewOpenGLDevice func(api OpenGL) (Device, error)
NewDirect3D11Device func(api Direct3D11) (Device, error)
NewMetalDevice func(api Metal) (Device, error)
NewVulkanDevice func(api Vulkan) (Device, error)
)
// NewDevice creates a new Device given the api.
//
// Note that the device does not assume ownership of the resources contained in
// api; the caller must ensure the resources are valid until the device is
// released.
func NewDevice(api API) (Device, error) {
switch api := api.(type) {
case OpenGL:
if NewOpenGLDevice != nil {
return NewOpenGLDevice(api)
}
case Direct3D11:
if NewDirect3D11Device != nil {
return NewDirect3D11Device(api)
}
case Metal:
if NewMetalDevice != nil {
return NewMetalDevice(api)
}
case Vulkan:
if NewVulkanDevice != nil {
return NewVulkanDevice(api)
}
}
return nil, fmt.Errorf("driver: no driver available for the API %T", api)
}
func (OpenGL) implementsAPI() {}
func (Direct3D11) implementsAPI() {}
func (Metal) implementsAPI() {}
func (Vulkan) implementsAPI() {}
func (OpenGLRenderTarget) ImplementsRenderTarget() {}
func (Direct3D11RenderTarget) ImplementsRenderTarget() {}
func (MetalRenderTarget) ImplementsRenderTarget() {}
func (VulkanRenderTarget) ImplementsRenderTarget() {}
================================================
FILE: vendor/gioui.org/gpu/internal/driver/driver.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package driver
import (
"errors"
"image"
"time"
"gioui.org/internal/f32color"
"gioui.org/shader"
)
// Device represents the abstraction of underlying GPU
// APIs such as OpenGL, Direct3D useful for rendering Gio
// operations.
type Device interface {
BeginFrame(target RenderTarget, clear bool, viewport image.Point) Texture
EndFrame()
Caps() Caps
NewTimer() Timer
// IsContinuousTime reports whether all timer measurements
// are valid at the point of call.
IsTimeContinuous() bool
NewTexture(format TextureFormat, width, height int, minFilter, magFilter TextureFilter, bindings BufferBinding) (Texture, error)
NewImmutableBuffer(typ BufferBinding, data []byte) (Buffer, error)
NewBuffer(typ BufferBinding, size int) (Buffer, error)
NewComputeProgram(shader shader.Sources) (Program, error)
NewVertexShader(src shader.Sources) (VertexShader, error)
NewFragmentShader(src shader.Sources) (FragmentShader, error)
NewPipeline(desc PipelineDesc) (Pipeline, error)
Viewport(x, y, width, height int)
DrawArrays(off, count int)
DrawElements(off, count int)
BeginRenderPass(t Texture, desc LoadDesc)
EndRenderPass()
PrepareTexture(t Texture)
BindProgram(p Program)
BindPipeline(p Pipeline)
BindTexture(unit int, t Texture)
BindVertexBuffer(b Buffer, offset int)
BindIndexBuffer(b Buffer)
BindImageTexture(unit int, texture Texture)
BindUniforms(buf Buffer)
BindStorageBuffer(binding int, buf Buffer)
BeginCompute()
EndCompute()
CopyTexture(dst Texture, dstOrigin image.Point, src Texture, srcRect image.Rectangle)
DispatchCompute(x, y, z int)
Release()
}
var ErrDeviceLost = errors.New("GPU device lost")
type LoadDesc struct {
Action LoadAction
ClearColor f32color.RGBA
}
type Pipeline interface {
Release()
}
type PipelineDesc struct {
VertexShader VertexShader
FragmentShader FragmentShader
VertexLayout VertexLayout
BlendDesc BlendDesc
PixelFormat TextureFormat
Topology Topology
}
type VertexLayout struct {
Inputs []InputDesc
Stride int
}
// InputDesc describes a vertex attribute as laid out in a Buffer.
type InputDesc struct {
Type shader.DataType
Size int
Offset int
}
type BlendDesc struct {
Enable bool
SrcFactor, DstFactor BlendFactor
}
type BlendFactor uint8
type Topology uint8
type TextureFilter uint8
type TextureFormat uint8
type BufferBinding uint8
type LoadAction uint8
type Features uint
type Caps struct {
// BottomLeftOrigin is true if the driver has the origin in the lower left
// corner. The OpenGL driver returns true.
BottomLeftOrigin bool
Features Features
MaxTextureSize int
}
type VertexShader interface {
Release()
}
type FragmentShader interface {
Release()
}
type Program interface {
Release()
}
type Buffer interface {
Release()
Upload(data []byte)
Download(data []byte) error
}
type Timer interface {
Begin()
End()
Duration() (time.Duration, bool)
Release()
}
type Texture interface {
RenderTarget
Upload(offset, size image.Point, pixels []byte, stride int)
ReadPixels(src image.Rectangle, pixels []byte, stride int) error
Release()
}
const (
BufferBindingIndices BufferBinding = 1 << iota
BufferBindingVertices
BufferBindingUniforms
BufferBindingTexture
BufferBindingFramebuffer
BufferBindingShaderStorageRead
BufferBindingShaderStorageWrite
)
const (
TextureFormatSRGBA TextureFormat = iota
TextureFormatFloat
TextureFormatRGBA8
// TextureFormatOutput denotes the format used by the output framebuffer.
TextureFormatOutput
)
const (
FilterNearest TextureFilter = iota
FilterLinear
)
const (
FeatureTimers Features = 1 << iota
FeatureFloatRenderTargets
FeatureCompute
FeatureSRGB
)
const (
TopologyTriangleStrip Topology = iota
TopologyTriangles
)
const (
BlendFactorOne BlendFactor = iota
BlendFactorOneMinusSrcAlpha
BlendFactorZero
BlendFactorDstColor
)
const (
LoadActionKeep LoadAction = iota
LoadActionClear
LoadActionInvalidate
)
var ErrContentLost = errors.New("buffer content lost")
func (f Features) Has(feats Features) bool {
return f&feats == feats
}
func DownloadImage(d Device, t Texture, img *image.RGBA) error {
r := img.Bounds()
if err := t.ReadPixels(r, img.Pix, img.Stride); err != nil {
return err
}
if d.Caps().BottomLeftOrigin {
// OpenGL origin is in the lower-left corner. Flip the image to
// match.
flipImageY(r.Dx()*4, r.Dy(), img.Pix)
}
return nil
}
func flipImageY(stride, height int, pixels []byte) {
// Flip image in y-direction. OpenGL's origin is in the lower
// left corner.
row := make([]uint8, stride)
for y := 0; y < height/2; y++ {
y1 := height - y - 1
dest := y1 * stride
src := y * stride
copy(row, pixels[dest:])
copy(pixels[dest:], pixels[src:src+len(row)])
copy(pixels[src:], row)
}
}
func UploadImage(t Texture, offset image.Point, img *image.RGBA) {
var pixels []byte
size := img.Bounds().Size()
min := img.Rect.Min
start := img.PixOffset(min.X, min.Y)
end := img.PixOffset(min.X+size.X, min.Y+size.Y-1)
pixels = img.Pix[start:end]
t.Upload(offset, size, pixels, img.Stride)
}
================================================
FILE: vendor/gioui.org/gpu/internal/metal/metal.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
// This file exists so this package builds on non-Darwin platforms.
package metal
================================================
FILE: vendor/gioui.org/gpu/internal/metal/metal_darwin.go
================================================
// SPDX-License-Identifier: Unlicense OR MIT
package metal
import (
"errors"
"fmt"
"image"
"unsafe"
"gioui.org/gpu/internal/driver"
"gioui.org/shader"
)
/*
#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
#cgo LDFLAGS: -framework CoreGraphics
@import Metal;
#include
#include
typedef struct {
void *addr;
NSUInteger size;
} slice;
static CFTypeRef queueNewBuffer(CFTypeRef queueRef) {
@autoreleasepool {
id queue = (__bridge id)queueRef;
return CFBridgingRetain([queue commandBuffer]);
}
}
static void cmdBufferCommit(CFTypeRef cmdBufRef) {
@autoreleasepool {
id cmdBuf = (__bridge id)cmdBufRef;
[cmdBuf commit];
}
}
static void cmdBufferWaitUntilCompleted(CFTypeRef cmdBufRef) {
@autoreleasepool {
id cmdBuf = (__bridge id)cmdBufRef;
[cmdBuf waitUntilCompleted];
}
}
static CFTypeRef cmdBufferRenderEncoder(CFTypeRef cmdBufRef, CFTypeRef textureRef, MTLLoadAction act, float r, float g, float b, float a) {
@autoreleasepool {
id cmdBuf = (__bridge id)cmdBufRef;
MTLRenderPassDescriptor *desc = [MTLRenderPassDescriptor new];
desc.colorAttachments[0].texture = (__bridge id)textureRef;
desc.colorAttachments[0].loadAction = act;
desc.colorAttachments[0].clearColor = MTLClearColorMake(r, g, b, a);
return CFBridgingRetain([cmdBuf renderCommandEncoderWithDescriptor:desc]);
}
}
static CFTypeRef cmdBufferComputeEncoder(CFTypeRef cmdBufRef) {
@autoreleasepool {
id cmdBuf = (__bridge id)cmdBufRef;
return CFBridgingRetain([cmdBuf computeCommandEncoder]);
}
}
static CFTypeRef cmdBufferBlitEncoder(CFTypeRef cmdBufRef) {
@autoreleasepool {
id cmdBuf = (__bridge id)cmdBufRef;
return CFBridgingRetain([cmdBuf blitCommandEncoder]);
}
}
static void renderEncEnd(CFTypeRef renderEncRef) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
[enc endEncoding];
}
}
static void renderEncViewport(CFTypeRef renderEncRef, MTLViewport viewport) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
[enc setViewport:viewport];
}
}
static void renderEncSetFragmentTexture(CFTypeRef renderEncRef, NSUInteger index, CFTypeRef texRef) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
id tex = (__bridge id)texRef;
[enc setFragmentTexture:tex atIndex:index];
}
}
static void renderEncSetFragmentSamplerState(CFTypeRef renderEncRef, NSUInteger index, CFTypeRef samplerRef) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
id sampler = (__bridge id)samplerRef;
[enc setFragmentSamplerState:sampler atIndex:index];
}
}
static void renderEncSetVertexBuffer(CFTypeRef renderEncRef, CFTypeRef bufRef, NSUInteger idx, NSUInteger offset) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
id buf = (__bridge id)bufRef;
[enc setVertexBuffer:buf offset:offset atIndex:idx];
}
}
static void renderEncSetFragmentBuffer(CFTypeRef renderEncRef, CFTypeRef bufRef, NSUInteger idx, NSUInteger offset) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
id buf = (__bridge id)bufRef;
[enc setFragmentBuffer:buf offset:offset atIndex:idx];
}
}
static void renderEncSetFragmentBytes(CFTypeRef renderEncRef, const void *bytes, NSUInteger length, NSUInteger idx) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
[enc setFragmentBytes:bytes length:length atIndex:idx];
}
}
static void renderEncSetVertexBytes(CFTypeRef renderEncRef, const void *bytes, NSUInteger length, NSUInteger idx) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
[enc setVertexBytes:bytes length:length atIndex:idx];
}
}
static void renderEncSetRenderPipelineState(CFTypeRef renderEncRef, CFTypeRef pipeRef) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
id pipe = (__bridge id)pipeRef;
[enc setRenderPipelineState:pipe];
}
}
static void renderEncDrawPrimitives(CFTypeRef renderEncRef, MTLPrimitiveType type, NSUInteger start, NSUInteger count) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
[enc drawPrimitives:type vertexStart:start vertexCount:count];
}
}
static void renderEncDrawIndexedPrimitives(CFTypeRef renderEncRef, MTLPrimitiveType type, CFTypeRef bufRef, NSUInteger offset, NSUInteger count) {
@autoreleasepool {
id enc = (__bridge id)renderEncRef;
id buf = (__bridge id)bufRef;
[enc drawIndexedPrimitives:type indexCount:count indexType:MTLIndexTypeUInt16 indexBuffer:buf indexBufferOffset:offset];
}
}
static void computeEncSetPipeline(CFTypeRef computeEncRef, CFTypeRef pipeRef) {
@autoreleasepool {
id enc = (__bridge id)computeEncRef;
id pipe = (__bridge id)pipeRef;
[enc setComputePipelineState:pipe];
}
}
static void computeEncSetTexture(CFTypeRef computeEncRef, NSUInteger index, CFTypeRef texRef) {
@autoreleasepool {
id enc = (__bridge id)computeEncRef;
id tex = (__bridge id)texRef;
[enc setTexture:tex atIndex:index];
}
}
static void computeEncEnd(CFTypeRef computeEncRef) {
@autoreleasepool {
id enc = (__bridge id)computeEncRef;
[enc endEncoding];
}
}
static void computeEncSetBuffer(CFTypeRef computeEncRef, NSUInteger index, CFTypeRef bufRef) {
@autoreleasepool {
id enc = (__bridge id)computeEncRef;
id buf = (__bridge id)bufRef;
[enc setBuffer:buf offset:0 atIndex:index];
}
}
static void computeEncDispatch(CFTypeRef computeEncRef, MTLSize threadgroupsPerGrid, MTLSize threadsPerThreadgroup) {
@autoreleasepool {
id enc = (__bridge id)computeEncRef;
[enc dispatchThreadgroups:threadgroupsPerGrid threadsPerThreadgroup:threadsPerThreadgroup];
}
}
static void computeEncSetBytes(CFTypeRef computeEncRef, const void *bytes, NSUInteger length, NSUInteger index) {
@autoreleasepool {
id enc = (__bridge id)computeEncRef;
[enc setBytes:bytes length:length atIndex:index];
}
}
static void blitEncEnd(CFTypeRef blitEncRef) {
@autoreleasepool {
id enc = (__bridge id)blitEncRef;
[enc endEncoding];
}
}
static void blitEncCopyFromTexture(CFTypeRef blitEncRef, CFTypeRef srcRef, MTLOrigin srcOrig, MTLSize srcSize, CFTypeRef dstRef, MTLOrigin dstOrig) {
@autoreleasepool {
id enc = (__bridge id)blitEncRef;
id src = (__bridge id)srcRef;
id dst = (__bridge id)dstRef;
[enc copyFromTexture:src
sourceSlice:0
sourceLevel:0
sourceOrigin:srcOrig
sourceSize:srcSize
toTexture:dst
destinationSlice:0
destinationLevel:0
destinationOrigin:dstOrig];
}
}
static void blitEncCopyBufferToTexture(CFTypeRef blitEncRef, CFTypeRef bufRef, CFTypeRef texRef, NSUInteger offset, NSUInteger stride, NSUInteger length, MTLSize dims, MTLOrigin orig) {
@autoreleasepool {
id enc = (__bridge id)blitEncRef;
id src = (__bridge id)bufRef;
id dst = (__bridge id)texRef;
[enc copyFromBuffer:src
sourceOffset:offset
sourceBytesPerRow:stride
sourceBytesPerImage:length
sourceSize:dims
toTexture:dst
destinationSlice:0
destinationLevel:0
destinationOrigin:orig];
}
}
static void blitEncCopyTextureToBuffer(CFTypeRef blitEncRef, CFTypeRef texRef, CFTypeRef bufRef, NSUInteger offset, NSUInteger stride, NSUInteger length, MTLSize dims, MTLOrigin orig) {
@autoreleasepool {
id enc = (__bridge id)blitEncRef;
id src = (__bridge id)texRef;
id dst = (__bridge id)bufRef;
[enc copyFromTexture:src
sourceSlice:0
sourceLevel:0
sourceOrigin:orig
sourceSize:dims
toBuffer:dst
destinationOffset:offset
destinationBytesPerRow:stride
destinationBytesPerImage:length];
}
}
static void blitEncCopyBufferToBuffer(CFTypeRef blitEncRef, CFTypeRef srcRef, CFTypeRef dstRef, NSUInteger srcOff, NSUInteger dstOff, NSUInteger size) {
@autoreleasepool {
id enc = (__bridge id)blitEncRef;
id src = (__bridge id)srcRef;
id dst = (__bridge id)dstRef;
[enc copyFromBuffer:src
sourceOffset:srcOff
toBuffer:dst
destinationOffset:dstOff
size:size];
}
}
static CFTypeRef newTexture(CFTypeRef devRef, NSUInteger width, NSUInteger height, MTLPixelFormat format, MTLTextureUsage usage) {
@autoreleasepool {
id dev = (__bridge id)devRef;
MTLTextureDescriptor *mtlDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: format
width: width
height: height
mipmapped: NO];
mtlDesc.usage = usage;
mtlDesc.storageMode = MTLStorageModePrivate;
return CFBridgingRetain([dev newTextureWithDescriptor:mtlDesc]);
}
}
static CFTypeRef newSampler(CFTypeRef devRef, MTLSamplerMinMagFilter minFilter, MTLSamplerMinMagFilter magFilter) {
@autoreleasepool {
id dev = (__bridge id)devRef;
MTLSamplerDescriptor *desc = [MTLSamplerDescriptor new];
desc.minFilter = minFilter;
desc.magFilter = magFilter;
return CFBridgingRetain([dev newSamplerStateWithDescriptor:desc]);
}
}
static CFTypeRef newBuffer(CFTypeRef devRef, NSUInteger size, MTLResourceOptions opts) {
@autoreleasepool {
id dev = (__bridge id)devRef;
id buf = [dev newBufferWithLength:size
options:opts];
return CFBridgingRetain(buf);
}
}
static slice bufferContents(CFTypeRef bufRef) {
@autoreleasepool {
id buf = (__bridge id)bufRef;
slice s = {.addr = [buf contents], .size = [buf length]};
return s;
}
}
static CFTypeRef newLibrary(CFTypeRef devRef, char *name, void *mtllib, size_t size) {
@autoreleasepool {
id dev = (__bridge id)devRef;
dispatch_data_t data = dispatch_data_create(mtllib, size, DISPATCH_TARGET_QUEUE_DEFAULT, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
id lib = [dev newLibraryWithData:data error:nil];
lib.label = [NSString stringWithUTF8String:name];
return CFBridgingRetain(lib);
}
}
static CFTypeRef libraryNewFunction(CFTypeRef libRef, char *funcName) {
@autoreleasepool {
id lib = (__bridge id)libRef;
NSString *name = [NSString stringWithUTF8String:funcName];
return CFBridgingRetain([lib newFunctionWithName:name]);
}
}
static CFTypeRef newComputePipeline(CFTypeRef devRef, CFTypeRef funcRef) {
@autoreleasepool {
id dev = (__bridge id)devRef;
id func = (__bridge id