# engine

`import "github.com/matjam/bunyip/engine"`

Package engine runs a game's main loop. To start a game, implement Game or provide GameFuncs callbacks, fill in a Config and call Run. Run owns the window, the event loop, the renderer, the audio device and the frame pacing, and passes a Context to each call with the input state, the Graphics to draw with, the audio Mixer, the clock and the window controls.

### The loop {#hdr-The_loop}

There are two loop modes. Real-time games use a fixed timestep: Update runs at Config.FixedStep regardless of frame rate, Draw runs once per frame, and Context.Alpha reports how far the next update is so drawing can interpolate. Turn-based games set TurnBased. The loop then draws the first frame without an Update, sleeps in the operating system until input arrives, or until Context.Wake is called from another goroutine, and runs one Update and one Draw per batch of events while not paused. A paused game draws but never updates. A wake that brings no event runs nothing. While a controller is connected the sleep lasts at most 10 ms, because controller input does not wake the operating system's wait, and a change in any controller's state runs a turn. The main loop blocks between batches; audio and any game-owned goroutines can keep running.

A window that cannot be seen (Context.Visible is false) does not draw: Draw is not called and nothing is presented until the window is seen again, which draws at once. A hidden real-time window still updates at its fixed step unless Config.PauseHidden stops it, and the loop sleeps between those updates rather than spinning. A headless run paces its frames to FixedStep against the wall clock.

### The view {#hdr-The_view}

Config.ViewWidth and ViewHeight fix the game's coordinate space, and the window scales it by Config.Scaling (fit with letterboxing, whole multiples for pixel art, or stretch). Without them the view follows the window's size in points. Config.Headless runs the same loop without a window, for tests and screenshot runs. Context.Screenshot saves any frame.

### Errors and exit {#hdr-Errors_and_exit}

Returning an error from Init, Update or Draw stops the loop, and Run returns it. Context.Quit stops it cleanly. With Config.HandleClose the window's close button sets Context.CloseRequested instead of quitting, so a game can save or prompt first. Games that implement Recoverer can rebuild resources after a lost graphics device; Recover runs with the new context instead of Init. Other games receive the device-loss error.

Graphics releases its GPU resources when the context closes, including after setup fails. Context.Cleanup registers closures for other teardown in reverse acquisition order. Context.NewUI supplies a default font and connects clipboard and text-input placement to the window. Config.Console enables an engine-drawn console without an extra drawing call.

## Variables

<a id="ErrUnsupported"></a>

```go
var ErrUnsupported = platform.ErrUnsupported
```

ErrUnsupported identifies an operation unavailable in the active backend, including native window operations during a headless run.

## Functions

<a id="OpenURL"></a>

### OpenURL

```go
func OpenURL(url string) error
```

OpenURL opens a web address in the player's browser: a link to the game's site, a bug tracker, a store page. Only http and https addresses are opened, so a link from untrusted data cannot run a program. It returns without waiting for the browser.

<a id="Run"></a>

### Run

```go
func Run(cfg Config, game Game) (err error)
```

Run opens the window, drives game until it quits or the window closes, and tears everything down. It must be called from the main goroutine.

Example:

```go
package main

import (
	"github.com/matjam/bunyip/engine"
	"github.com/matjam/bunyip/gfx"
	"github.com/matjam/bunyip/input"
)

// A game is a value with Update and Draw; Init and Shutdown are optional.
type game struct {
	x float32
}

func (g *game) Update(ctx *engine.Context) error {
	if ctx.Input.KeyPressed(input.KeyEscape) {
		ctx.Quit()
	}
	g.x += 100 * float32(ctx.Delta)
	return nil
}

func (g *game) Draw(ctx *engine.Context) error {
	ctx.Gfx.FillRect(g.x, 100, 40, 40, gfx.RGB(255, 200, 40))
	return nil
}

func main() {
	err := engine.Run(engine.Config{Title: "Hello", Width: 960, Height: 600, Resizable: true}, &game{})
	if err != nil {
		panic(err)
	}
}
```

Example (turnBased):

A turn-based game sleeps in the operating system until input arrives.

```go
package main

import (
	"github.com/matjam/bunyip/engine"
	"github.com/matjam/bunyip/gfx"
	"github.com/matjam/bunyip/input"
)

// A game is a value with Update and Draw; Init and Shutdown are optional.
type game struct {
	x float32
}

func (g *game) Update(ctx *engine.Context) error {
	if ctx.Input.KeyPressed(input.KeyEscape) {
		ctx.Quit()
	}
	g.x += 100 * float32(ctx.Delta)
	return nil
}

func (g *game) Draw(ctx *engine.Context) error {
	ctx.Gfx.FillRect(g.x, 100, 40, 40, gfx.RGB(255, 200, 40))
	return nil
}

func main() {
	err := engine.Run(engine.Config{Title: "Roguelike", Width: 960, Height: 600, TurnBased: true}, &game{})
	if err != nil {
		panic(err)
	}
}
```

## Types

<a id="Config"></a>

<a id="Config.Parent"></a>

<a id="Config.Title"></a>

<a id="Config.Width"></a>

<a id="Config.Height"></a>

<a id="Config.Resizable"></a>

<a id="Config.NoVSync"></a>

<a id="Config.TurnBased"></a>

<a id="Config.FixedStep"></a>

<a id="Config.MaxCatchUp"></a>

<a id="Config.MaxSteps"></a>

<a id="Config.PauseUnfocused"></a>

<a id="Config.PauseHidden"></a>

<a id="Config.DrawBudget"></a>

<a id="Config.LogFile"></a>

<a id="Config.ViewWidth"></a>

<a id="Config.ViewHeight"></a>

<a id="Config.Scaling"></a>

<a id="Config.Headless"></a>

<a id="Config.FixedClock"></a>

<a id="Config.Icon"></a>

<a id="Config.HandleClose"></a>

<a id="Config.Validation"></a>

<a id="Config.NoAudio"></a>

<a id="Config.Log"></a>

<a id="Config.Debug"></a>

<a id="Config.Pprof"></a>

<a id="Config.Console"></a>

<a id="Config.ConsoleKey"></a>

### Config

```go
type Config struct {
	// Parent embeds the output in borrowed native content. Nil opens a top-level
	// window. Embedded content initially fits its parent; SetBounds switches to
	// manual placement. Width/Height, Title, Icon and Resizable do not configure
	// the borrowed host. Wayland and headless embedding are unsupported.
	Parent    *NativeParent
	Title     string // window title; empty means "Bunyip"
	Width     int    // window content width in points; nonpositive means 1280
	Height    int    // window content height in points; nonpositive means 720
	Resizable bool   // allow the player to resize the window; off by default
	NoVSync   bool   // present without waiting for the display; vsync is on by default

	TurnBased bool          // wait for input instead of running a clock
	FixedStep time.Duration // real-time update interval; default 1/60 s
	// MaxCatchUp caps how much lost time the loop makes up with extra
	// updates after a stall (a window drag, a long load), so a game does
	// not spiral into ever more updates per frame; the rest is dropped.
	// Zero means a quarter of a second, and a value below FixedStep is
	// raised to one step, since less would never run an update. MaxSteps
	// caps the updates in one frame the same way; zero means no cap
	// beyond MaxCatchUp.
	MaxCatchUp time.Duration
	MaxSteps   int
	// PauseUnfocused stops this window's updates while the
	// window does not have focus, so a game does not play on behind
	// another window; frames still draw. Off by default: a server, a
	// music player or a game with real-time multiplayer keeps running.
	// The shared mixer pauses only when all active windows are paused.
	PauseUnfocused bool
	// PauseHidden stops updates while the window
	// cannot be seen, in the same way PauseUnfocused does for focus. A
	// window is hidden while it is minimised, and while it is wholly
	// covered by other windows on the platforms that report that. Off by
	// default, and a headless run is always visible. With both settings
	// on this window is paused while either is true. The loop writes the
	// mixer only when the all-windows pause state changes, so a mixer the game paused itself
	// stays paused. A hidden window never draws, with or without this
	// setting; with it, a hidden game costs nothing until it is seen,
	// except that a close request is drawn so a paused game's Draw can
	// see it.
	PauseHidden bool

	// DrawBudget is the number of draw calls (2D and 3D together) a
	// frame should stay under; the debug overlay warns when a frame goes
	// over, so a batching regression is noticed. Zero means no warning.
	DrawBudget int
	// LogFile appends the engine's log (and a panic's stack trace) to a
	// file when Log is nil, for crash reports from players' machines;
	// empty logs to the terminal.
	LogFile string

	// ViewWidth and ViewHeight fix the game's view in view units: the 2D
	// coordinate space and the 3D viewport. The window scales that view by
	// Scaling and centres it, so a pixel-art game designs for 320 by 180
	// once. Zero means the view is the window's size in points and follows
	// it as the window resizes.
	ViewWidth, ViewHeight int
	Scaling               Scaling

	// Headless runs without a window or input: frames render offscreen at
	// Width by Height and Context.Screenshot still works, for tests and
	// screenshot runs on a build machine.
	Headless bool

	// FixedClock advances the real-time game clock by one FixedStep each
	// frame instead of reading the wall clock, and runs one Update per
	// frame unless paused. It has no effect in turn-based mode.
	// Frame N is then always at N steps, whatever the
	// machine took to render it, so a run produces the same frames every
	// time and a screenshot can be compared against a stored image. It is
	// for tests and for recording, not for playing: a game run this way
	// speeds up or slows down with the frame rate. Context.Alpha is
	// always zero. The environment variable BUNYIP_FIXED_CLOCK sets it,
	// as BUNYIP_HEADLESS sets Headless. Off by default.
	FixedClock bool

	// Icon is the window's or application's icon; nil keeps the default.
	// HandleClose leaves the window open when the user asks to close it
	// and sets Context.CloseRequested instead, so the game can save or
	// ask first and then Quit.
	Icon        image.Image
	HandleClose bool

	// Validation turns on the Vulkan validation layer, when it is
	// installed, and logs its messages. To find a Vulkan usage error
	// during development, set it or set the environment variable
	// BUNYIP_VALIDATION (for example BUNYIP_VALIDATION=1), which turns it
	// on without a code change. The layer checks every Vulkan call, so it
	// adds about a millisecond of CPU time to a frame; leave it off when
	// measuring performance. Off by default.
	Validation bool
	NoAudio    bool // disable audio output and microphone capture
	Log        *slog.Logger

	// Debug shows the frame-timing overlay at start; F3 toggles it either
	// way. The overlay's figures change four times a second so they can be
	// read; Context.Stats holds every frame's.
	Debug bool

	// Pprof starts a Go profiling HTTP server at this address, for example
	// "127.0.0.1:6060". Empty disables starting the server. Profiles are at
	// /debug/pprof/; CPU profiling and execution tracing start on request.
	// Use a loopback address for local debugging: the server has no authentication.
	// Listen failures are logged and do not stop the game. The server uses
	// http.DefaultServeMux and remains running until the process exits.
	Pprof string

	// Console builds the debug console, puts it on Context.Console and
	// tees the log through it. The engine draws it after the game and
	// debug overlay, above the game's own interface. The console starts
	// closed and costs nothing until it is opened. ConsoleKey is the key
	// that opens it; zero means the backquote key.
	Console    bool
	ConsoleKey input.Key
	// contains filtered or unexported fields
}
```

Config describes the window and the loop.

<a id="Context"></a>

<a id="Context.Gfx"></a>

<a id="Context.Input"></a>

<a id="Context.Log"></a>

<a id="Context.Audio"></a>

<a id="Context.Console"></a>

<a id="Context.Clear"></a>

<a id="Context.Width"></a>

<a id="Context.Height"></a>

<a id="Context.Scale"></a>

<a id="Context.Delta"></a>

<a id="Context.Time"></a>

<a id="Context.Frame"></a>

<a id="Context.Alpha"></a>

<a id="Context.Stats"></a>

### Context

```go
type Context struct {
	Gfx   *gfx.Graphics
	Input *input.State
	Log   *slog.Logger

	// Audio always exists. Without an output device playback is silent
	// and voices do not advance automatically.
	Audio *audio.Mixer

	// Console is the debug console, set when Config.Console is on and nil
	// otherwise. The engine draws it last; ask Open whether it has the
	// keyboard:
	//
	//	func (g *game) Update(ctx *engine.Context) error {
	//		if ctx.Console.Open() {
	//			return nil // the console is taking the keys
	//		}
	//		...
	//	}
	//
	// Register commands and variables and attach worlds from Init. A lost
	// GPU device rebuilds the console with the rest of the engine and
	// calls Recover instead of Init; register the commands there too.
	Console *console.Console

	// Clear is the frame's background colour; set it whenever you like.
	Clear gfx.Color

	// Width and Height are the view's size and Scale is pixels per point.
	// With Config.ViewWidth and ViewHeight set, Width and Height are that
	// fixed view in view units and stay put as the window resizes, and
	// Scale is the pixels a view unit covers; without them the view is the
	// window's content size in points and follows it.
	Width, Height float32
	Scale         float32

	// Delta is the seconds this Update covers, scaled by TimeScale: the
	// fixed step in real-time mode, active wall time in turn-based mode.
	// Configured pauses contribute no elapsed time; a turn-based resume
	// update has zero Delta. Time measures elapsed wall seconds in the
	// current loop and continues during pauses, except with FixedClock.
	// Frame counts drawn frames. Time and Frame restart after device recovery.
	Delta float64
	Time  float64
	Frame uint64
	// Alpha, during Draw, is how far the clock has run past the last
	// Update as a fraction of a fixed step, 0 to 1. Drawing a body at
	// previous + (current - previous) * Alpha moves it smoothly when the
	// display runs faster than the update rate. It is 1 in turn-based mode.
	Alpha float32

	// Stats holds the previous frame's timings.
	Stats Stats
	// contains filtered or unexported fields
}
```

Context is everything a game touches during a callback. Use it from the main goroutine in Init, Recover, Update, Draw or Shutdown. Wake is safe from another goroutine; other operations are not synchronized.

<a id="Context.Cleanup"></a>

#### Context.Cleanup

```go
func (c *Context) Cleanup(fn func())
```

Cleanup registers work to run when this context closes, after the game's optional Shutdown and before graphics, audio and the window close. Call it on the game goroutine, usually just after acquiring a resource. Callbacks run in reverse registration order, including when Init or Recover fails and before a device-loss rebuild. GPU resources already belong to Graphics and need no cleanup registration.

A nil callback is ignored. A callback may register more cleanup work. If a callback panics, remaining callbacks still run and the panic continues, as with deferred functions.

<a id="Context.Clipboard"></a>

#### Context.Clipboard

```go
func (c *Context) Clipboard() (string, error)
```

Clipboard returns the system clipboard's text, empty when it holds none. Under X11 the read waits about a second for whoever owns the selection to answer and returns empty text if nobody does. A Wayland compositor without wl\_data\_device\_manager returns an error.

<a id="Context.CloseRequested"></a>

#### Context.CloseRequested

```go
func (c *Context) CloseRequested() bool
```

CloseRequested reports that the user asked to close the window since the last Update. With Config.HandleClose the loop keeps running and the game decides: save, ask, then Quit. Without it the loop quits on its own and this is never true.

<a id="Context.ConsoleFrame"></a>

#### Context.ConsoleFrame

```go
func (c *Context) ConsoleFrame() console.Frame
```

ConsoleFrame reports the state the debug console draws from. The console calls it; the engine passes its context to Console.Draw and never calls this itself. The Stats.Scopes and Stats.GPU slices of the frame it returns are reused by the next call, so a caller that keeps them past the frame copies them first.

<a id="Context.CursorCaptured"></a>

#### Context.CursorCaptured

```go
func (c *Context) CursorCaptured() bool
```

CursorCaptured reports the capture state.

<a id="Context.Displays"></a>

#### Context.Displays

```go
func (c *Context) Displays() ([]Display, error)
```

Displays enumerates displays known to the active window system. It performs no display mode switch. Call on the game goroutine, as for window controls.

<a id="Context.Focused"></a>

#### Context.Focused

```go
func (c *Context) Focused() bool
```

Focused reports whether the window has keyboard focus.

<a id="Context.Fullscreen"></a>

#### Context.Fullscreen

```go
func (c *Context) Fullscreen() bool
```

Fullscreen reports whether the window is full screen.

<a id="Context.Hide"></a>

#### Context.Hide

```go
func (c *Context) Hide() error
```

Hide requests that the window be hidden. Wake does not show a hidden window. Wayland and headless mode return ErrUnsupported.

<a id="Context.KeyboardLayout"></a>

#### Context.KeyboardLayout

```go
func (c *Context) KeyboardLayout() (input.KeyboardLayout, error)
```

KeyboardLayout reads the current native keyboard layout for binding labels and physical/logical lookup. It does not modify text-input or dead-key state. Refresh when displaying binding UI; a snapshot is not intended for per-frame polling. Call on the game goroutine. Headless mode or an unavailable native keymap returns ErrUnsupported; Key.String remains a stable physical-name fallback.

<a id="Context.NewUI"></a>

#### Context.NewUI

```go
func (c *Context) NewUI(theme ui.Theme) (*ui.Context, error)
```

NewUI creates an interface with clipboard and input-method placement connected to this context. Create it once during Init and reuse it while this graphics context is alive.

A zero theme selects DarkTheme with the engine's shared Go Regular font at 14 view units. A custom theme keeps every setting; only a nil Font is filled with that default. The engine owns the default font, so do not destroy it separately. Device recovery requires creating a new interface.

Example:

```go
package main

import (
	"github.com/matjam/bunyip/engine"
	"github.com/matjam/bunyip/ui"
)

func main() {
	var menu *ui.Context
	g := engine.GameFuncs{
		InitFunc: func(ctx *engine.Context) error {
			var err error
			menu, err = ctx.NewUI(ui.Theme{})
			return err
		},
		DrawFunc: func(ctx *engine.Context) error {
			menu.Begin(ctx.Input, func() {
				menu.Panel("Menu", ui.Rect{X: 8, Y: 8, W: 160, H: 70}, func() {
					menu.Label("Ready")
				})
			})
			return nil
		},
	}
	err := engine.Run(engine.Config{Title: "Interface", Width: 640, Height: 480}, g)
	if err != nil {
		panic(err)
	}
}
```

<a id="Context.NewWindow"></a>

#### Context.NewWindow

```go
func (c *Context) NewWindow(cfg Config, game Game) (result *Window, err error)
```

NewWindow creates an additional window and calls its optional Init before returning. Update and Draw begin in the next scheduler iteration. Failure releases everything created for the window, including children and Cleanup registrations; Shutdown runs only after successful Init.

The new window inherits the application's headless/native mode and Vulkan validation. FixedClock is inherited when the application uses it. Window, view, timing and console settings otherwise use Config's normal defaults. Log defaults to the creating context's logger. LogFile and Pprof are application settings and cannot be set here; NoAudio cannot disable the shared mixer. A child context's Quit closes that child and its descendants; the main context's Quit ends Run. Callback errors return from Run.

Call on the game goroutine from Init, Update or Draw. Device-loss recovery closes all children; recreate them from the main game's Recover callback.

Example:

```go
package main

import (
	"github.com/matjam/bunyip/engine"
	"github.com/matjam/bunyip/gfx"
)

func main() {
	engine.Run(engine.Config{Title: "Editor"}, engine.GameFuncs{
		InitFunc: func(ctx *engine.Context) error {
			_, err := ctx.NewWindow(engine.Config{Title: "Preview", Width: 480, Height: 320}, engine.GameFuncs{
				DrawFunc: func(preview *engine.Context) error {
					preview.Gfx.FillRect(20, 20, 100, 100, gfx.RGB(100, 180, 255))
					return nil
				},
			})
			return err
		},
	})
}
```

<a id="Context.Position"></a>

#### Context.Position

```go
func (c *Context) Position() (x, y int)
```

Position returns the window frame's top-left corner on the screen, in points from the screen's top-left, for saving with the settings. Platforms other than macOS and headless runs return (0, 0).

<a id="Context.Profile"></a>

#### Context.Profile

```go
func (c *Context) Profile(name string) ProfileScope
```

Profile starts timing a section of game code, until End is called on what it returns; the result shows in Stats.Scopes and the debug overlay:

	defer ctx.Profile("pathfinding").End()

Timing runs whether or not the overlay is shown, so the figures are there the moment F3 opens it.

<a id="Context.Quit"></a>

#### Context.Quit

```go
func (c *Context) Quit()
```

Quit closes this window and its descendants after the current callback. On the main context it ends Run and closes every additional window.

<a id="Context.RequestFocus"></a>

#### Context.RequestFocus

```go
func (c *Context) RequestFocus() error
```

RequestFocus asks the desktop to focus the window. A successful request does not guarantee focus: use Focused to observe the desktop's decision. Wayland activation is not implemented and returns ErrUnsupported.

<a id="Context.RequestRedraw"></a>

#### Context.RequestRedraw

```go
func (c *Context) RequestRedraw()
```

RequestRedraw asks a turn-based loop to draw again without waiting for input, for animations that span turns. Real-time loops always redraw.

<a id="Context.Screenshot"></a>

#### Context.Screenshot

```go
func (c *Context) Screenshot(path string)
```

Screenshot writes the next drawn frame to a PNG at path.

<a id="Context.SetAlwaysOnTop"></a>

#### Context.SetAlwaysOnTop

```go
func (c *Context) SetAlwaysOnTop(on bool) error
```

SetAlwaysOnTop keeps the window above other applications' windows, for an overlay or a companion tool. macOS and Windows support it; X11 sends a window-manager request. Wayland leaves stacking to compositor policy and returns ErrUnsupported, as does headless mode.

<a id="Context.SetBounds"></a>

#### Context.SetBounds

```go
func (c *Context) SetBounds(x, y, width, height int) error
```

SetBounds places embedded content in its parent's logical points, with a top-left origin (X11 uses pixels). It disables automatic parent fitting. Width and height must be positive. Top-level and headless outputs return ErrUnsupported. Call on the game goroutine.

<a id="Context.SetClipboard"></a>

#### Context.SetClipboard

```go
func (c *Context) SetClipboard(text string) error
```

SetClipboard puts text on the system clipboard. On Linux the text is handed over when another program asks for it, so it stays on the clipboard only while the game runs. Under Wayland it returns an error until the window has had input, because the compositor changes the selection only in answer to a key, a button or the pointer arriving.

<a id="Context.SetCursor"></a>

#### Context.SetCursor

```go
func (c *Context) SetCursor(shape Cursor)
```

SetCursor picks the pointer's shape over the window.

<a id="Context.SetCursorCaptured"></a>

#### Context.SetCursorCaptured

```go
func (c *Context) SetCursorCaptured(on bool)
```

SetCursorCaptured hides the cursor and delivers relative motion only, through Input.MouseDelta.

<a id="Context.SetCursorImage"></a>

#### Context.SetCursorImage

```go
func (c *Context) SetCursorImage(img image.Image, hotX, hotY int) error
```

SetCursorImage replaces the pointer with an image, its hot spot at (hotX, hotY) pixels from the image's top-left: a crosshair, a hand, a sword. SetCursor with a shape puts the system pointer back. The image is copied and owned by the window until replacement or close. Nil/empty images, dimensions above 4096 pixels, and out-of-bounds hotspots return an error. Supported on macOS, Windows, Wayland with SHM, and X11 with Render ARGB32. Headless or unavailable cursor support returns ErrUnsupported. Image pixels map to logical cursor units on macOS/Wayland and desktop pixels on X11/Windows.

<a id="Context.SetCursorVisible"></a>

#### Context.SetCursorVisible

```go
func (c *Context) SetCursorVisible(on bool)
```

SetCursorVisible shows or hides the pointer over the window.

<a id="Context.SetFullscreen"></a>

#### Context.SetFullscreen

```go
func (c *Context) SetFullscreen(on bool)
```

SetFullscreen enters or leaves full-screen mode.

<a id="Context.SetIcon"></a>

#### Context.SetIcon

```go
func (c *Context) SetIcon(img image.Image)
```

SetIcon sets the window's or application's icon from an image; 256 pixels square is a good size.

<a id="Context.SetPointerPosition"></a>

#### Context.SetPointerPosition

```go
func (c *Context) SetPointerPosition(x, y float32) error
```

SetPointerPosition requests a pointer position in view coordinates. It uses the current viewport and window scale; input changes arrive on the next poll. Wayland forbids arbitrary pointer warping and returns ErrUnsupported.

<a id="Context.SetPosition"></a>

#### Context.SetPosition

```go
func (c *Context) SetPosition(x, y int)
```

SetPosition moves the window so its frame's top-left corner sits at a point on the screen, in points from the screen's top-left: a remembered position from a settings file, a tool window beside the main one. Implemented on macOS. Linux placement belongs to the compositor or window manager; Windows and headless positioning are not implemented.

<a id="Context.SetSize"></a>

#### Context.SetSize

```go
func (c *Context) SetSize(width, height int) error
```

SetSize requests a positive content size in logical window points. Resize events update the view later; the desktop can constrain the requested size. Call this and the other native controls on the game goroutine.

<a id="Context.SetSizeLimits"></a>

#### Context.SetSizeLimits

```go
func (c *Context) SetSizeLimits(minW, minH, maxW, maxH int)
```

SetSizeLimits bounds the window's content size in points; zero lifts a bound.

<a id="Context.SetTextInputRect"></a>

#### Context.SetTextInputRect

```go
func (c *Context) SetTextInputRect(x, y, w, h float32)
```

SetTextInputRect tells the operating system's input method where text is being entered, in view units from the top-left, so that candidate windows for languages such as Japanese open beside the field. Text fields in the ui package call it for you. The rectangle follows the fixed view's scaling and letterboxing. Candidate placement is implemented on macOS; other platforms ignore it.

<a id="Context.SetTimeScale"></a>

#### Context.SetTimeScale

```go
func (c *Context) SetTimeScale(scale float64)
```

SetTimeScale changes how fast game time runs: 0.5 is slow motion, 2 is double speed, 0 freezes the simulation. It scales Delta, which every system and animation steps by, and leaves the update rate and Time alone, so the loop keeps its fixed step and the clock keeps real time. Negative values are clamped to zero. The default is 1.

<a id="Context.SetTitle"></a>

#### Context.SetTitle

```go
func (c *Context) SetTitle(title string)
```

SetTitle changes the window's title.

<a id="Context.Show"></a>

#### Context.Show

```go
func (c *Context) Show() error
```

Show requests that the window be shown, without requesting keyboard focus. Native visibility changes arrive through the regular event loop. The current Wayland backend does not implement hide/remap coordination and returns ErrUnsupported, as does headless mode.

<a id="Context.TimeScale"></a>

#### Context.TimeScale

```go
func (c *Context) TimeScale() float64
```

TimeScale is how fast game time is running; 1 is real time.

<a id="Context.Visible"></a>

#### Context.Visible

```go
func (c *Context) Visible() bool
```

Visible reports whether the window can be seen. It is false while the window is minimised, and while it is wholly covered by other windows on the platforms that report that; Windows reports only minimising, and a Wayland compositor older than xdg\_toplevel version six reports nothing, so the window stays visible there. A headless run is always visible. While it is false the window does not draw, and Config.PauseHidden also stops updates and silences the mixer.

<a id="Context.Wake"></a>

#### Context.Wake

```go
func (c *Context) Wake()
```

Wake makes a turn-based game run an Update and Draw even though no input arrived. It is safe to call from any goroutine, so a timer, a network reply or a finished asset load can prod the game while it sleeps waiting for the player.

<a id="Context.WindowCapabilities"></a>

#### Context.WindowCapabilities

```go
func (c *Context) WindowCapabilities() WindowCapabilities
```

WindowCapabilities reports available native window operations.

<a id="Cursor"></a>

### Cursor

```go
type Cursor uint8
```

Cursor is a pointer shape for SetCursor.

<a id="CursorArrow"></a>

<a id="CursorHand"></a>

<a id="CursorIBeam"></a>

<a id="CursorCrosshair"></a>

<a id="CursorResizeH"></a>

<a id="CursorResizeV"></a>

<a id="CursorGrab"></a>

<a id="CursorGrabbing"></a>

<a id="CursorNotAllowed"></a>

```go
const (
	CursorArrow      Cursor = Cursor(platform.CursorArrow)
	CursorHand       Cursor = Cursor(platform.CursorHand)
	CursorIBeam      Cursor = Cursor(platform.CursorIBeam)
	CursorCrosshair  Cursor = Cursor(platform.CursorCrosshair)
	CursorResizeH    Cursor = Cursor(platform.CursorResizeH)
	CursorResizeV    Cursor = Cursor(platform.CursorResizeV)
	CursorGrab       Cursor = Cursor(platform.CursorGrab)
	CursorGrabbing   Cursor = Cursor(platform.CursorGrabbing)
	CursorNotAllowed Cursor = Cursor(platform.CursorNotAllowed)
)
```

Standard cursor shapes for SetCursor.

<a id="Display"></a>

<a id="Display.Name"></a>

<a id="Display.Bounds"></a>

<a id="Display.BoundsKnown"></a>

<a id="Display.Scale"></a>

<a id="Display.Current"></a>

<a id="Display.Modes"></a>

### Display

```go
type Display struct {
	Name        string          // system-provided description; not a persistent identifier
	Bounds      image.Rectangle // macOS: logical points; X11/Windows: desktop pixels
	BoundsKnown bool            // false where the backend cannot determine desktop bounds
	Scale       float64         // physical pixels per logical point, or zero when unknown
	Current     VideoMode
	Modes       []VideoMode // advertised modes; Wayland may expose only the current mode
}
```

Display is a snapshot of an attached display and its advertised modes.

<a id="FlyCamera"></a>

<a id="FlyCamera.Position"></a>

<a id="FlyCamera.Yaw"></a>

<a id="FlyCamera.Pitch"></a>

<a id="FlyCamera.Speed"></a>

<a id="FlyCamera.Fast"></a>

<a id="FlyCamera.Sensitivity"></a>

<a id="FlyCamera.FovY"></a>

<a id="FlyCamera.Near"></a>

<a id="FlyCamera.Far"></a>

<a id="FlyCamera.AlwaysLook"></a>

### FlyCamera

```go
type FlyCamera struct {
	Position lin.Vec3
	Yaw      float32 // radians about +y; zero looks along -z
	Pitch    float32 // radians, positive looks up
	// Speed is units per second; zero means 10. Fast multiplies it while
	// Shift is held; zero means 4. Sensitivity is radians per view unit
	// of pointer travel; zero means 0.004.
	Speed, Fast, Sensitivity float32
	// FovY, Near and Far pass through to the camera; zero means the
	// camera's defaults.
	FovY, Near, Far float32
	// AlwaysLook turns the view with every pointer movement, not only
	// while the right button is held: set it when the cursor is captured.
	AlwaysLook bool
}
```

FlyCamera is a free-flying camera for looking around a scene while a game is being written: W, A, S and D move, Q and E go down and up, Shift goes faster, and the view turns while the right mouse button is held (or always, when AlwaysLook is set). Call Update each update and hand Camera to the renderer:

	fly := &engine.FlyCamera{Position: lin.V3(0, 5, 10)}
	// in Update:
	fly.Update(ctx)
	// in Draw:
	ctx.Gfx.SetCamera(fly.Camera())

<a id="FlyCamera.Camera"></a>

#### FlyCamera.Camera

```go
func (f *FlyCamera) Camera() gfx.Camera
```

Camera returns the camera to give the renderer.

<a id="FlyCamera.Forward"></a>

#### FlyCamera.Forward

```go
func (f *FlyCamera) Forward() lin.Vec3
```

Forward is the direction the camera looks along.

<a id="FlyCamera.LookAt"></a>

#### FlyCamera.LookAt

```go
func (f *FlyCamera) LookAt(target lin.Vec3)
```

LookAt turns the camera towards a point.

<a id="FlyCamera.Update"></a>

#### FlyCamera.Update

```go
func (f *FlyCamera) Update(ctx *Context)
```

Update moves and turns the camera from this update's input.

<a id="Game"></a>

<a id="Game.Update"></a>

<a id="Game.Draw"></a>

### Game

```go
type Game interface {
	Update(ctx *Context) error
	Draw(ctx *Context) error
}
```

Game is what Run drives. Update advances the simulation and Draw queues drawing through ctx.Gfx. Optional interfaces Initer and Shutdowner add setup and teardown with a live context.

<a id="GameFuncs"></a>

<a id="GameFuncs.InitFunc"></a>

<a id="GameFuncs.UpdateFunc"></a>

<a id="GameFuncs.DrawFunc"></a>

<a id="GameFuncs.ShutdownFunc"></a>

### GameFuncs

```go
type GameFuncs struct {
	InitFunc     func(*Context) error
	UpdateFunc   func(*Context) error
	DrawFunc     func(*Context) error
	ShutdownFunc func(*Context)
}
```

GameFuncs adapts callbacks to Game. Every callback is optional; a nil callback does nothing. Use it for small games and tools that do not need a type with methods. It does not implement Recoverer: recovery requires a game that explicitly rebuilds its resources.

<a id="GameFuncs.Draw"></a>

#### GameFuncs.Draw

```go
func (g GameFuncs) Draw(ctx *Context) error
```

Draw calls DrawFunc, when set.

<a id="GameFuncs.Init"></a>

#### GameFuncs.Init

```go
func (g GameFuncs) Init(ctx *Context) error
```

Init calls InitFunc, when set.

<a id="GameFuncs.Shutdown"></a>

#### GameFuncs.Shutdown

```go
func (g GameFuncs) Shutdown(ctx *Context)
```

Shutdown calls ShutdownFunc, when set and setup succeeded.

<a id="GameFuncs.Update"></a>

#### GameFuncs.Update

```go
func (g GameFuncs) Update(ctx *Context) error
```

Update calls UpdateFunc, when set.

<a id="Initer"></a>

<a id="Initer.Init"></a>

### Initer

```go
type Initer interface {
	Init(ctx *Context) error
}
```

Initer is implemented by games that need the graphics context to load resources before the first update.

<a id="NativeBackend"></a>

### NativeBackend

```go
type NativeBackend uint8
```

NativeBackend identifies the native type of a borrowed embedding parent.

<a id="NativeWin32"></a>

<a id="NativeCocoa"></a>

<a id="NativeX11"></a>

```go
const (
	NativeWin32 NativeBackend = iota + 1 // HWND in the current process and UI thread
	NativeCocoa                          // NSView attached to an NSWindow on the main thread
	NativeX11                            // Window XID on the engine's X server and screen
)
```

<a id="NativeParent"></a>

<a id="NativeParent.Backend"></a>

<a id="NativeParent.Handle"></a>

### NativeParent

```go
type NativeParent struct {
	Backend NativeBackend
	Handle  uintptr
}
```

NativeParent is borrowed native content into which Bunyip inserts an owned rendering child. Keep it alive until Run returns or the additional Window reports Closed. Bunyip never destroys the parent or replaces its delegate. Run owns scheduling and native event dispatch on the host UI thread; this is not an adapter for toolkits that require their own event loop.

<a id="ProfileScope"></a>

### ProfileScope

```go
type ProfileScope struct {
	// contains filtered or unexported fields
}
```

ProfileScope is a section being timed, returned by Context.Profile and closed with End. It is a small value that costs no allocation, so a game may profile a section that runs many times a frame.

<a id="ProfileScope.End"></a>

#### ProfileScope.End

```go
func (p ProfileScope) End()
```

End closes the scope and records how long it took. Ending a scope twice records it twice; ending the zero ProfileScope does nothing.

<a id="Recoverer"></a>

<a id="Recoverer.Recover"></a>

### Recoverer

```go
type Recoverer interface {
	Recover(ctx *Context) error
}
```

Recoverer is implemented by games that can survive a lost GPU device. When the driver reports a device loss, Run rebuilds the graphics stack and calls Recover with the new context; every texture, mesh, font and render texture the game created is gone and must be created again. Input, mixer and console are also new. Restore mixer/bus settings and restart desired playback; old voice handles belong to the discarded mixer. Device loss in any additional window tears down the entire application family. Recreate additional windows from the main game's Recover callback; old Window handles are closed and their contexts are no longer usable. Games without Recover get the error from Run instead.

<a id="Scaling"></a>

### Scaling

```go
type Scaling uint8
```

Scaling is how a fixed view fills the window.

<a id="ScaleFit"></a>

<a id="ScaleInteger"></a>

<a id="ScaleStretch"></a>

```go
const (
	// ScaleFit is the largest size that fits with the aspect ratio kept;
	// black bars fill the rest.
	ScaleFit Scaling = iota
	// ScaleInteger scales by whole numbers only, so pixel art stays crisp,
	// falling back to ScaleFit when even one times does not fit.
	ScaleInteger
	// ScaleStretch fills the window, distorting the aspect ratio.
	ScaleStretch
)
```

<a id="Scope"></a>

<a id="Scope.Name"></a>

<a id="Scope.MS"></a>

### Scope

```go
type Scope struct {
	Name string
	MS   float64
}
```

Scope is one timed section recorded with Context.Profile.

<a id="Shutdowner"></a>

<a id="Shutdowner.Shutdown"></a>

### Shutdowner

```go
type Shutdowner interface {
	Shutdown(ctx *Context)
}
```

Shutdowner is implemented by games that free resources on exit. Shutdown runs with a live context after successful Init or Recover, including before a device-loss rebuild. It is not called if Init or Recover returns an error. Context.Cleanup runs even when setup fails, and Graphics always releases the GPU resources it owns.

<a id="Stats"></a>

<a id="Stats.FPS"></a>

<a id="Stats.FrameMS"></a>

<a id="Stats.UpdateMS"></a>

<a id="Stats.DrawMS"></a>

<a id="Stats.PresentMS"></a>

<a id="Stats.Updates"></a>

<a id="Stats.Scopes"></a>

<a id="Stats.Draws2D"></a>

<a id="Stats.Vertices2D"></a>

<a id="Stats.Draws3D"></a>

<a id="Stats.Instances"></a>

<a id="Stats.Waits"></a>

<a id="Stats.GPUFrameMS"></a>

<a id="Stats.GPU"></a>

### Stats

```go
type Stats struct {
	FPS       float64 // frames per second over the last second
	FrameMS   float64 // wall time from one frame's start to the next
	UpdateMS  float64 // time spent in Update calls this frame
	DrawMS    float64 // time spent in Draw
	PresentMS float64 // time submitting and waiting on the GPU
	Updates   int     // Update calls this frame
	Scopes    []Scope // Profile scopes recorded this frame

	// GPU work in the last finished frame: 2D draw calls after batching
	// and their vertices, mesh draw calls after instancing and the
	// instances they covered. A rising Draws2D means state changes are
	// breaking batches: textures, shaders, blend modes, clips.
	Draws2D, Vertices2D, Draws3D, Instances int
	// Waits counts the times the last finished frame stopped for the GPU
	// to go idle. Uploads and destroys inside a frame do not, so a
	// running game reports zero; the overlay names it when it is not.
	Waits int

	// GPUFrameMS is how long the GPU spent on a recent frame, from its
	// first pass to the end of its last. GPU breaks that down by pass:
	// the shadow atlas, the opaque and blended scene, the reflections, the
	// decals, bloom, ambient occlusion, the composite and the 2D stream.
	// Both come from
	// timestamp queries read back a frame or two later, so they lag the
	// frame on screen, and both are zero and empty on a device without
	// timestamp queries.
	GPUFrameMS float64
	GPU        []Scope
}
```

Stats are the previous frame's timings, kept up to date on Context.

<a id="VideoMode"></a>

<a id="VideoMode.Width"></a>

<a id="VideoMode.Height"></a>

<a id="VideoMode.RefreshHz"></a>

### VideoMode

```go
type VideoMode struct {
	Width, Height int     // physical pixels
	RefreshHz     float64 // zero means the operating system did not report a rate
}
```

VideoMode describes physical pixel dimensions and a reported refresh rate.

<a id="Window"></a>

### Window

```go
type Window struct {
	// contains filtered or unexported fields
}
```

Window is an additional output managed by Run. Its callbacks and controls run on the same game goroutine as the main window. Each output owns its Graphics and Input; GPU resources cannot be shared between outputs. Audio is shared by every window in the application.

<a id="Window.Close"></a>

#### Window.Close

```go
func (w *Window) Close()
```

Close requests closure of this window and its descendants after the active callbacks finish. It does not close its parent. Call on the game goroutine.

<a id="Window.Closed"></a>

#### Window.Closed

```go
func (w *Window) Closed() bool
```

Closed reports whether teardown has completed, rather than just requested. A nil handle is closed.

<a id="Window.Context"></a>

#### Window.Context

```go
func (w *Window) Context() *Context
```

Context returns the context supplied to this window's callbacks. After Closed becomes true its graphics resources have been released.

<a id="WindowCapabilities"></a>

<a id="WindowCapabilities.Resize"></a>

<a id="WindowCapabilities.Show"></a>

<a id="WindowCapabilities.Hide"></a>

<a id="WindowCapabilities.Focus"></a>

<a id="WindowCapabilities.AlwaysOnTop"></a>

<a id="WindowCapabilities.CursorImage"></a>

<a id="WindowCapabilities.PointerPosition"></a>

<a id="WindowCapabilities.EmbeddedBounds"></a>

### WindowCapabilities

```go
type WindowCapabilities struct {
	Resize, Show, Hide, Focus, AlwaysOnTop, CursorImage, PointerPosition bool
	EmbeddedBounds                                                       bool // SetBounds can place this embedded child
}
```

WindowCapabilities describes native requests the active backend can issue. Desktop policy can still decline supported requests.
