Преглед на файлове

Minor changes; vendor dependencies

Jonathan D. Storm преди 1 месец
родител
ревизия
595233fc06
променени са 100 файла, в които са добавени 33489 реда и са изтрити 66 реда
  1. 1 4
      go.mod
  2. 0 2
      go.sum
  3. 45 60
      main.go
  4. 4 0
      notes.md
  5. 63 0
      vendor/gioui.org/LICENSE
  6. 68 0
      vendor/gioui.org/app/Gio.java
  7. 63 0
      vendor/gioui.org/app/GioActivity.java
  8. 842 0
      vendor/gioui.org/app/GioView.java
  9. 149 0
      vendor/gioui.org/app/app.go
  10. 135 0
      vendor/gioui.org/app/d3d11_windows.go
  11. 12 0
      vendor/gioui.org/app/datadir.go
  12. 66 0
      vendor/gioui.org/app/doc.go
  13. 66 0
      vendor/gioui.org/app/egl_android.go
  14. 91 0
      vendor/gioui.org/app/egl_wayland.go
  15. 59 0
      vendor/gioui.org/app/egl_windows.go
  16. 61 0
      vendor/gioui.org/app/egl_x11.go
  17. 6 0
      vendor/gioui.org/app/framework_ios.h
  18. 148 0
      vendor/gioui.org/app/gl_ios.go
  19. 47 0
      vendor/gioui.org/app/gl_ios.m
  20. 79 0
      vendor/gioui.org/app/gl_js.go
  21. 124 0
      vendor/gioui.org/app/gl_macos.go
  22. 73 0
      vendor/gioui.org/app/gl_macos.m
  23. 118 0
      vendor/gioui.org/app/ime.go
  24. 858 0
      vendor/gioui.org/app/internal/windows/windows.go
  25. 375 0
      vendor/gioui.org/app/internal/xkb/xkb_unix.go
  26. 87 0
      vendor/gioui.org/app/log_android.go
  27. 54 0
      vendor/gioui.org/app/log_ios.go
  28. 35 0
      vendor/gioui.org/app/log_windows.go
  29. 174 0
      vendor/gioui.org/app/metal_darwin.go
  30. 51 0
      vendor/gioui.org/app/metal_ios.go
  31. 51 0
      vendor/gioui.org/app/metal_macos.go
  32. 365 0
      vendor/gioui.org/app/os.go
  33. 1500 0
      vendor/gioui.org/app/os_android.go
  34. 272 0
      vendor/gioui.org/app/os_darwin.go
  35. 11 0
      vendor/gioui.org/app/os_darwin.m
  36. 446 0
      vendor/gioui.org/app/os_ios.go
  37. 302 0
      vendor/gioui.org/app/os_ios.m
  38. 827 0
      vendor/gioui.org/app/os_js.go
  39. 1168 0
      vendor/gioui.org/app/os_macos.go
  40. 459 0
      vendor/gioui.org/app/os_macos.m
  41. 99 0
      vendor/gioui.org/app/os_unix.go
  42. 124 0
      vendor/gioui.org/app/os_wayland.c
  43. 1933 0
      vendor/gioui.org/app/os_wayland.go
  44. 995 0
      vendor/gioui.org/app/os_windows.go
  45. 929 0
      vendor/gioui.org/app/os_x11.go
  46. 30 0
      vendor/gioui.org/app/runmain.go
  47. 13 0
      vendor/gioui.org/app/system.go
  48. 218 0
      vendor/gioui.org/app/vulkan.go
  49. 90 0
      vendor/gioui.org/app/vulkan_android.go
  50. 81 0
      vendor/gioui.org/app/vulkan_wayland.go
  51. 81 0
      vendor/gioui.org/app/vulkan_x11.go
  52. 100 0
      vendor/gioui.org/app/wayland_text_input.c
  53. 836 0
      vendor/gioui.org/app/wayland_text_input.h
  54. 79 0
      vendor/gioui.org/app/wayland_xdg_decoration.c
  55. 382 0
      vendor/gioui.org/app/wayland_xdg_decoration.h
  56. 185 0
      vendor/gioui.org/app/wayland_xdg_shell.c
  57. 2003 0
      vendor/gioui.org/app/wayland_xdg_shell.h
  58. 984 0
      vendor/gioui.org/app/window.go
  59. 172 0
      vendor/gioui.org/f32/affine.go
  60. 60 0
      vendor/gioui.org/f32/f32.go
  61. 126 0
      vendor/gioui.org/font/font.go
  62. 83 0
      vendor/gioui.org/font/gofont/gofont.go
  63. 190 0
      vendor/gioui.org/font/opentype/opentype.go
  64. 481 0
      vendor/gioui.org/gesture/gesture.go
  65. 40 0
      vendor/gioui.org/gpu/api.go
  66. 153 0
      vendor/gioui.org/gpu/caches.go
  67. 146 0
      vendor/gioui.org/gpu/clip.go
  68. 1592 0
      vendor/gioui.org/gpu/gpu.go
  69. 5 0
      vendor/gioui.org/gpu/internal/d3d11/d3d11.go
  70. 876 0
      vendor/gioui.org/gpu/internal/d3d11/d3d11_windows.go
  71. 129 0
      vendor/gioui.org/gpu/internal/driver/api.go
  72. 238 0
      vendor/gioui.org/gpu/internal/driver/driver.go
  73. 5 0
      vendor/gioui.org/gpu/internal/metal/metal.go
  74. 1159 0
      vendor/gioui.org/gpu/internal/metal/metal_darwin.go
  75. 1381 0
      vendor/gioui.org/gpu/internal/opengl/opengl.go
  76. 176 0
      vendor/gioui.org/gpu/internal/opengl/srgb.go
  77. 1163 0
      vendor/gioui.org/gpu/internal/vulkan/vulkan.go
  78. 5 0
      vendor/gioui.org/gpu/internal/vulkan/vulkan_nosupport.go
  79. 109 0
      vendor/gioui.org/gpu/pack.go
  80. 424 0
      vendor/gioui.org/gpu/path.go
  81. 94 0
      vendor/gioui.org/gpu/timer.go
  82. 36 0
      vendor/gioui.org/internal/byteslice/byteslice.go
  83. 21 0
      vendor/gioui.org/internal/cocoainit/cocoa_darwin.go
  84. 1694 0
      vendor/gioui.org/internal/d3d11/d3d11_windows.go
  85. 57 0
      vendor/gioui.org/internal/debug/debug.go
  86. 251 0
      vendor/gioui.org/internal/egl/egl.go
  87. 109 0
      vendor/gioui.org/internal/egl/egl_unix.go
  88. 191 0
      vendor/gioui.org/internal/egl/egl_windows.go
  89. 175 0
      vendor/gioui.org/internal/f32/f32.go
  90. 191 0
      vendor/gioui.org/internal/f32color/rgba.go
  91. 25 0
      vendor/gioui.org/internal/f32color/tables.go
  92. 95 0
      vendor/gioui.org/internal/fling/animation.go
  93. 332 0
      vendor/gioui.org/internal/fling/extrapolation.go
  94. 131 0
      vendor/gioui.org/internal/gl/gl.go
  95. 653 0
      vendor/gioui.org/internal/gl/gl_js.go
  96. 1323 0
      vendor/gioui.org/internal/gl/gl_unix.go
  97. 627 0
      vendor/gioui.org/internal/gl/gl_windows.go
  98. 77 0
      vendor/gioui.org/internal/gl/types.go
  99. 90 0
      vendor/gioui.org/internal/gl/types_js.go
  100. 87 0
      vendor/gioui.org/internal/gl/util.go

+ 1 - 4
go.mod

@@ -2,10 +2,7 @@ module graph_layout
 
 go 1.23.4
 
-require (
-	gioui.org v0.8.0
-	gioui.org/x v0.8.1
-)
+require gioui.org v0.8.0
 
 require (
 	gioui.org/shader v1.0.8 // indirect

+ 0 - 2
go.sum

@@ -5,8 +5,6 @@ gioui.org v0.8.0/go.mod h1:vEMmpxMOd/iwJhXvGVIzWEbxMWhnMQ9aByOGQdlQ8rc=
 gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
 gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
 gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
-gioui.org/x v0.8.1 h1:Q2wumEOfjz3XfRa3TEi6w7dq8+cxV8zsYK8xXQkrCRk=
-gioui.org/x v0.8.1/go.mod h1:v2g60aiZtIVR7lNFXZ123+U0kijJeOChODSuqr7MFSI=
 github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
 github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
 github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=

+ 45 - 60
main.go

@@ -65,8 +65,7 @@ func run(window *app.Window) error {
 	g.nodes = g._nodes[:0]
 	g.allocs = g._allocs[:]
 	g.edges = g._edges[:0]
-
-	//generateRandomGraph(&g)
+	g.adjacency = g._adjacency[:0]
 
 	title := material.Body1(theme, "")
 	title.Color = theme.Fg
@@ -82,11 +81,13 @@ func run(window *app.Window) error {
 	simFinishedTime := time.Now()
 	simCnt := int64(0)
 	var (
-		stepOut         strings.Builder
+		stepOutput      strings.Builder
 		statusText      strings.Builder
 		ops             op.Ops
 		screensaverMode bool = true
-		finished        bool
+		finished,
+		resetRequested,
+		bumpRequested bool
 	)
 	for {
 		switch e := window.Event().(type) {
@@ -97,10 +98,6 @@ func run(window *app.Window) error {
 			gtx := app.NewContext(&ops, e)
 
 			// Handle input
-			var (
-				resetRequested,
-				bumpRequested bool
-			)
 			for {
 				event, ok := e.Source.Event(
 					key.Filter{Name: "B"},
@@ -153,15 +150,15 @@ func run(window *app.Window) error {
 			if time.Since(lastStep) >= simPeriod {
 				if int(g.order) == cap(g.nodes) && bumpRequested {
 					bump(&g, bumpShift)
-					bumpShift++
+					//bumpShift++
 					bumpRequested = false
 				}
-				stepOut.Reset()
-				stepOut.WriteString((&g).Step())
+				stepOutput.Reset()
+				stepOutput.WriteString((&g).Step())
 				//statusText.WriteString((&g).Semilattice())
 				lastStep = time.Now()
 
-				addRandomNode(&g)
+				addNodeAtRandom(&g)
 				stepCnt++
 			}
 
@@ -206,7 +203,7 @@ func run(window *app.Window) error {
 			statusText.WriteString(fmt.Sprintf("simulation rate: %s per step\n", simPeriod))
 			statusText.WriteString(fmt.Sprintf("fps=%d; sps=%d\n", fps, sps))
 
-			statusText.WriteString(stepOut.String())
+			statusText.WriteString(stepOutput.String())
 			title.Text = statusText.String()
 
 			if int(g.order) == cap(g.nodes) && !finished {
@@ -223,7 +220,7 @@ func run(window *app.Window) error {
 
 func bump(g *Graph, shift int) int {
 	root := -1
-	for n := len(g.nodes) - 1; n >= 0; n-- {
+	for n := 0; n < int(g.order); n++ {
 		if g.nodes[n] == 1 {
 			root = n
 			break
@@ -233,6 +230,7 @@ func bump(g *Graph, shift int) int {
 		return root
 	}
 	g.nodes[root] <<= shift
+	return root
 
 	nodes := make([]NodeId, 1, g.order)
 	nextNodes := make(map[NodeId]struct{}, g.order)
@@ -260,11 +258,16 @@ func bump(g *Graph, shift int) int {
 	return root
 }
 
-func addRandomNode(g *Graph) (order uint32) {
+func addNodeAtRandom(g *Graph) (order uint32) {
 	if g.order == uint32(cap(g.nodes)) ||
 		g.size == uint32(cap(g.edges)) {
 		return g.order
 	}
+
+	/*threshold := 0.05 * float64(g.order)
+	if g.random.ExpFloat64() <= threshold {
+		return g.order
+	}*/
 	g.edges = g.edges[:cap(g.edges)]
 	g.nodes = g.nodes[:cap(g.nodes)]
 
@@ -292,52 +295,32 @@ func addRandomNode(g *Graph) (order uint32) {
 	return g.order
 }
 
-func generateRandomGraph(g *Graph) {
-	k := EdgeId(0) // Graph size
-
-	for n := 0; n < len(g.nodes); n++ {
-		g.nodes[n] = 1
-		g.offsets[n] = k
-
-		weight := float64(0)
-		for i := 1; n+i < len(g.nodes); i++ {
-			g.edges[k] = NodeId(n + i)
-
-			threshold :=
-				1.4 + weight*float64(n)/float64(len(g.nodes))
-
-			if g.random.ExpFloat64() <= threshold {
-				continue
-			}
-			weight++
-			k++
-		}
-	}
-	g.offsets[len(g.nodes)] = k
-	g.edges = g.edges[:k]
-}
-
 type NodeId uint32
 type EdgeId uint32
 
-const GraphOrder int = 64
+const GraphOrder int = 128
 
 type Graph struct {
-	_offsets [GraphOrder + 1]EdgeId
-	_nodes   [GraphOrder]uint32
-	_allocs  [GraphOrder]uint32
-	_edges   [(GraphOrder * (GraphOrder - 1)) >> 1]NodeId
-	_degrees [GraphOrder]byte
-	random   *rand.Rand
-	offsets  []EdgeId
-	nodes    []uint32
-	allocs   []uint32 // Address allocation state, per node
-	edges    []NodeId // Adjacent node ids
-	degrees  []byte   // Node degrees
-	size     uint32   // Number of edges added to graph
-	order    uint32   // Number of nodes added to graph
-	source   NodeId   // A selected source node
-	target   NodeId   // A selected target node
+	_adjacency [GraphOrder * (GraphOrder - 1)]NodeId
+	_edges     [(GraphOrder * (GraphOrder - 1)) >> 1]NodeId
+	_nodes     [GraphOrder]uint32
+	_allocs    [GraphOrder]uint32
+	_offsets   [GraphOrder + 1]EdgeId
+	_degrees   [GraphOrder]byte
+
+	random *rand.Rand
+
+	adjacency []NodeId // Bidirectional adjacent node ids
+	edges     []NodeId // Unidirectional adjacent node ids
+	nodes     []uint32
+	allocs    []uint32 // Address allocation state, per node
+	offsets   []EdgeId
+	degrees   []byte // Node degrees
+
+	size   uint32 // Number of edges added to graph
+	order  uint32 // Number of nodes added to graph
+	source NodeId // A selected source node
+	target NodeId // A selected target node
 }
 
 func (g *Graph) Reset() {
@@ -387,18 +370,20 @@ func (g *Graph) Step() string {
 		}
 		edges := g.edges[g.offsets[n]:g.offsets[n+1]]
 
+		shiftStart := 1
 		// Shift a node containing exactly one 1
 		if (n == 0 || g.nodes[n] > 1) && // root or assigned
 			g.nodes[n]&(g.nodes[n]-1) == 0 { // ...contains one 1
-			if len(edges) > 0 {
-				e := edges[0]
+
+			for i := 0; i < min(shiftStart, len(edges)); i++ {
+				e := edges[i]
 				if uint32(e) < g.order && g.nodes[n] >= g.nodes[e] {
-					g.nodes[e] = g.nodes[n] << 1
+					g.nodes[e] = g.nodes[n] << (i + 1)
 				}
 			}
 		}
 		// Assign addresses to neighbors
-		for i := 1; i < len(edges); i++ {
+		for i := shiftStart; i < len(edges); i++ {
 			nextAlloc := (g.allocs[n] << shift) | g.nodes[n]
 			e := edges[i]
 			if uint32(e) >= g.order ||

+ 4 - 0
notes.md

@@ -0,0 +1,4 @@
+### Questions
+
+* Are the addresses of every connected component unique?
+

+ 63 - 0
vendor/gioui.org/LICENSE

@@ -0,0 +1,63 @@
+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 <https://unlicense.org/>
+---

+ 68 - 0
vendor/gioui.org/app/Gio.java

@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package org.gioui;
+
+import android.content.ClipboardManager;
+import android.content.ClipData;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.io.UnsupportedEncodingException;
+
+public final class Gio {
+	private static final Object initLock = new Object();
+	private static boolean jniLoaded;
+	private static final Handler handler = new Handler(Looper.getMainLooper());
+
+	/**
+	 * init loads and initializes the Go native library and runs
+	 * the Go main function.
+	 *
+	 * It is exported for use by Android apps that need to run Go code
+	 * outside the lifecycle of the Gio activity.
+	 */
+	public static synchronized void init(Context appCtx) {
+		synchronized (initLock) {
+			if (jniLoaded) {
+				return;
+			}
+			String dataDir = appCtx.getFilesDir().getAbsolutePath();
+			byte[] dataDirUTF8;
+			try {
+				dataDirUTF8 = dataDir.getBytes("UTF-8");
+			} catch (UnsupportedEncodingException e) {
+				throw new RuntimeException(e);
+			}
+			System.loadLibrary("gio");
+			runGoMain(dataDirUTF8, appCtx);
+			jniLoaded = true;
+		}
+	}
+
+	static private native void runGoMain(byte[] dataDir, Context context);
+
+	static void writeClipboard(Context ctx, String s) {
+		ClipboardManager m = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
+		m.setPrimaryClip(ClipData.newPlainText(null, s));
+	}
+
+	static String readClipboard(Context ctx) {
+		ClipboardManager m = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
+		ClipData c = m.getPrimaryClip();
+		if (c == null || c.getItemCount() < 1) {
+			return null;
+		}
+		return c.getItemAt(0).coerceToText(ctx).toString();
+	}
+
+	static void wakeupMainThread() {
+		handler.post(new Runnable() {
+			@Override public void run() {
+				scheduleMainFuncs();
+			}
+		});
+	}
+
+	static private native void scheduleMainFuncs();
+}

+ 63 - 0
vendor/gioui.org/app/GioActivity.java

@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package org.gioui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.content.res.Configuration;
+import android.view.ViewGroup;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+public final class GioActivity extends Activity {
+	private GioView view;
+	public FrameLayout layer;
+
+	@Override public void onCreate(Bundle state) {
+            super.onCreate(state);
+
+            layer = new FrameLayout(this);
+            view = new GioView(this);
+
+            view.setLayoutParams(new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT
+            ));
+            view.setFocusable(true);
+            view.setFocusableInTouchMode(true);
+
+            layer.addView(view);
+            setContentView(layer);
+	}
+
+	@Override public void onDestroy() {
+		view.destroy();
+		super.onDestroy();
+	}
+
+	@Override public void onStart() {
+		super.onStart();
+		view.start();
+	}
+
+	@Override public void onStop() {
+		view.stop();
+		super.onStop();
+	}
+
+	@Override public void onConfigurationChanged(Configuration c) {
+		super.onConfigurationChanged(c);
+		view.configurationChanged();
+	}
+
+	@Override public void onLowMemory() {
+		super.onLowMemory();
+		GioView.onLowMemory();
+	}
+
+	@Override public void onBackPressed() {
+		if (!view.backPressed())
+			super.onBackPressed();
+	}
+}

+ 842 - 0
vendor/gioui.org/app/GioView.java

@@ -0,0 +1,842 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package org.gioui;
+
+import java.lang.Class;
+import java.lang.IllegalAccessException;
+import java.lang.InstantiationException;
+import java.lang.ExceptionInInitializerError;
+import java.lang.SecurityException;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.PointerIcon;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowInsets;
+import android.view.Surface;
+import android.view.SurfaceView;
+import android.view.SurfaceHolder;
+import android.view.Window;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.SurroundingText;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import java.io.UnsupportedEncodingException;
+
+public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
+	private static boolean jniLoaded;
+
+	private final SurfaceHolder.Callback surfCallbacks;
+	private final View.OnFocusChangeListener focusCallback;
+	private final InputMethodManager imm;
+	private final float scrollXScale;
+	private final float scrollYScale;
+	private final AccessibilityManager accessManager;
+	private int keyboardHint;
+
+	private long nhandle;
+
+	public GioView(Context context) {
+		this(context, null);
+	}
+
+	public GioView(Context context, AttributeSet attrs) {
+		super(context, attrs);
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+			setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+		}
+		setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
+
+		// Late initialization of the Go runtime to wait for a valid context.
+		Gio.init(context.getApplicationContext());
+
+		// Set background color to transparent to avoid a flickering
+		// issue on ChromeOS.
+		setBackgroundColor(Color.argb(0, 0, 0, 0));
+
+		ViewConfiguration conf = ViewConfiguration.get(context);
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+			scrollXScale = conf.getScaledHorizontalScrollFactor();
+			scrollYScale = conf.getScaledVerticalScrollFactor();
+
+			// The platform focus highlight is not aware of Gio's widgets.
+			setDefaultFocusHighlightEnabled(false);
+		} else {
+			float listItemHeight = 48; // dp
+			float px = TypedValue.applyDimension(
+				TypedValue.COMPLEX_UNIT_DIP,
+				listItemHeight,
+				getResources().getDisplayMetrics()
+			);
+			scrollXScale = px;
+			scrollYScale = px;
+		}
+
+		setHighRefreshRate();
+
+		accessManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+		imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
+		nhandle = onCreateView(this);
+		setFocusable(true);
+		setFocusableInTouchMode(true);
+		focusCallback = new View.OnFocusChangeListener() {
+			@Override public void onFocusChange(View v, boolean focus) {
+				GioView.this.onFocusChange(nhandle, focus);
+			}
+		};
+		setOnFocusChangeListener(focusCallback);
+		surfCallbacks = new SurfaceHolder.Callback() {
+			@Override public void surfaceCreated(SurfaceHolder holder) {
+				// Ignore; surfaceChanged is guaranteed to be called immediately after this.
+			}
+			@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+				onSurfaceChanged(nhandle, getHolder().getSurface());
+			}
+			@Override public void surfaceDestroyed(SurfaceHolder holder) {
+				onSurfaceDestroyed(nhandle);
+			}
+		};
+		getHolder().addCallback(surfCallbacks);
+	}
+
+	@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
+		if (nhandle != 0) {
+			onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), true, event.getEventTime());
+		}
+		return false;
+	}
+
+	@Override public boolean onKeyUp(int keyCode, KeyEvent event) {
+		if (nhandle != 0) {
+			onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), false, event.getEventTime());
+		}
+		return false;
+	}
+
+	@Override public boolean onGenericMotionEvent(MotionEvent event) {
+		dispatchMotionEvent(event);
+		return true;
+	}
+
+	@Override public boolean onTouchEvent(MotionEvent event) {
+		// Ask for unbuffered events. Flutter and Chrome do it
+		// so assume it's good for us as well.
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+			requestUnbufferedDispatch(event);
+		}
+
+		dispatchMotionEvent(event);
+		return true;
+	}
+
+	private void setCursor(int id) {
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+			return;
+		}
+		PointerIcon pointerIcon = PointerIcon.getSystemIcon(getContext(), id);
+		setPointerIcon(pointerIcon);
+	}
+
+	private void setOrientation(int id, int fallback) {
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+			id = fallback;
+		}
+		((Activity) this.getContext()).setRequestedOrientation(id);
+	}
+
+	private void setFullscreen(boolean enabled) {
+		int flags = this.getSystemUiVisibility();
+		if (enabled) {
+		   flags |= SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+		   flags |= SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+		   flags |= SYSTEM_UI_FLAG_FULLSCREEN;
+		   flags |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+		} else {
+		   flags &= ~SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+		   flags &= ~SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+		   flags &= ~SYSTEM_UI_FLAG_FULLSCREEN;
+		   flags &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+		}
+		this.setSystemUiVisibility(flags);
+	}
+
+	private enum Bar {
+		NAVIGATION,
+		STATUS,
+	}
+
+	private void setBarColor(Bar t, int color, int luminance) {
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+			return;
+		}
+
+		Window window = ((Activity) this.getContext()).getWindow();
+
+		int insetsMask;
+		int viewMask;
+
+		switch (t) {
+		case STATUS:
+			insetsMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+			viewMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+			window.setStatusBarColor(color);
+			break;
+		case NAVIGATION:
+			insetsMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+			viewMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+			window.setNavigationBarColor(color);
+			break;
+		default:
+			throw new RuntimeException("invalid bar type");
+		}
+
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+			return;
+		}
+
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+			int flags = this.getSystemUiVisibility();
+			if (luminance > 128) {
+			   flags |=  viewMask;
+			} else {
+			   flags &= ~viewMask;
+			}
+			this.setSystemUiVisibility(flags);
+			return;
+		}
+
+		WindowInsetsController insetsController = window.getInsetsController();
+		if (insetsController == null) {
+			return;
+		}
+		if (luminance > 128) {
+			insetsController.setSystemBarsAppearance(insetsMask, insetsMask);
+		} else {
+			insetsController.setSystemBarsAppearance(0, insetsMask);
+		}
+	}
+
+	private void setStatusColor(int color, int luminance) {
+		this.setBarColor(Bar.STATUS, color, luminance);
+	}
+
+	private void setNavigationColor(int color, int luminance) {
+		this.setBarColor(Bar.NAVIGATION, color, luminance);
+	}
+
+	private void setHighRefreshRate() {
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+			return;
+		}
+
+		Context context = getContext();
+		Display display = context.getDisplay();
+		Display.Mode[] supportedModes = display.getSupportedModes();
+		if (supportedModes.length <= 1) {
+			// Nothing to set
+			return;
+		}
+
+		Display.Mode currentMode = display.getMode();
+		int currentWidth = currentMode.getPhysicalWidth();
+		int currentHeight = currentMode.getPhysicalHeight();
+
+		float minRefreshRate = -1;
+		float maxRefreshRate = -1;
+		float bestRefreshRate = -1;
+		int bestModeId = -1;
+		for (Display.Mode mode : supportedModes) {
+			float refreshRate = mode.getRefreshRate();
+			float width = mode.getPhysicalWidth();
+			float height = mode.getPhysicalHeight();
+
+			if (minRefreshRate == -1 || refreshRate < minRefreshRate) {
+				minRefreshRate = refreshRate;
+			}
+			if (maxRefreshRate == -1 || refreshRate > maxRefreshRate) {
+				maxRefreshRate = refreshRate;
+			}
+
+			boolean refreshRateIsBetter = bestRefreshRate == -1 || refreshRate > bestRefreshRate;
+			if (width == currentWidth && height == currentHeight && refreshRateIsBetter) {
+				int modeId = mode.getModeId();
+				bestRefreshRate = refreshRate;
+				bestModeId = modeId;
+			}
+		}
+
+		if (bestModeId == -1) {
+			// Not expecting this but just in case
+			return;
+		}
+
+		if (minRefreshRate == maxRefreshRate) {
+			// Can't improve the refresh rate
+			return;
+		}
+
+		Window window = ((Activity) context).getWindow();
+		WindowManager.LayoutParams layoutParams = window.getAttributes();
+		layoutParams.preferredDisplayModeId = bestModeId;
+		window.setAttributes(layoutParams);
+	}
+
+	@Override protected boolean dispatchHoverEvent(MotionEvent event) {
+		if (!accessManager.isTouchExplorationEnabled()) {
+			return super.dispatchHoverEvent(event);
+		}
+		switch (event.getAction()) {
+		case MotionEvent.ACTION_HOVER_ENTER:
+			// Fall through.
+		case MotionEvent.ACTION_HOVER_MOVE:
+			onTouchExploration(nhandle, event.getX(), event.getY());
+			break;
+		case MotionEvent.ACTION_HOVER_EXIT:
+			onExitTouchExploration(nhandle);
+			break;
+		}
+		return true;
+	}
+
+	void sendA11yEvent(int eventType, int viewId) {
+		if (!accessManager.isEnabled()) {
+			return;
+		}
+		AccessibilityEvent event = obtainA11yEvent(eventType, viewId);
+		getParent().requestSendAccessibilityEvent(this, event);
+	}
+
+	AccessibilityEvent obtainA11yEvent(int eventType, int viewId) {
+		AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+		event.setPackageName(getContext().getPackageName());
+		event.setSource(this, viewId);
+		return event;
+	}
+
+	boolean isA11yActive() {
+		return accessManager.isEnabled();
+	}
+
+	void sendA11yChange(int viewId) {
+		if (!accessManager.isEnabled()) {
+			return;
+		}
+		AccessibilityEvent event = obtainA11yEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, viewId);
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+			event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+		}
+		getParent().requestSendAccessibilityEvent(this, event);
+	}
+
+	private void dispatchMotionEvent(MotionEvent event) {
+		if (nhandle == 0) {
+			return;
+		}
+		for (int j = 0; j < event.getHistorySize(); j++) {
+			long time = event.getHistoricalEventTime(j);
+			for (int i = 0; i < event.getPointerCount(); i++) {
+				onTouchEvent(
+						nhandle,
+						event.ACTION_MOVE,
+						event.getPointerId(i),
+						event.getToolType(i),
+						event.getHistoricalX(i, j),
+						event.getHistoricalY(i, j),
+						scrollXScale*event.getHistoricalAxisValue(MotionEvent.AXIS_HSCROLL, i, j),
+						scrollYScale*event.getHistoricalAxisValue(MotionEvent.AXIS_VSCROLL, i, j),
+						event.getButtonState(),
+						time);
+			}
+		}
+		int act = event.getActionMasked();
+		int idx = event.getActionIndex();
+		for (int i = 0; i < event.getPointerCount(); i++) {
+			int pact = event.ACTION_MOVE;
+			if (i == idx) {
+				pact = act;
+			}
+			onTouchEvent(
+					nhandle,
+					pact,
+					event.getPointerId(i),
+					event.getToolType(i),
+					event.getX(i), event.getY(i),
+					scrollXScale*event.getAxisValue(MotionEvent.AXIS_HSCROLL, i),
+					scrollYScale*event.getAxisValue(MotionEvent.AXIS_VSCROLL, i),
+					event.getButtonState(),
+					event.getEventTime());
+		}
+	}
+
+	@Override public InputConnection onCreateInputConnection(EditorInfo editor) {
+		Snippet snip = getSnippet();
+		editor.inputType = this.keyboardHint;
+		editor.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
+		editor.initialSelStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
+		editor.initialSelEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
+		int selStart = editor.initialSelStart - snip.offset;
+		editor.initialCapsMode = TextUtils.getCapsMode(snip.snippet, selStart, this.keyboardHint);
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+			editor.setInitialSurroundingSubText(snip.snippet, imeToUTF16(nhandle, snip.offset));
+		}
+		imeSetComposingRegion(nhandle, -1, -1);
+		return new GioInputConnection();
+	}
+
+	void setInputHint(int hint) {
+		if (hint == this.keyboardHint) {
+			return;
+		}
+		this.keyboardHint = hint;
+		restartInput();
+	}
+
+	void showTextInput() {
+		GioView.this.requestFocus();
+		imm.showSoftInput(GioView.this, 0);
+	}
+
+	void hideTextInput() {
+		imm.hideSoftInputFromWindow(getWindowToken(), 0);
+	}
+
+	@Override protected boolean fitSystemWindows(Rect insets) {
+		if (nhandle != 0) {
+			onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left);
+		}
+		return true;
+	}
+
+	void postFrameCallback() {
+		Choreographer.getInstance().removeFrameCallback(this);
+		Choreographer.getInstance().postFrameCallback(this);
+	}
+
+	@Override public void doFrame(long nanos) {
+		if (nhandle != 0) {
+			onFrameCallback(nhandle);
+		}
+	}
+
+	int getDensity() {
+		return getResources().getDisplayMetrics().densityDpi;
+	}
+
+	float getFontScale() {
+		return getResources().getConfiguration().fontScale;
+	}
+
+	public void start() {
+		if (nhandle != 0) {
+			onStartView(nhandle);
+		}
+	}
+
+	public void stop() {
+		if (nhandle != 0) {
+			onStopView(nhandle);
+		}
+	}
+
+	public void destroy() {
+		if (nhandle != 0) {
+			onDestroyView(nhandle);
+		}
+	}
+
+	protected void unregister() {
+		setOnFocusChangeListener(null);
+		getHolder().removeCallback(surfCallbacks);
+		nhandle = 0;
+	}
+
+	public void configurationChanged() {
+		if (nhandle != 0) {
+			onConfigurationChanged(nhandle);
+		}
+	}
+
+	public boolean backPressed() {
+		if (nhandle == 0) {
+			return false;
+		}
+		return onBack(nhandle);
+	}
+
+	void restartInput() {
+		imm.restartInput(this);
+	}
+
+	void updateSelection() {
+		int selStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
+		int selEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
+		int compStart = imeToUTF16(nhandle, imeComposingStart(nhandle));
+		int compEnd = imeToUTF16(nhandle, imeComposingEnd(nhandle));
+		imm.updateSelection(this, selStart, selEnd, compStart, compEnd);
+	}
+
+	void updateCaret(float m00, float m01, float m02, float m10, float m11, float m12, float caretX, float caretTop, float caretBase, float caretBottom) {
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+			return;
+		}
+		Matrix m = new Matrix();
+		m.setValues(new float[]{m00, m01, m02, m10, m11, m12, 0.0f, 0.0f, 1.0f});
+		m.setConcat(getMatrix(), m);
+		int selStart = imeSelectionStart(nhandle);
+		int selEnd = imeSelectionEnd(nhandle);
+		int compStart = imeComposingStart(nhandle);
+		int compEnd = imeComposingEnd(nhandle);
+		Snippet snip = getSnippet();
+		String composing = "";
+		if (compStart != -1) {
+			composing = snip.substringRunes(compStart, compEnd);
+		}
+		CursorAnchorInfo inf = new CursorAnchorInfo.Builder()
+			.setMatrix(m)
+			.setComposingText(imeToUTF16(nhandle, compStart), composing)
+			.setSelectionRange(imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd))
+			.setInsertionMarkerLocation(caretX, caretTop, caretBase, caretBottom, 0)
+			.build();
+		imm.updateCursorAnchorInfo(this, inf);
+	}
+
+	static private native long onCreateView(GioView view);
+	static private native void onDestroyView(long handle);
+	static private native void onStartView(long handle);
+	static private native void onStopView(long handle);
+	static private native void onSurfaceDestroyed(long handle);
+	static private native void onSurfaceChanged(long handle, Surface surface);
+	static private native void onConfigurationChanged(long handle);
+	static private native void onWindowInsets(long handle, int top, int right, int bottom, int left);
+	static public native void onLowMemory();
+	static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, float scrollX, float scrollY, int buttons, long time);
+	static private native void onKeyEvent(long handle, int code, int character, boolean pressed, long time);
+	static private native void onFrameCallback(long handle);
+	static private native boolean onBack(long handle);
+	static private native void onFocusChange(long handle, boolean focus);
+	static private native AccessibilityNodeInfo initializeAccessibilityNodeInfo(long handle, int viewId, int screenX, int screenY, AccessibilityNodeInfo info);
+	static private native void onTouchExploration(long handle, float x, float y);
+	static private native void onExitTouchExploration(long handle);
+	static private native void onA11yFocus(long handle, int viewId);
+	static private native void onClearA11yFocus(long handle, int viewId);
+	static private native void imeSetSnippet(long handle, int start, int end);
+	static private native String imeSnippet(long handle);
+	static private native int imeSnippetStart(long handle);
+	static private native int imeSelectionStart(long handle);
+	static private native int imeSelectionEnd(long handle);
+	static private native int imeComposingStart(long handle);
+	static private native int imeComposingEnd(long handle);
+	static private native int imeReplace(long handle, int start, int end, String text);
+	static private native int imeSetSelection(long handle, int start, int end);
+	static private native int imeSetComposingRegion(long handle, int start, int end);
+	// imeToRunes converts the Java character index into runes (Java code points).
+	static private native int imeToRunes(long handle, int chars);
+	// imeToUTF16 converts the rune index into Java characters.
+	static private native int imeToUTF16(long handle, int runes);
+
+	private class GioInputConnection implements InputConnection {
+		private int batchDepth;
+
+		@Override public boolean beginBatchEdit() {
+			batchDepth++;
+			return true;
+		}
+
+		@Override public boolean endBatchEdit() {
+			batchDepth--;
+			return batchDepth > 0;
+		}
+
+		@Override public boolean clearMetaKeyStates(int states) {
+			return false;
+		}
+
+		@Override public boolean commitCompletion(CompletionInfo text) {
+			return false;
+		}
+
+		@Override public boolean commitCorrection(CorrectionInfo info) {
+			return false;
+		}
+
+		@Override public boolean commitText(CharSequence text, int cursor) {
+			setComposingText(text, cursor);
+			return finishComposingText();
+		}
+
+		@Override public boolean deleteSurroundingText(int beforeChars, int afterChars) {
+			// translate before and after to runes.
+			int selStart = imeSelectionStart(nhandle);
+			int selEnd = imeSelectionEnd(nhandle);
+			int before = selStart - imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - beforeChars);
+			int after = selEnd - imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) - afterChars);
+			return deleteSurroundingTextInCodePoints(before, after);
+		}
+
+		@Override public boolean finishComposingText() {
+			imeSetComposingRegion(nhandle, -1, -1);
+			return true;
+		}
+
+		@Override public int getCursorCapsMode(int reqModes) {
+			Snippet snip = getSnippet();
+			int selStart = imeSelectionStart(nhandle);
+			int off = imeToUTF16(nhandle, selStart - snip.offset);
+			if (off < 0 || off > snip.snippet.length()) {
+				return 0;
+			}
+			return TextUtils.getCapsMode(snip.snippet, off, reqModes);
+		}
+
+		@Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+			return null;
+		}
+
+		@Override public CharSequence getSelectedText(int flags) {
+			Snippet snip = getSnippet();
+			int selStart = imeSelectionStart(nhandle);
+			int selEnd = imeSelectionEnd(nhandle);
+			String sub = snip.substringRunes(selStart, selEnd);
+			return sub;
+		}
+
+		@Override public CharSequence getTextAfterCursor(int n, int flags) {
+			Snippet snip = getSnippet();
+			int selStart = imeSelectionStart(nhandle);
+			int selEnd = imeSelectionEnd(nhandle);
+			// n are in Java characters, but in worst case we'll just ask for more runes
+			// than wanted.
+			imeSetSnippet(nhandle, selStart - n, selEnd + n);
+			int start = selEnd;
+			int end = imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) + n);
+			String ret = snip.substringRunes(start, end);
+			return ret;
+		}
+
+		@Override public CharSequence getTextBeforeCursor(int n, int flags) {
+			Snippet snip = getSnippet();
+			int selStart = imeSelectionStart(nhandle);
+			int selEnd = imeSelectionEnd(nhandle);
+			// n are in Java characters, but in worst case we'll just ask for more runes
+			// than wanted.
+			imeSetSnippet(nhandle, selStart - n, selEnd + n);
+			int start = imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - n);
+			int end = selStart;
+			String ret = snip.substringRunes(start, end);
+			return ret;
+		}
+
+		@Override public boolean performContextMenuAction(int id) {
+			return false;
+		}
+
+		@Override public boolean performEditorAction(int editorAction) {
+			long eventTime = SystemClock.uptimeMillis();
+			// Translate to enter key.
+			onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', true, eventTime);
+			onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', false, eventTime);
+			return true;
+		}
+
+		@Override public boolean performPrivateCommand(String action, Bundle data) {
+			return false;
+		}
+
+		@Override public boolean reportFullscreenMode(boolean enabled) {
+			return false;
+		}
+
+		@Override public boolean sendKeyEvent(KeyEvent event) {
+			boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
+			onKeyEvent(nhandle, event.getKeyCode(), event.getUnicodeChar(), pressed, event.getEventTime());
+			return true;
+		}
+
+		@Override public boolean setComposingRegion(int startChars, int endChars) {
+			int compStart = imeToRunes(nhandle, startChars);
+			int compEnd = imeToRunes(nhandle, endChars);
+			imeSetComposingRegion(nhandle, compStart, compEnd);
+			return true;
+		}
+
+		@Override public boolean setComposingText(CharSequence text, int relCursor) {
+			int start = imeComposingStart(nhandle);
+			int end = imeComposingEnd(nhandle);
+			if (start == -1 || end == -1) {
+				start = imeSelectionStart(nhandle);
+				end = imeSelectionEnd(nhandle);
+			}
+			String str = text.toString();
+			imeReplace(nhandle, start, end, str);
+			int cursor = start;
+			int runes = str.codePointCount(0, str.length());
+			if (relCursor > 0) {
+				cursor += runes;
+				relCursor--;
+			}
+			imeSetComposingRegion(nhandle, start, start + runes);
+
+			// Move cursor.
+			Snippet snip = getSnippet();
+			cursor = imeToRunes(nhandle, imeToUTF16(nhandle, cursor) + relCursor);
+			imeSetSelection(nhandle, cursor, cursor);
+			return true;
+		}
+
+		@Override public boolean setSelection(int startChars, int endChars) {
+			int start = imeToRunes(nhandle, startChars);
+			int end = imeToRunes(nhandle, endChars);
+			imeSetSelection(nhandle, start, end);
+			return true;
+		}
+
+		/*@Override*/ public boolean requestCursorUpdates(int cursorUpdateMode) {
+			// We always provide cursor updates.
+			return true;
+		}
+
+		/*@Override*/ public void closeConnection() {
+		}
+
+		/*@Override*/ public Handler getHandler() {
+			return null;
+		}
+
+		/*@Override*/ public boolean commitContent(InputContentInfo info, int flags, Bundle opts) {
+			return false;
+		}
+
+		/*@Override*/ public boolean deleteSurroundingTextInCodePoints(int before, int after) {
+			if (after > 0) {
+				int selEnd = imeSelectionEnd(nhandle);
+				imeReplace(nhandle, selEnd, selEnd + after, "");
+			}
+			if (before > 0) {
+				int selStart = imeSelectionStart(nhandle);
+				imeReplace(nhandle, selStart - before, selStart, "");
+			}
+			return true;
+		}
+
+		/*@Override*/ public SurroundingText getSurroundingText(int beforeChars, int afterChars, int flags) {
+			Snippet snip = getSnippet();
+			int selStart = imeSelectionStart(nhandle);
+			int selEnd = imeSelectionEnd(nhandle);
+			// Expanding in Java characters is ok.
+			imeSetSnippet(nhandle, selStart - beforeChars, selEnd + afterChars);
+			return new SurroundingText(snip.snippet, imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd), imeToUTF16(nhandle, snip.offset));
+		}
+	}
+
+	private Snippet getSnippet() {
+		Snippet snip = new Snippet();
+		snip.snippet = imeSnippet(nhandle);
+		snip.offset = imeSnippetStart(nhandle);
+		return snip;
+	}
+
+	// Snippet is like android.view.inputmethod.SurroundingText but available for Android < 31.
+	private static class Snippet {
+		String snippet;
+		// offset of snippet into the entire editor content. It is in runes because we won't require
+		// Gio editors to keep track of UTF-16 offsets. The distinction won't matter in practice because IMEs only
+		// ever see snippets.
+		int offset;
+
+		// substringRunes returns the substring from start to end in runes. The resuls is
+		// truncated to the snippet.
+		String substringRunes(int start, int end) {
+			start -= this.offset;
+			end -= this.offset;
+			int runes = snippet.codePointCount(0, snippet.length());
+			if (start < 0) {
+				start = 0;
+			}
+			if (end < 0) {
+				end = 0;
+			}
+			if (start > runes) {
+				start = runes;
+			}
+			if (end > runes) {
+				end = runes;
+			}
+			return snippet.substring(
+				snippet.offsetByCodePoints(0, start),
+				snippet.offsetByCodePoints(0, end)
+			);
+		}
+	}
+
+	@Override public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+		return new AccessibilityNodeProvider() {
+			private final int[] screenOff = new int[2];
+
+			@Override public AccessibilityNodeInfo createAccessibilityNodeInfo(int viewId) {
+				AccessibilityNodeInfo info = null;
+				if (viewId == View.NO_ID) {
+					info = AccessibilityNodeInfo.obtain(GioView.this);
+					GioView.this.onInitializeAccessibilityNodeInfo(info);
+				} else {
+					info = AccessibilityNodeInfo.obtain(GioView.this, viewId);
+					info.setPackageName(getContext().getPackageName());
+					info.setVisibleToUser(true);
+				}
+				GioView.this.getLocationOnScreen(screenOff);
+				info = GioView.this.initializeAccessibilityNodeInfo(nhandle, viewId, screenOff[0], screenOff[1], info);
+				return info;
+			}
+
+			@Override public boolean performAction(int viewId, int action, Bundle arguments) {
+				if (viewId == View.NO_ID) {
+					return GioView.this.performAccessibilityAction(action, arguments);
+				}
+				switch (action) {
+				case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+					GioView.this.onA11yFocus(nhandle, viewId);
+					GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, viewId);
+					return true;
+				case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+					GioView.this.onClearA11yFocus(nhandle, viewId);
+					GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, viewId);
+					return true;
+				}
+				return false;
+			}
+		};
+	}
+}

+ 149 - 0
vendor/gioui.org/app/app.go

@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+import (
+	"image"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"gioui.org/io/input"
+	"gioui.org/layout"
+	"gioui.org/op"
+	"gioui.org/unit"
+)
+
+// extraArgs contains extra arguments to append to
+// os.Args. The arguments are separated with |.
+// Useful for running programs on mobiles where the
+// command line is not available.
+// Set with the go linker flag -X.
+var extraArgs string
+
+// ID is the app id exposed to the platform.
+//
+// On Android ID is the package property of AndroidManifest.xml,
+// on iOS ID is the CFBundleIdentifier of the app Info.plist,
+// on Wayland it is the toplevel app_id,
+// on X11 it is the X11 XClassHint.
+//
+// ID is set by the [gioui.org/cmd/gogio] tool or manually with the -X linker flag. For example,
+//
+//	go build -ldflags="-X 'gioui.org/app.ID=org.gioui.example.Kitchen'" .
+//
+// Note that ID is treated as a constant, and that changing it at runtime
+// is not supported. The default value of ID is filepath.Base(os.Args[0]).
+var ID = ""
+
+// A FrameEvent requests a new frame in the form of a list of
+// operations that describes the window content.
+type FrameEvent struct {
+	// Now is the current animation. Use Now instead of time.Now to
+	// synchronize animation and to avoid the time.Now call overhead.
+	Now time.Time
+	// Metric converts device independent dp and sp to device pixels.
+	Metric unit.Metric
+	// Size is the dimensions of the window.
+	Size image.Point
+	// Insets represent the space occupied by system decorations and controls.
+	Insets Insets
+	// Frame completes the FrameEvent by drawing the graphical operations
+	// from ops into the window.
+	Frame func(frame *op.Ops)
+	// Source is the interface between the window and widgets.
+	Source input.Source
+}
+
+// ViewEvent provides handles to the underlying window objects for the
+// current display protocol.
+type ViewEvent interface {
+	implementsViewEvent()
+	ImplementsEvent()
+	// Valid will return true when the ViewEvent does contains valid handles.
+	// If a window receives an invalid ViewEvent, it should deinitialize any
+	// state referring to handles from a previous ViewEvent.
+	Valid() bool
+}
+
+// Insets is the space taken up by
+// system decoration such as translucent
+// system bars and software keyboards.
+type Insets struct {
+	// Values are in pixels.
+	Top, Bottom, Left, Right unit.Dp
+}
+
+// NewContext is shorthand for
+//
+//	layout.Context{
+//	  Ops: ops,
+//	  Now: e.Now,
+//	  Source: e.Source,
+//	  Metric: e.Metric,
+//	  Constraints: layout.Exact(e.Size),
+//	}
+//
+// NewContext calls ops.Reset and adjusts ops for e.Insets.
+func NewContext(ops *op.Ops, e FrameEvent) layout.Context {
+	ops.Reset()
+
+	size := e.Size
+
+	if e.Insets != (Insets{}) {
+		left := e.Metric.Dp(e.Insets.Left)
+		top := e.Metric.Dp(e.Insets.Top)
+		op.Offset(image.Point{
+			X: left,
+			Y: top,
+		}).Add(ops)
+
+		size.X -= left + e.Metric.Dp(e.Insets.Right)
+		size.Y -= top + e.Metric.Dp(e.Insets.Bottom)
+	}
+
+	return layout.Context{
+		Ops:         ops,
+		Now:         e.Now,
+		Source:      e.Source,
+		Metric:      e.Metric,
+		Constraints: layout.Exact(size),
+	}
+}
+
+// DataDir returns a path to use for application-specific
+// configuration data.
+// On desktop systems, DataDir use os.UserConfigDir.
+// On iOS NSDocumentDirectory is queried.
+// For Android Context.getFilesDir is used.
+//
+// BUG: DataDir blocks on Android until init functions
+// have completed.
+func DataDir() (string, error) {
+	return dataDir()
+}
+
+// Main must be called last from the program main function.
+// On most platforms Main blocks forever, for Android and
+// iOS it returns immediately to give control of the main
+// thread back to the system.
+//
+// Calling Main is necessary because some operating systems
+// require control of the main thread of the program for
+// running windows.
+func Main() {
+	osMain()
+}
+
+func (FrameEvent) ImplementsEvent() {}
+
+func init() {
+	if extraArgs != "" {
+		args := strings.Split(extraArgs, "|")
+		os.Args = append(os.Args, args...)
+	}
+	if ID == "" {
+		ID = filepath.Base(os.Args[0])
+	}
+}

+ 135 - 0
vendor/gioui.org/app/d3d11_windows.go

@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+import (
+	"fmt"
+	"unsafe"
+
+	"gioui.org/gpu"
+	"gioui.org/internal/d3d11"
+)
+
+type d3d11Context struct {
+	win *window
+	dev *d3d11.Device
+	ctx *d3d11.DeviceContext
+
+	swchain       *d3d11.IDXGISwapChain
+	renderTarget  *d3d11.RenderTargetView
+	width, height int
+}
+
+const debugDirectX = false
+
+func init() {
+	drivers = append(drivers, gpuAPI{
+		priority: 1,
+		initializer: func(w *window) (context, error) {
+			hwnd, _, _ := w.HWND()
+			var flags uint32
+			if debugDirectX {
+				flags |= d3d11.CREATE_DEVICE_DEBUG
+			}
+			dev, ctx, _, err := d3d11.CreateDevice(
+				d3d11.DRIVER_TYPE_HARDWARE,
+				flags,
+			)
+			if err != nil {
+				return nil, fmt.Errorf("NewContext: %v", err)
+			}
+			swchain, err := d3d11.CreateSwapChain(dev, hwnd)
+			if err != nil {
+				d3d11.IUnknownRelease(unsafe.Pointer(ctx), ctx.Vtbl.Release)
+				d3d11.IUnknownRelease(unsafe.Pointer(dev), dev.Vtbl.Release)
+				return nil, err
+			}
+			return &d3d11Context{win: w, dev: dev, ctx: ctx, swchain: swchain}, nil
+		},
+	})
+}
+
+func (c *d3d11Context) API() gpu.API {
+	return gpu.Direct3D11{Device: unsafe.Pointer(c.dev)}
+}
+
+func (c *d3d11Context) RenderTarget() (gpu.RenderTarget, error) {
+	return gpu.Direct3D11RenderTarget{
+		RenderTarget: unsafe.Pointer(c.renderTarget),
+	}, nil
+}
+
+func (c *d3d11Context) Present() error {
+	return wrapErr(c.swchain.Present(1, 0))
+}
+
+func wrapErr(err error) error {
+	if err, ok := err.(d3d11.ErrorCode); ok {
+		switch err.Code {
+		case d3d11.DXGI_STATUS_OCCLUDED:
+			// Ignore
+			return nil
+		case d3d11.DXGI_ERROR_DEVICE_RESET, d3d11.DXGI_ERROR_DEVICE_REMOVED, d3d11.D3DDDIERR_DEVICEREMOVED:
+			return gpu.ErrDeviceLost
+		}
+	}
+	return err
+}
+
+func (c *d3d11Context) Refresh() error {
+	var width, height int
+	_, width, height = c.win.HWND()
+	if c.renderTarget != nil && width == c.width && height == c.height {
+		return nil
+	}
+	c.releaseFBO()
+	if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
+		return wrapErr(err)
+	}
+	c.width = width
+	c.height = height
+
+	backBuffer, err := c.swchain.GetBuffer(0, &d3d11.IID_Texture2D)
+	if err != nil {
+		return err
+	}
+	texture := (*d3d11.Resource)(unsafe.Pointer(backBuffer))
+	renderTarget, err := c.dev.CreateRenderTargetView(texture)
+	d3d11.IUnknownRelease(unsafe.Pointer(backBuffer), backBuffer.Vtbl.Release)
+	if err != nil {
+		return err
+	}
+	c.renderTarget = renderTarget
+	return nil
+}
+
+func (c *d3d11Context) Lock() error {
+	c.ctx.OMSetRenderTargets(c.renderTarget, nil)
+	return nil
+}
+
+func (c *d3d11Context) Unlock() {}
+
+func (c *d3d11Context) Release() {
+	c.releaseFBO()
+	if c.swchain != nil {
+		d3d11.IUnknownRelease(unsafe.Pointer(c.swchain), c.swchain.Vtbl.Release)
+	}
+	if c.ctx != nil {
+		d3d11.IUnknownRelease(unsafe.Pointer(c.ctx), c.ctx.Vtbl.Release)
+	}
+	if c.dev != nil {
+		d3d11.IUnknownRelease(unsafe.Pointer(c.dev), c.dev.Vtbl.Release)
+	}
+	*c = d3d11Context{}
+	if debugDirectX {
+		d3d11.ReportLiveObjects()
+	}
+}
+
+func (c *d3d11Context) releaseFBO() {
+	if c.renderTarget != nil {
+		d3d11.IUnknownRelease(unsafe.Pointer(c.renderTarget), c.renderTarget.Vtbl.Release)
+		c.renderTarget = nil
+	}
+}

+ 12 - 0
vendor/gioui.org/app/datadir.go

@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build !android
+// +build !android
+
+package app
+
+import "os"
+
+func dataDir() (string, error) {
+	return os.UserConfigDir()
+}

+ 66 - 0
vendor/gioui.org/app/doc.go

@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+/*
+Package app provides a platform-independent interface to operating system
+functionality for running graphical user interfaces.
+
+See https://gioui.org for instructions to set up and run Gio programs.
+
+# Windows
+
+A Window is run by calling its Event method in a loop. The first time a
+method on Window is called, a new GUI window is created and shown. On mobile
+platforms or when Gio is embedded in another project, Window merely connects
+with a previously created GUI window.
+
+The most important event is [FrameEvent] that prompts an update of the window
+contents.
+
+For example:
+
+	w := new(app.Window)
+	for {
+		e := w.Event()
+		if e, ok := e.(app.FrameEvent); ok {
+			ops.Reset()
+			// Add operations to ops.
+			...
+			// Completely replace the window contents and state.
+			e.Frame(ops)
+		}
+	}
+
+A program must keep receiving events from the event channel until
+[DestroyEvent] is received.
+
+# Main
+
+The Main function must be called from a program's main function, to hand over
+control of the main thread to operating systems that need it.
+
+Because Main is also blocking on some platforms, the event loop of a Window must run in a goroutine.
+
+For example, to display a blank but otherwise functional window:
+
+	package main
+
+	import "gioui.org/app"
+
+	func main() {
+		go func() {
+			w := app.NewWindow()
+			for {
+				w.Event()
+			}
+		}()
+		app.Main()
+	}
+
+# Permissions
+
+The packages under gioui.org/app/permission should be imported
+by a Gio program or by one of its dependencies to indicate that specific
+operating-system permissions are required.  Please see documentation for
+package gioui.org/app/permission for more information.
+*/
+package app

+ 66 - 0
vendor/gioui.org/app/egl_android.go

@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build !noopengl
+
+package app
+
+/*
+#include <android/native_window_jni.h>
+#include <EGL/egl.h>
+*/
+import "C"
+
+import (
+	"unsafe"
+
+	"gioui.org/internal/egl"
+)
+
+type androidContext struct {
+	win     *window
+	eglSurf egl.NativeWindowType
+	*egl.Context
+}
+
+func init() {
+	newAndroidGLESContext = func(w *window) (context, error) {
+		ctx, err := egl.NewContext(nil)
+		if err != nil {
+			return nil, err
+		}
+		return &androidContext{win: w, Context: ctx}, nil
+	}
+}
+
+func (c *androidContext) Release() {
+	if c.Context != nil {
+		c.Context.Release()
+		c.Context = nil
+	}
+}
+
+func (c *androidContext) Refresh() error {
+	c.Context.ReleaseSurface()
+	if err := c.win.setVisual(c.Context.VisualID()); err != nil {
+		return err
+	}
+	win, _, _ := c.win.nativeWindow()
+	c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
+	return nil
+}
+
+func (c *androidContext) Lock() error {
+	// The Android emulator creates a broken surface if it is not
+	// created on the same thread as the context is made current.
+	if c.eglSurf != nil {
+		if err := c.Context.CreateSurface(c.eglSurf); err != nil {
+			return err
+		}
+		c.eglSurf = nil
+	}
+	return c.Context.MakeCurrent()
+}
+
+func (c *androidContext) Unlock() {
+	c.Context.ReleaseCurrent()
+}

+ 91 - 0
vendor/gioui.org/app/egl_wayland.go

@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build ((linux && !android) || freebsd) && !nowayland && !noopengl
+// +build linux,!android freebsd
+// +build !nowayland
+// +build !noopengl
+
+package app
+
+import (
+	"errors"
+	"unsafe"
+
+	"gioui.org/internal/egl"
+)
+
+/*
+#cgo linux pkg-config: egl wayland-egl
+#cgo freebsd openbsd LDFLAGS: -lwayland-egl
+#cgo CFLAGS: -DEGL_NO_X11
+
+#include <EGL/egl.h>
+#include <wayland-client.h>
+#include <wayland-egl.h>
+*/
+import "C"
+
+type wlContext struct {
+	win *window
+	*egl.Context
+	eglWin *C.struct_wl_egl_window
+}
+
+func init() {
+	newWaylandEGLContext = func(w *window) (context, error) {
+		disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
+		ctx, err := egl.NewContext(disp)
+		if err != nil {
+			return nil, err
+		}
+		return &wlContext{Context: ctx, win: w}, nil
+	}
+}
+
+func (c *wlContext) Release() {
+	if c.Context != nil {
+		c.Context.Release()
+		c.Context = nil
+	}
+	if c.eglWin != nil {
+		C.wl_egl_window_destroy(c.eglWin)
+		c.eglWin = nil
+	}
+}
+
+func (c *wlContext) Refresh() error {
+	c.Context.ReleaseSurface()
+	if c.eglWin != nil {
+		C.wl_egl_window_destroy(c.eglWin)
+		c.eglWin = nil
+	}
+	surf, width, height := c.win.surface()
+	if surf == nil {
+		return errors.New("wayland: no surface")
+	}
+	eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
+	if eglWin == nil {
+		return errors.New("wayland: wl_egl_window_create failed")
+	}
+	c.eglWin = eglWin
+	eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
+	if err := c.Context.CreateSurface(eglSurf); err != nil {
+		return err
+	}
+	if err := c.Context.MakeCurrent(); err != nil {
+		return err
+	}
+	defer c.Context.ReleaseCurrent()
+	// We're in charge of the frame callbacks, don't let eglSwapBuffers
+	// wait for callbacks that may never arrive.
+	c.Context.EnableVSync(false)
+	return nil
+}
+
+func (c *wlContext) Lock() error {
+	return c.Context.MakeCurrent()
+}
+
+func (c *wlContext) Unlock() {
+	c.Context.ReleaseCurrent()
+}

+ 59 - 0
vendor/gioui.org/app/egl_windows.go

@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build !noopengl
+
+package app
+
+import (
+	"gioui.org/internal/egl"
+)
+
+type glContext struct {
+	win *window
+	*egl.Context
+}
+
+func init() {
+	drivers = append(drivers, gpuAPI{
+		priority: 2,
+		initializer: func(w *window) (context, error) {
+			disp := egl.NativeDisplayType(w.HDC())
+			ctx, err := egl.NewContext(disp)
+			if err != nil {
+				return nil, err
+			}
+			win, _, _ := w.HWND()
+			eglSurf := egl.NativeWindowType(win)
+			if err := ctx.CreateSurface(eglSurf); err != nil {
+				ctx.Release()
+				return nil, err
+			}
+			if err := ctx.MakeCurrent(); err != nil {
+				ctx.Release()
+				return nil, err
+			}
+			defer ctx.ReleaseCurrent()
+			ctx.EnableVSync(true)
+			return &glContext{win: w, Context: ctx}, nil
+		},
+	})
+}
+
+func (c *glContext) Release() {
+	if c.Context != nil {
+		c.Context.Release()
+		c.Context = nil
+	}
+}
+
+func (c *glContext) Refresh() error {
+	return nil
+}
+
+func (c *glContext) Lock() error {
+	return c.Context.MakeCurrent()
+}
+
+func (c *glContext) Unlock() {
+	c.Context.ReleaseCurrent()
+}

+ 61 - 0
vendor/gioui.org/app/egl_x11.go

@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build ((linux && !android) || freebsd || openbsd) && !nox11 && !noopengl
+// +build linux,!android freebsd openbsd
+// +build !nox11
+// +build !noopengl
+
+package app
+
+import (
+	"unsafe"
+
+	"gioui.org/internal/egl"
+)
+
+type x11Context struct {
+	win *x11Window
+	*egl.Context
+}
+
+func init() {
+	newX11EGLContext = func(w *x11Window) (context, error) {
+		disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
+		ctx, err := egl.NewContext(disp)
+		if err != nil {
+			return nil, err
+		}
+		win, _, _ := w.window()
+		eglSurf := egl.NativeWindowType(uintptr(win))
+		if err := ctx.CreateSurface(eglSurf); err != nil {
+			ctx.Release()
+			return nil, err
+		}
+		if err := ctx.MakeCurrent(); err != nil {
+			ctx.Release()
+			return nil, err
+		}
+		defer ctx.ReleaseCurrent()
+		ctx.EnableVSync(true)
+		return &x11Context{win: w, Context: ctx}, nil
+	}
+}
+
+func (c *x11Context) Release() {
+	if c.Context != nil {
+		c.Context.Release()
+		c.Context = nil
+	}
+}
+
+func (c *x11Context) Refresh() error {
+	return nil
+}
+
+func (c *x11Context) Lock() error {
+	return c.Context.MakeCurrent()
+}
+
+func (c *x11Context) Unlock() {
+	c.Context.ReleaseCurrent()
+}

+ 6 - 0
vendor/gioui.org/app/framework_ios.h

@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+#include <UIKit/UIKit.h>
+
+@interface GioViewController : UIViewController
+@end

+ 148 - 0
vendor/gioui.org/app/gl_ios.go

@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build darwin && ios && nometal
+// +build darwin,ios,nometal
+
+package app
+
+/*
+@import UIKit;
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <OpenGLES/ES2/gl.h>
+#include <OpenGLES/ES2/glext.h>
+
+__attribute__ ((visibility ("hidden"))) int gio_renderbufferStorage(CFTypeRef ctx, CFTypeRef layer, GLenum buffer);
+__attribute__ ((visibility ("hidden"))) int gio_presentRenderbuffer(CFTypeRef ctx, GLenum buffer);
+__attribute__ ((visibility ("hidden"))) int gio_makeCurrent(CFTypeRef ctx);
+__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createContext(void);
+__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLLayer(void);
+
+static CFTypeRef getViewLayer(CFTypeRef viewRef) {
+	@autoreleasepool {
+		UIView *view = (__bridge UIView *)viewRef;
+		return CFBridgingRetain(view.layer);
+	}
+}
+
+*/
+import "C"
+
+import (
+	"errors"
+	"fmt"
+	"runtime"
+
+	"gioui.org/gpu"
+	"gioui.org/internal/gl"
+)
+
+type context struct {
+	owner       *window
+	c           *gl.Functions
+	ctx         C.CFTypeRef
+	layer       C.CFTypeRef
+	init        bool
+	frameBuffer gl.Framebuffer
+	colorBuffer gl.Renderbuffer
+}
+
+func newContext(w *window) (*context, error) {
+	ctx := C.gio_createContext()
+	if ctx == 0 {
+		return nil, fmt.Errorf("failed to create EAGLContext")
+	}
+	api := contextAPI()
+	f, err := gl.NewFunctions(api.Context, api.ES)
+	if err != nil {
+		return nil, err
+	}
+	c := &context{
+		ctx:   ctx,
+		owner: w,
+		layer: C.getViewLayer(w.contextView()),
+		c:     f,
+	}
+	return c, nil
+}
+
+func contextAPI() gpu.OpenGL {
+	return gpu.OpenGL{}
+}
+
+func (c *context) RenderTarget() gpu.RenderTarget {
+	return gpu.OpenGLRenderTarget(c.frameBuffer)
+}
+
+func (c *context) API() gpu.API {
+	return contextAPI()
+}
+
+func (c *context) Release() {
+	if c.ctx == 0 {
+		return
+	}
+	C.gio_renderbufferStorage(c.ctx, 0, C.GLenum(gl.RENDERBUFFER))
+	c.c.DeleteFramebuffer(c.frameBuffer)
+	c.c.DeleteRenderbuffer(c.colorBuffer)
+	C.gio_makeCurrent(0)
+	C.CFRelease(c.ctx)
+	c.ctx = 0
+}
+
+func (c *context) Present() error {
+	if c.layer == 0 {
+		panic("context is not active")
+	}
+	c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
+	if C.gio_presentRenderbuffer(c.ctx, C.GLenum(gl.RENDERBUFFER)) == 0 {
+		return errors.New("presentRenderBuffer failed")
+	}
+	return nil
+}
+
+func (c *context) Lock() error {
+	// OpenGL contexts are implicit and thread-local. Lock the OS thread.
+	runtime.LockOSThread()
+
+	if C.gio_makeCurrent(c.ctx) == 0 {
+		return errors.New("[EAGLContext setCurrentContext] failed")
+	}
+	return nil
+}
+
+func (c *context) Unlock() {
+	C.gio_makeCurrent(0)
+}
+
+func (c *context) Refresh() error {
+	if C.gio_makeCurrent(c.ctx) == 0 {
+		return errors.New("[EAGLContext setCurrentContext] failed")
+	}
+	if !c.init {
+		c.init = true
+		c.frameBuffer = c.c.CreateFramebuffer()
+		c.colorBuffer = c.c.CreateRenderbuffer()
+	}
+	if !c.owner.visible {
+		// Make sure any in-flight GL commands are complete.
+		c.c.Finish()
+		return nil
+	}
+	currentRB := gl.Renderbuffer{uint(c.c.GetInteger(gl.RENDERBUFFER_BINDING))}
+	c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
+	if C.gio_renderbufferStorage(c.ctx, c.layer, C.GLenum(gl.RENDERBUFFER)) == 0 {
+		return errors.New("renderbufferStorage failed")
+	}
+	c.c.BindRenderbuffer(gl.RENDERBUFFER, currentRB)
+	c.c.BindFramebuffer(gl.FRAMEBUFFER, c.frameBuffer)
+	c.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, c.colorBuffer)
+	if st := c.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
+		return fmt.Errorf("framebuffer incomplete, status: %#x\n", st)
+	}
+	return nil
+}
+
+func (w *window) NewContext() (Context, error) {
+	return newContext(w)
+}

+ 47 - 0
vendor/gioui.org/app/gl_ios.m

@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build darwin,ios,nometal
+
+@import UIKit;
+@import OpenGLES;
+
+#include "_cgo_export.h"
+
+Class gio_layerClass(void) {
+	return [CAEAGLLayer class];
+}
+
+int gio_renderbufferStorage(CFTypeRef ctxRef, CFTypeRef layerRef, GLenum buffer) {
+	EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
+	CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
+	return (int)[ctx renderbufferStorage:buffer fromDrawable:layer];
+}
+
+int gio_presentRenderbuffer(CFTypeRef ctxRef, GLenum buffer) {
+	EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
+	return (int)[ctx presentRenderbuffer:buffer];
+}
+
+int gio_makeCurrent(CFTypeRef ctxRef) {
+	EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
+	return (int)[EAGLContext setCurrentContext:ctx];
+}
+
+CFTypeRef gio_createContext(void) {
+	EAGLContext *ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
+	if (ctx == nil) {
+		return nil;
+	}
+	return CFBridgingRetain(ctx);
+}
+
+CFTypeRef gio_createGLLayer(void) {
+	CAEAGLLayer *layer = [[CAEAGLLayer layer] init];
+	if (layer == nil) {
+		return nil;
+	}
+	layer.drawableProperties = @{kEAGLDrawablePropertyColorFormat: kEAGLColorFormatSRGBA8};
+	layer.opaque = YES;
+	layer.anchorPoint = CGPointMake(0, 0);
+	return CFBridgingRetain(layer);
+}

+ 79 - 0
vendor/gioui.org/app/gl_js.go

@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+import (
+	"errors"
+	"syscall/js"
+
+	"gioui.org/gpu"
+	"gioui.org/internal/gl"
+)
+
+type glContext struct {
+	ctx js.Value
+	cnv js.Value
+	w   *window
+}
+
+func newContext(w *window) (*glContext, error) {
+	args := map[string]interface{}{
+		// Enable low latency rendering.
+		// See https://developers.google.com/web/updates/2019/05/desynchronized.
+		"desynchronized":        true,
+		"preserveDrawingBuffer": true,
+	}
+	ctx := w.cnv.Call("getContext", "webgl2", args)
+	if ctx.IsNull() {
+		ctx = w.cnv.Call("getContext", "webgl", args)
+	}
+	if ctx.IsNull() {
+		return nil, errors.New("app: webgl is not supported")
+	}
+	c := &glContext{
+		ctx: ctx,
+		cnv: w.cnv,
+		w:   w,
+	}
+	return c, nil
+}
+
+func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
+	if c.w.contextStatus != contextStatusOkay {
+		return nil, gpu.ErrDeviceLost
+	}
+	return gpu.OpenGLRenderTarget{}, nil
+}
+
+func (c *glContext) API() gpu.API {
+	return gpu.OpenGL{Context: gl.Context(c.ctx)}
+}
+
+func (c *glContext) Release() {
+}
+
+func (c *glContext) Present() error {
+	return nil
+}
+
+func (c *glContext) Lock() error {
+	return nil
+}
+
+func (c *glContext) Unlock() {}
+
+func (c *glContext) Refresh() error {
+	switch c.w.contextStatus {
+	case contextStatusLost:
+		return errOutOfDate
+	case contextStatusRestored:
+		c.w.contextStatus = contextStatusOkay
+		return gpu.ErrDeviceLost
+	default:
+		return nil
+	}
+}
+
+func (w *window) NewContext() (context, error) {
+	return newContext(w)
+}

+ 124 - 0
vendor/gioui.org/app/gl_macos.go

@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build darwin && !ios && nometal
+// +build darwin,!ios,nometal
+
+package app
+
+import (
+	"errors"
+	"runtime"
+
+	"unsafe"
+
+	"gioui.org/gpu"
+	"gioui.org/internal/gl"
+)
+
+/*
+#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -xobjective-c -fobjc-arc
+#cgo LDFLAGS: -framework OpenGL
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <AppKit/AppKit.h>
+#include <dlfcn.h>
+
+__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLContext(void);
+__attribute__ ((visibility ("hidden"))) void gio_setContextView(CFTypeRef ctx, CFTypeRef view);
+__attribute__ ((visibility ("hidden"))) void gio_makeCurrentContext(CFTypeRef ctx);
+__attribute__ ((visibility ("hidden"))) void gio_updateContext(CFTypeRef ctx);
+__attribute__ ((visibility ("hidden"))) void gio_flushContextBuffer(CFTypeRef ctx);
+__attribute__ ((visibility ("hidden"))) void gio_clearCurrentContext(void);
+__attribute__ ((visibility ("hidden"))) void gio_lockContext(CFTypeRef ctxRef);
+__attribute__ ((visibility ("hidden"))) void gio_unlockContext(CFTypeRef ctxRef);
+
+typedef void (*PFN_glFlush)(void);
+
+static void glFlush(PFN_glFlush f) {
+	f();
+}
+*/
+import "C"
+
+type glContext struct {
+	c    *gl.Functions
+	ctx  C.CFTypeRef
+	view C.CFTypeRef
+
+	glFlush C.PFN_glFlush
+}
+
+func newContext(w *window) (*glContext, error) {
+	clib := C.CString("/System/Library/Frameworks/OpenGL.framework/OpenGL")
+	defer C.free(unsafe.Pointer(clib))
+	lib, err := C.dlopen(clib, C.RTLD_NOW|C.RTLD_LOCAL)
+	if err != nil {
+		return nil, err
+	}
+	csym := C.CString("glFlush")
+	defer C.free(unsafe.Pointer(csym))
+	glFlush := C.PFN_glFlush(C.dlsym(lib, csym))
+	if glFlush == nil {
+		return nil, errors.New("gl: missing symbol glFlush in the OpenGL framework")
+	}
+	view := w.contextView()
+	ctx := C.gio_createGLContext()
+	if ctx == 0 {
+		return nil, errors.New("gl: failed to create NSOpenGLContext")
+	}
+	C.gio_setContextView(ctx, view)
+	c := &glContext{
+		ctx:     ctx,
+		view:    view,
+		glFlush: glFlush,
+	}
+	return c, nil
+}
+
+func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
+	return gpu.OpenGLRenderTarget{}, nil
+}
+
+func (c *glContext) API() gpu.API {
+	return gpu.OpenGL{}
+}
+
+func (c *glContext) Release() {
+	if c.ctx != 0 {
+		C.gio_clearCurrentContext()
+		C.CFRelease(c.ctx)
+		c.ctx = 0
+	}
+}
+
+func (c *glContext) Present() error {
+	// Assume the caller already locked the context.
+	C.glFlush(c.glFlush)
+	return nil
+}
+
+func (c *glContext) Lock() error {
+	// OpenGL contexts are implicit and thread-local. Lock the OS thread.
+	runtime.LockOSThread()
+
+	C.gio_lockContext(c.ctx)
+	C.gio_makeCurrentContext(c.ctx)
+	return nil
+}
+
+func (c *glContext) Unlock() {
+	C.gio_clearCurrentContext()
+	C.gio_unlockContext(c.ctx)
+}
+
+func (c *glContext) Refresh() error {
+	c.Lock()
+	defer c.Unlock()
+	C.gio_updateContext(c.ctx)
+	return nil
+}
+
+func (w *window) NewContext() (context, error) {
+	return newContext(w)
+}

+ 73 - 0
vendor/gioui.org/app/gl_macos.m

@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build darwin,!ios,nometal
+
+#import <AppKit/AppKit.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <OpenGL/OpenGL.h>
+#include "_cgo_export.h"
+
+CALayer *gio_layerFactory(BOOL presentWithTrans) {
+	@autoreleasepool {
+		return [CALayer layer];
+	}
+}
+
+CFTypeRef gio_createGLContext(void) {
+	@autoreleasepool {
+		NSOpenGLPixelFormatAttribute attr[] = {
+			NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+			NSOpenGLPFAColorSize,     24,
+			NSOpenGLPFAAccelerated,
+			// Opt-in to automatic GPU switching. CGL-only property.
+			kCGLPFASupportsAutomaticGraphicsSwitching,
+			NSOpenGLPFAAllowOfflineRenderers,
+			0
+		};
+		NSOpenGLPixelFormat *pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
+
+		NSOpenGLContext *ctx = [[NSOpenGLContext alloc] initWithFormat:pixFormat shareContext: nil];
+		return CFBridgingRetain(ctx);
+	}
+}
+
+void gio_setContextView(CFTypeRef ctxRef, CFTypeRef viewRef) {
+	NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
+	NSView *view = (__bridge NSView *)viewRef;
+	[view setWantsBestResolutionOpenGLSurface:YES];
+	[ctx setView:view];
+}
+
+void gio_clearCurrentContext(void) {
+	@autoreleasepool {
+		[NSOpenGLContext clearCurrentContext];
+	}
+}
+
+void gio_updateContext(CFTypeRef ctxRef) {
+	@autoreleasepool {
+		NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
+		[ctx update];
+	}
+}
+
+void gio_makeCurrentContext(CFTypeRef ctxRef) {
+	@autoreleasepool {
+		NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
+		[ctx makeCurrentContext];
+	}
+}
+
+void gio_lockContext(CFTypeRef ctxRef) {
+	@autoreleasepool {
+		NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
+		CGLLockContext([ctx CGLContextObj]);
+	}
+}
+
+void gio_unlockContext(CFTypeRef ctxRef) {
+	@autoreleasepool {
+		NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
+		CGLUnlockContext([ctx CGLContextObj]);
+	}
+}

+ 118 - 0
vendor/gioui.org/app/ime.go

@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+package app
+
+import (
+	"unicode"
+	"unicode/utf16"
+
+	"gioui.org/io/input"
+	"gioui.org/io/key"
+)
+
+type editorState struct {
+	input.EditorState
+	compose key.Range
+}
+
+func (e *editorState) Replace(r key.Range, text string) {
+	if r.Start > r.End {
+		r.Start, r.End = r.End, r.Start
+	}
+	runes := []rune(text)
+	newEnd := r.Start + len(runes)
+	adjust := func(pos int) int {
+		switch {
+		case newEnd < pos && pos <= r.End:
+			return newEnd
+		case r.End < pos:
+			diff := newEnd - r.End
+			return pos + diff
+		}
+		return pos
+	}
+	e.Selection.Start = adjust(e.Selection.Start)
+	e.Selection.End = adjust(e.Selection.End)
+	if e.compose.Start != -1 {
+		e.compose.Start = adjust(e.compose.Start)
+		e.compose.End = adjust(e.compose.End)
+	}
+	s := e.Snippet
+	if r.End < s.Start || r.Start > s.End {
+		// Discard snippet if it doesn't overlap with replacement.
+		s = key.Snippet{
+			Range: key.Range{
+				Start: r.Start,
+				End:   r.Start,
+			},
+		}
+	}
+	var newSnippet []rune
+	snippet := []rune(s.Text)
+	// Append first part of existing snippet.
+	if end := r.Start - s.Start; end > 0 {
+		newSnippet = append(newSnippet, snippet[:end]...)
+	}
+	// Append replacement.
+	newSnippet = append(newSnippet, runes...)
+	// Append last part of existing snippet.
+	if start := r.End; start < s.End {
+		newSnippet = append(newSnippet, snippet[start-s.Start:]...)
+	}
+	// Adjust snippet range to include replacement.
+	if r.Start < s.Start {
+		s.Start = r.Start
+	}
+	s.End = s.Start + len(newSnippet)
+	s.Text = string(newSnippet)
+	e.Snippet = s
+}
+
+// UTF16Index converts the given index in runes into an index in utf16 characters.
+func (e *editorState) UTF16Index(runes int) int {
+	if runes == -1 {
+		return -1
+	}
+	if runes < e.Snippet.Start {
+		// Assume runes before sippet are one UTF-16 character each.
+		return runes
+	}
+	chars := e.Snippet.Start
+	runes -= e.Snippet.Start
+	for _, r := range e.Snippet.Text {
+		if runes == 0 {
+			break
+		}
+		runes--
+		chars++
+		if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
+			chars++
+		}
+	}
+	// Assume runes after snippets are one UTF-16 character each.
+	return chars + runes
+}
+
+// RunesIndex converts the given index in utf16 characters to an index in runes.
+func (e *editorState) RunesIndex(chars int) int {
+	if chars == -1 {
+		return -1
+	}
+	if chars < e.Snippet.Start {
+		// Assume runes before offset are one UTF-16 character each.
+		return chars
+	}
+	runes := e.Snippet.Start
+	chars -= e.Snippet.Start
+	for _, r := range e.Snippet.Text {
+		if chars == 0 {
+			break
+		}
+		chars--
+		runes++
+		if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
+			chars--
+		}
+	}
+	// Assume runes after snippets are one UTF-16 character each.
+	return runes + chars
+}

+ 858 - 0
vendor/gioui.org/app/internal/windows/windows.go

@@ -0,0 +1,858 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build windows
+// +build windows
+
+package windows
+
+import (
+	"fmt"
+	"runtime"
+	"time"
+	"unicode/utf16"
+	"unsafe"
+
+	syscall "golang.org/x/sys/windows"
+)
+
+type CompositionForm struct {
+	dwStyle      uint32
+	ptCurrentPos Point
+	rcArea       Rect
+}
+
+type CandidateForm struct {
+	dwIndex      uint32
+	dwStyle      uint32
+	ptCurrentPos Point
+	rcArea       Rect
+}
+
+type Rect struct {
+	Left, Top, Right, Bottom int32
+}
+
+type WndClassEx struct {
+	CbSize        uint32
+	Style         uint32
+	LpfnWndProc   uintptr
+	CnClsExtra    int32
+	CbWndExtra    int32
+	HInstance     syscall.Handle
+	HIcon         syscall.Handle
+	HCursor       syscall.Handle
+	HbrBackground syscall.Handle
+	LpszMenuName  *uint16
+	LpszClassName *uint16
+	HIconSm       syscall.Handle
+}
+
+type Margins struct {
+	CxLeftWidth    int32
+	CxRightWidth   int32
+	CyTopHeight    int32
+	CyBottomHeight int32
+}
+
+type Msg struct {
+	Hwnd     syscall.Handle
+	Message  uint32
+	WParam   uintptr
+	LParam   uintptr
+	Time     uint32
+	Pt       Point
+	LPrivate uint32
+}
+
+type Point struct {
+	X, Y int32
+}
+
+type MinMaxInfo struct {
+	PtReserved     Point
+	PtMaxSize      Point
+	PtMaxPosition  Point
+	PtMinTrackSize Point
+	PtMaxTrackSize Point
+}
+
+type NCCalcSizeParams struct {
+	Rgrc  [3]Rect
+	LpPos *WindowPos
+}
+
+type WindowPos struct {
+	HWND            syscall.Handle
+	HWNDInsertAfter syscall.Handle
+	x               int32
+	y               int32
+	cx              int32
+	cy              int32
+	flags           uint32
+}
+
+type WindowPlacement struct {
+	length           uint32
+	flags            uint32
+	showCmd          uint32
+	ptMinPosition    Point
+	ptMaxPosition    Point
+	rcNormalPosition Rect
+	rcDevice         Rect
+}
+
+type MonitorInfo struct {
+	cbSize   uint32
+	Monitor  Rect
+	WorkArea Rect
+	Flags    uint32
+}
+
+const (
+	TRUE = 1
+
+	CPS_CANCEL = 0x0004
+
+	CS_HREDRAW     = 0x0002
+	CS_INSERTCHAR  = 0x2000
+	CS_NOMOVECARET = 0x4000
+	CS_VREDRAW     = 0x0001
+	CS_OWNDC       = 0x0020
+
+	CW_USEDEFAULT = -2147483648
+
+	GWL_STYLE = ^(uintptr(16) - 1) // -16
+
+	GCS_COMPSTR       = 0x0008
+	GCS_COMPREADSTR   = 0x0001
+	GCS_CURSORPOS     = 0x0080
+	GCS_DELTASTART    = 0x0100
+	GCS_RESULTREADSTR = 0x0200
+	GCS_RESULTSTR     = 0x0800
+
+	CFS_POINT        = 0x0002
+	CFS_CANDIDATEPOS = 0x0040
+
+	HWND_TOPMOST = ^(uint32(1) - 1) // -1
+
+	HTCAPTION     = 2
+	HTCLIENT      = 1
+	HTLEFT        = 10
+	HTRIGHT       = 11
+	HTTOP         = 12
+	HTTOPLEFT     = 13
+	HTTOPRIGHT    = 14
+	HTBOTTOM      = 15
+	HTBOTTOMLEFT  = 16
+	HTBOTTOMRIGHT = 17
+
+	IDC_APPSTARTING = 32650 // Standard arrow and small hourglass
+	IDC_ARROW       = 32512 // Standard arrow
+	IDC_CROSS       = 32515 // Crosshair
+	IDC_HAND        = 32649 // Hand
+	IDC_HELP        = 32651 // Arrow and question mark
+	IDC_IBEAM       = 32513 // I-beam
+	IDC_NO          = 32648 // Slashed circle
+	IDC_SIZEALL     = 32646 // Four-pointed arrow pointing north, south, east, and west
+	IDC_SIZENESW    = 32643 // Double-pointed arrow pointing northeast and southwest
+	IDC_SIZENS      = 32645 // Double-pointed arrow pointing north and south
+	IDC_SIZENWSE    = 32642 // Double-pointed arrow pointing northwest and southeast
+	IDC_SIZEWE      = 32644 // Double-pointed arrow pointing west and east
+	IDC_UPARROW     = 32516 // Vertical arrow
+	IDC_WAIT        = 32514 // Hour
+
+	INFINITE = 0xFFFFFFFF
+
+	LOGPIXELSX = 88
+
+	MDT_EFFECTIVE_DPI = 0
+
+	MONITOR_DEFAULTTOPRIMARY = 1
+
+	NI_COMPOSITIONSTR = 0x0015
+
+	SIZE_MAXIMIZED = 2
+	SIZE_MINIMIZED = 1
+	SIZE_RESTORED  = 0
+
+	SCS_SETSTR = GCS_COMPREADSTR | GCS_COMPSTR
+
+	SM_CXSIZEFRAME = 32
+	SM_CYSIZEFRAME = 33
+
+	SW_SHOWDEFAULT   = 10
+	SW_SHOWMINIMIZED = 2
+	SW_SHOWMAXIMIZED = 3
+	SW_SHOWNORMAL    = 1
+	SW_SHOW          = 5
+
+	SWP_FRAMECHANGED  = 0x0020
+	SWP_NOMOVE        = 0x0002
+	SWP_NOOWNERZORDER = 0x0200
+	SWP_NOSIZE        = 0x0001
+	SWP_NOZORDER      = 0x0004
+	SWP_SHOWWINDOW    = 0x0040
+
+	USER_TIMER_MINIMUM = 0x0000000A
+
+	VK_CONTROL = 0x11
+	VK_LWIN    = 0x5B
+	VK_MENU    = 0x12
+	VK_RWIN    = 0x5C
+	VK_SHIFT   = 0x10
+
+	VK_BACK   = 0x08
+	VK_DELETE = 0x2e
+	VK_DOWN   = 0x28
+	VK_END    = 0x23
+	VK_ESCAPE = 0x1b
+	VK_HOME   = 0x24
+	VK_LEFT   = 0x25
+	VK_NEXT   = 0x22
+	VK_PRIOR  = 0x21
+	VK_RIGHT  = 0x27
+	VK_RETURN = 0x0d
+	VK_SPACE  = 0x20
+	VK_TAB    = 0x09
+	VK_UP     = 0x26
+
+	VK_F1  = 0x70
+	VK_F2  = 0x71
+	VK_F3  = 0x72
+	VK_F4  = 0x73
+	VK_F5  = 0x74
+	VK_F6  = 0x75
+	VK_F7  = 0x76
+	VK_F8  = 0x77
+	VK_F9  = 0x78
+	VK_F10 = 0x79
+	VK_F11 = 0x7A
+	VK_F12 = 0x7B
+
+	VK_OEM_1      = 0xba
+	VK_OEM_PLUS   = 0xbb
+	VK_OEM_COMMA  = 0xbc
+	VK_OEM_MINUS  = 0xbd
+	VK_OEM_PERIOD = 0xbe
+	VK_OEM_2      = 0xbf
+	VK_OEM_3      = 0xc0
+	VK_OEM_4      = 0xdb
+	VK_OEM_5      = 0xdc
+	VK_OEM_6      = 0xdd
+	VK_OEM_7      = 0xde
+	VK_OEM_102    = 0xe2
+
+	UNICODE_NOCHAR = 65535
+
+	WM_CANCELMODE           = 0x001F
+	WM_CHAR                 = 0x0102
+	WM_CLOSE                = 0x0010
+	WM_CREATE               = 0x0001
+	WM_DPICHANGED           = 0x02E0
+	WM_DESTROY              = 0x0002
+	WM_ERASEBKGND           = 0x0014
+	WM_GETMINMAXINFO        = 0x0024
+	WM_IME_COMPOSITION      = 0x010F
+	WM_IME_ENDCOMPOSITION   = 0x010E
+	WM_IME_STARTCOMPOSITION = 0x010D
+	WM_KEYDOWN              = 0x0100
+	WM_KEYUP                = 0x0101
+	WM_KILLFOCUS            = 0x0008
+	WM_LBUTTONDOWN          = 0x0201
+	WM_LBUTTONUP            = 0x0202
+	WM_MBUTTONDOWN          = 0x0207
+	WM_MBUTTONUP            = 0x0208
+	WM_MOUSEMOVE            = 0x0200
+	WM_MOUSEWHEEL           = 0x020A
+	WM_MOUSEHWHEEL          = 0x020E
+	WM_NCACTIVATE           = 0x0086
+	WM_NCHITTEST            = 0x0084
+	WM_NCCALCSIZE           = 0x0083
+	WM_PAINT                = 0x000F
+	WM_QUIT                 = 0x0012
+	WM_SETCURSOR            = 0x0020
+	WM_SETFOCUS             = 0x0007
+	WM_SHOWWINDOW           = 0x0018
+	WM_SIZE                 = 0x0005
+	WM_STYLECHANGED         = 0x007D
+	WM_SYSKEYDOWN           = 0x0104
+	WM_SYSKEYUP             = 0x0105
+	WM_RBUTTONDOWN          = 0x0204
+	WM_RBUTTONUP            = 0x0205
+	WM_TIMER                = 0x0113
+	WM_UNICHAR              = 0x0109
+	WM_USER                 = 0x0400
+	WM_WINDOWPOSCHANGED     = 0x0047
+
+	WS_CLIPCHILDREN     = 0x02000000
+	WS_CLIPSIBLINGS     = 0x04000000
+	WS_MAXIMIZE         = 0x01000000
+	WS_ICONIC           = 0x20000000
+	WS_VISIBLE          = 0x10000000
+	WS_OVERLAPPED       = 0x00000000
+	WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
+		WS_MINIMIZEBOX | WS_MAXIMIZEBOX
+	WS_CAPTION     = 0x00C00000
+	WS_SYSMENU     = 0x00080000
+	WS_THICKFRAME  = 0x00040000
+	WS_MINIMIZEBOX = 0x00020000
+	WS_MAXIMIZEBOX = 0x00010000
+
+	WS_EX_APPWINDOW  = 0x00040000
+	WS_EX_WINDOWEDGE = 0x00000100
+
+	QS_ALLINPUT = 0x04FF
+
+	MWMO_WAITALL        = 0x0001
+	MWMO_INPUTAVAILABLE = 0x0004
+
+	WAIT_OBJECT_0 = 0
+
+	PM_REMOVE   = 0x0001
+	PM_NOREMOVE = 0x0000
+
+	GHND = 0x0042
+
+	CF_UNICODETEXT = 13
+	IMAGE_BITMAP   = 0
+	IMAGE_ICON     = 1
+	IMAGE_CURSOR   = 2
+
+	LR_CREATEDIBSECTION = 0x00002000
+	LR_DEFAULTCOLOR     = 0x00000000
+	LR_DEFAULTSIZE      = 0x00000040
+	LR_LOADFROMFILE     = 0x00000010
+	LR_LOADMAP3DCOLORS  = 0x00001000
+	LR_LOADTRANSPARENT  = 0x00000020
+	LR_MONOCHROME       = 0x00000001
+	LR_SHARED           = 0x00008000
+	LR_VGACOLOR         = 0x00000080
+)
+
+var (
+	kernel32          = syscall.NewLazySystemDLL("kernel32.dll")
+	_GetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
+	_GlobalAlloc      = kernel32.NewProc("GlobalAlloc")
+	_GlobalFree       = kernel32.NewProc("GlobalFree")
+	_GlobalLock       = kernel32.NewProc("GlobalLock")
+	_GlobalUnlock     = kernel32.NewProc("GlobalUnlock")
+
+	user32                       = syscall.NewLazySystemDLL("user32.dll")
+	_AdjustWindowRectEx          = user32.NewProc("AdjustWindowRectEx")
+	_CallMsgFilter               = user32.NewProc("CallMsgFilterW")
+	_CloseClipboard              = user32.NewProc("CloseClipboard")
+	_CreateWindowEx              = user32.NewProc("CreateWindowExW")
+	_DefWindowProc               = user32.NewProc("DefWindowProcW")
+	_DestroyWindow               = user32.NewProc("DestroyWindow")
+	_DispatchMessage             = user32.NewProc("DispatchMessageW")
+	_EmptyClipboard              = user32.NewProc("EmptyClipboard")
+	_GetWindowRect               = user32.NewProc("GetWindowRect")
+	_GetClientRect               = user32.NewProc("GetClientRect")
+	_GetClipboardData            = user32.NewProc("GetClipboardData")
+	_GetDC                       = user32.NewProc("GetDC")
+	_GetDpiForWindow             = user32.NewProc("GetDpiForWindow")
+	_GetKeyState                 = user32.NewProc("GetKeyState")
+	_GetMessage                  = user32.NewProc("GetMessageW")
+	_GetMessageTime              = user32.NewProc("GetMessageTime")
+	_GetMonitorInfo              = user32.NewProc("GetMonitorInfoW")
+	_GetSystemMetrics            = user32.NewProc("GetSystemMetrics")
+	_GetWindowLong               = user32.NewProc("GetWindowLongPtrW")
+	_GetWindowLong32             = user32.NewProc("GetWindowLongW")
+	_GetWindowPlacement          = user32.NewProc("GetWindowPlacement")
+	_KillTimer                   = user32.NewProc("KillTimer")
+	_LoadCursor                  = user32.NewProc("LoadCursorW")
+	_LoadImage                   = user32.NewProc("LoadImageW")
+	_MonitorFromPoint            = user32.NewProc("MonitorFromPoint")
+	_MonitorFromWindow           = user32.NewProc("MonitorFromWindow")
+	_MoveWindow                  = user32.NewProc("MoveWindow")
+	_MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx")
+	_OpenClipboard               = user32.NewProc("OpenClipboard")
+	_PeekMessage                 = user32.NewProc("PeekMessageW")
+	_PostMessage                 = user32.NewProc("PostMessageW")
+	_PostQuitMessage             = user32.NewProc("PostQuitMessage")
+	_ReleaseCapture              = user32.NewProc("ReleaseCapture")
+	_RegisterClassExW            = user32.NewProc("RegisterClassExW")
+	_ReleaseDC                   = user32.NewProc("ReleaseDC")
+	_ScreenToClient              = user32.NewProc("ScreenToClient")
+	_ShowWindow                  = user32.NewProc("ShowWindow")
+	_SetCapture                  = user32.NewProc("SetCapture")
+	_SetCursor                   = user32.NewProc("SetCursor")
+	_SetClipboardData            = user32.NewProc("SetClipboardData")
+	_SetForegroundWindow         = user32.NewProc("SetForegroundWindow")
+	_SetFocus                    = user32.NewProc("SetFocus")
+	_SetProcessDPIAware          = user32.NewProc("SetProcessDPIAware")
+	_SetTimer                    = user32.NewProc("SetTimer")
+	_SetWindowLong               = user32.NewProc("SetWindowLongPtrW")
+	_SetWindowLong32             = user32.NewProc("SetWindowLongW")
+	_SetWindowPlacement          = user32.NewProc("SetWindowPlacement")
+	_SetWindowPos                = user32.NewProc("SetWindowPos")
+	_SetWindowText               = user32.NewProc("SetWindowTextW")
+	_TranslateMessage            = user32.NewProc("TranslateMessage")
+	_UnregisterClass             = user32.NewProc("UnregisterClassW")
+	_UpdateWindow                = user32.NewProc("UpdateWindow")
+
+	shcore            = syscall.NewLazySystemDLL("shcore")
+	_GetDpiForMonitor = shcore.NewProc("GetDpiForMonitor")
+
+	gdi32          = syscall.NewLazySystemDLL("gdi32")
+	_GetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
+
+	imm32                    = syscall.NewLazySystemDLL("imm32")
+	_ImmGetContext           = imm32.NewProc("ImmGetContext")
+	_ImmGetCompositionString = imm32.NewProc("ImmGetCompositionStringW")
+	_ImmNotifyIME            = imm32.NewProc("ImmNotifyIME")
+	_ImmReleaseContext       = imm32.NewProc("ImmReleaseContext")
+	_ImmSetCandidateWindow   = imm32.NewProc("ImmSetCandidateWindow")
+	_ImmSetCompositionWindow = imm32.NewProc("ImmSetCompositionWindow")
+
+	dwmapi                        = syscall.NewLazySystemDLL("dwmapi")
+	_DwmExtendFrameIntoClientArea = dwmapi.NewProc("DwmExtendFrameIntoClientArea")
+)
+
+func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
+	_AdjustWindowRectEx.Call(uintptr(unsafe.Pointer(r)), uintptr(dwStyle), uintptr(bMenu), uintptr(dwExStyle))
+}
+
+func CallMsgFilter(m *Msg, nCode uintptr) bool {
+	r, _, _ := _CallMsgFilter.Call(uintptr(unsafe.Pointer(m)), nCode)
+	return r != 0
+}
+
+func CloseClipboard() error {
+	r, _, err := _CloseClipboard.Call()
+	if r == 0 {
+		return fmt.Errorf("CloseClipboard: %v", err)
+	}
+	return nil
+}
+
+func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, dwStyle uint32, x, y, w, h int32, hWndParent, hMenu, hInstance syscall.Handle, lpParam uintptr) (syscall.Handle, error) {
+	wname := syscall.StringToUTF16Ptr(lpWindowName)
+	hwnd, _, err := _CreateWindowEx.Call(
+		uintptr(dwExStyle),
+		uintptr(lpClassName),
+		uintptr(unsafe.Pointer(wname)),
+		uintptr(dwStyle),
+		uintptr(x), uintptr(y),
+		uintptr(w), uintptr(h),
+		uintptr(hWndParent),
+		uintptr(hMenu),
+		uintptr(hInstance),
+		uintptr(lpParam))
+	if hwnd == 0 {
+		return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
+	}
+	return syscall.Handle(hwnd), nil
+}
+
+func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
+	r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
+	return r
+}
+
+func DestroyWindow(hwnd syscall.Handle) {
+	_DestroyWindow.Call(uintptr(hwnd))
+}
+
+func DispatchMessage(m *Msg) {
+	_DispatchMessage.Call(uintptr(unsafe.Pointer(m)))
+}
+
+func DwmExtendFrameIntoClientArea(hwnd syscall.Handle, margins Margins) error {
+	r, _, _ := _DwmExtendFrameIntoClientArea.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&margins)))
+	if r != 0 {
+		return fmt.Errorf("DwmExtendFrameIntoClientArea: %#x", r)
+	}
+	return nil
+}
+
+func EmptyClipboard() error {
+	r, _, err := _EmptyClipboard.Call()
+	if r == 0 {
+		return fmt.Errorf("EmptyClipboard: %v", err)
+	}
+	return nil
+}
+
+func GetWindowRect(hwnd syscall.Handle) Rect {
+	var r Rect
+	_GetWindowRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
+	return r
+}
+
+func GetClientRect(hwnd syscall.Handle) Rect {
+	var r Rect
+	_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
+	return r
+}
+
+func GetClipboardData(format uint32) (syscall.Handle, error) {
+	r, _, err := _GetClipboardData.Call(uintptr(format))
+	if r == 0 {
+		return 0, fmt.Errorf("GetClipboardData: %v", err)
+	}
+	return syscall.Handle(r), nil
+}
+
+func GetDC(hwnd syscall.Handle) (syscall.Handle, error) {
+	hdc, _, err := _GetDC.Call(uintptr(hwnd))
+	if hdc == 0 {
+		return 0, fmt.Errorf("GetDC failed: %v", err)
+	}
+	return syscall.Handle(hdc), nil
+}
+
+func GetModuleHandle() (syscall.Handle, error) {
+	h, _, err := _GetModuleHandleW.Call(uintptr(0))
+	if h == 0 {
+		return 0, fmt.Errorf("GetModuleHandleW failed: %v", err)
+	}
+	return syscall.Handle(h), nil
+}
+
+func getDeviceCaps(hdc syscall.Handle, index int32) int {
+	c, _, _ := _GetDeviceCaps.Call(uintptr(hdc), uintptr(index))
+	return int(c)
+}
+
+func getDpiForMonitor(hmonitor syscall.Handle, dpiType uint32) int {
+	var dpiX, dpiY uintptr
+	_GetDpiForMonitor.Call(uintptr(hmonitor), uintptr(dpiType), uintptr(unsafe.Pointer(&dpiX)), uintptr(unsafe.Pointer(&dpiY)))
+	return int(dpiX)
+}
+
+// GetSystemDPI returns the effective DPI of the system.
+func GetSystemDPI() int {
+	// Check for GetDpiForMonitor, introduced in Windows 8.1.
+	if _GetDpiForMonitor.Find() == nil {
+		hmon := monitorFromPoint(Point{}, MONITOR_DEFAULTTOPRIMARY)
+		return getDpiForMonitor(hmon, MDT_EFFECTIVE_DPI)
+	} else {
+		// Fall back to the physical device DPI.
+		screenDC, err := GetDC(0)
+		if err != nil {
+			return 96
+		}
+		defer ReleaseDC(screenDC)
+		return getDeviceCaps(screenDC, LOGPIXELSX)
+	}
+}
+
+func GetKeyState(nVirtKey int32) int16 {
+	c, _, _ := _GetKeyState.Call(uintptr(nVirtKey))
+	return int16(c)
+}
+
+func GetMessage(m *Msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax uint32) int32 {
+	r, _, _ := _GetMessage.Call(uintptr(unsafe.Pointer(m)),
+		uintptr(hwnd),
+		uintptr(wMsgFilterMin),
+		uintptr(wMsgFilterMax))
+	return int32(r)
+}
+
+func GetMessageTime() time.Duration {
+	r, _, _ := _GetMessageTime.Call()
+	return time.Duration(r) * time.Millisecond
+}
+
+func GetSystemMetrics(nIndex int) int {
+	r, _, _ := _GetSystemMetrics.Call(uintptr(nIndex))
+	return int(r)
+}
+
+// GetWindowDPI returns the effective DPI of the window.
+func GetWindowDPI(hwnd syscall.Handle) int {
+	// Check for GetDpiForWindow, introduced in Windows 10.
+	if _GetDpiForWindow.Find() == nil {
+		dpi, _, _ := _GetDpiForWindow.Call(uintptr(hwnd))
+		return int(dpi)
+	} else {
+		return GetSystemDPI()
+	}
+}
+
+func GetWindowPlacement(hwnd syscall.Handle) *WindowPlacement {
+	var wp WindowPlacement
+	wp.length = uint32(unsafe.Sizeof(wp))
+	_GetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&wp)))
+	return &wp
+}
+
+func GetMonitorInfo(hwnd syscall.Handle) MonitorInfo {
+	var mi MonitorInfo
+	mi.cbSize = uint32(unsafe.Sizeof(mi))
+	v, _, _ := _MonitorFromWindow.Call(uintptr(hwnd), MONITOR_DEFAULTTOPRIMARY)
+	_GetMonitorInfo.Call(v, uintptr(unsafe.Pointer(&mi)))
+	return mi
+}
+
+func GetWindowLong(hwnd syscall.Handle, index uintptr) (val uintptr) {
+	if runtime.GOARCH == "386" {
+		val, _, _ = _GetWindowLong32.Call(uintptr(hwnd), index)
+	} else {
+		val, _, _ = _GetWindowLong.Call(uintptr(hwnd), index)
+	}
+	return
+}
+
+func ImmGetContext(hwnd syscall.Handle) syscall.Handle {
+	h, _, _ := _ImmGetContext.Call(uintptr(hwnd))
+	return syscall.Handle(h)
+}
+
+func ImmReleaseContext(hwnd, imc syscall.Handle) {
+	_ImmReleaseContext.Call(uintptr(hwnd), uintptr(imc))
+}
+
+func ImmNotifyIME(imc syscall.Handle, action, index, value int) {
+	_ImmNotifyIME.Call(uintptr(imc), uintptr(action), uintptr(index), uintptr(value))
+}
+
+func ImmGetCompositionString(imc syscall.Handle, key int) string {
+	size, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
+	if int32(size) <= 0 {
+		return ""
+	}
+	u16 := make([]uint16, size/unsafe.Sizeof(uint16(0)))
+	_ImmGetCompositionString.Call(uintptr(imc), uintptr(key), uintptr(unsafe.Pointer(&u16[0])), size)
+	return string(utf16.Decode(u16))
+}
+
+func ImmGetCompositionValue(imc syscall.Handle, key int) int {
+	val, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
+	return int(int32(val))
+}
+
+func ImmSetCompositionWindow(imc syscall.Handle, x, y int) {
+	f := CompositionForm{
+		dwStyle: CFS_POINT,
+		ptCurrentPos: Point{
+			X: int32(x), Y: int32(y),
+		},
+	}
+	_ImmSetCompositionWindow.Call(uintptr(imc), uintptr(unsafe.Pointer(&f)))
+}
+
+func ImmSetCandidateWindow(imc syscall.Handle, x, y int) {
+	f := CandidateForm{
+		dwStyle: CFS_CANDIDATEPOS,
+		ptCurrentPos: Point{
+			X: int32(x), Y: int32(y),
+		},
+	}
+	_ImmSetCandidateWindow.Call(uintptr(imc), uintptr(unsafe.Pointer(&f)))
+}
+
+func SetWindowLong(hwnd syscall.Handle, idx uintptr, style uintptr) {
+	if runtime.GOARCH == "386" {
+		_SetWindowLong32.Call(uintptr(hwnd), idx, style)
+	} else {
+		_SetWindowLong.Call(uintptr(hwnd), idx, style)
+	}
+}
+
+func SetWindowPlacement(hwnd syscall.Handle, wp *WindowPlacement) {
+	_SetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wp)))
+}
+
+func SetWindowPos(hwnd syscall.Handle, hwndInsertAfter uint32, x, y, dx, dy int32, style uintptr) {
+	_SetWindowPos.Call(uintptr(hwnd), uintptr(hwndInsertAfter),
+		uintptr(x), uintptr(y),
+		uintptr(dx), uintptr(dy),
+		style,
+	)
+}
+
+func SetWindowText(hwnd syscall.Handle, title string) {
+	wname := syscall.StringToUTF16Ptr(title)
+	_SetWindowText.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wname)))
+}
+
+func GlobalAlloc(size int) (syscall.Handle, error) {
+	r, _, err := _GlobalAlloc.Call(GHND, uintptr(size))
+	if r == 0 {
+		return 0, fmt.Errorf("GlobalAlloc: %v", err)
+	}
+	return syscall.Handle(r), nil
+}
+
+func GlobalFree(h syscall.Handle) {
+	_GlobalFree.Call(uintptr(h))
+}
+
+func GlobalLock(h syscall.Handle) (unsafe.Pointer, error) {
+	r, _, err := _GlobalLock.Call(uintptr(h))
+	if r == 0 {
+		return nil, fmt.Errorf("GlobalLock: %v", err)
+	}
+	return unsafe.Pointer(r), nil
+}
+
+func GlobalUnlock(h syscall.Handle) {
+	_GlobalUnlock.Call(uintptr(h))
+}
+
+func KillTimer(hwnd syscall.Handle, nIDEvent uintptr) error {
+	r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), 0, 0)
+	if r == 0 {
+		return fmt.Errorf("KillTimer failed: %v", err)
+	}
+	return nil
+}
+
+func LoadCursor(curID uint16) (syscall.Handle, error) {
+	h, _, err := _LoadCursor.Call(0, uintptr(curID))
+	if h == 0 {
+		return 0, fmt.Errorf("LoadCursorW failed: %v", err)
+	}
+	return syscall.Handle(h), nil
+}
+
+func LoadImage(hInst syscall.Handle, res uint32, typ uint32, cx, cy int, fuload uint32) (syscall.Handle, error) {
+	h, _, err := _LoadImage.Call(uintptr(hInst), uintptr(res), uintptr(typ), uintptr(cx), uintptr(cy), uintptr(fuload))
+	if h == 0 {
+		return 0, fmt.Errorf("LoadImageW failed: %v", err)
+	}
+	return syscall.Handle(h), nil
+}
+
+func MoveWindow(hwnd syscall.Handle, x, y, width, height int32, repaint bool) {
+	var paint uintptr
+	if repaint {
+		paint = TRUE
+	}
+	_MoveWindow.Call(uintptr(hwnd), uintptr(x), uintptr(y), uintptr(width), uintptr(height), paint)
+}
+
+func monitorFromPoint(pt Point, flags uint32) syscall.Handle {
+	r, _, _ := _MonitorFromPoint.Call(uintptr(pt.X), uintptr(pt.Y), uintptr(flags))
+	return syscall.Handle(r)
+}
+
+func MsgWaitForMultipleObjectsEx(nCount uint32, pHandles uintptr, millis, mask, flags uint32) (uint32, error) {
+	r, _, err := _MsgWaitForMultipleObjectsEx.Call(uintptr(nCount), pHandles, uintptr(millis), uintptr(mask), uintptr(flags))
+	res := uint32(r)
+	if res == 0xFFFFFFFF {
+		return 0, fmt.Errorf("MsgWaitForMultipleObjectsEx failed: %v", err)
+	}
+	return res, nil
+}
+
+func OpenClipboard(hwnd syscall.Handle) error {
+	r, _, err := _OpenClipboard.Call(uintptr(hwnd))
+	if r == 0 {
+		return fmt.Errorf("OpenClipboard: %v", err)
+	}
+	return nil
+}
+
+func PeekMessage(m *Msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax, wRemoveMsg uint32) bool {
+	r, _, _ := _PeekMessage.Call(uintptr(unsafe.Pointer(m)), uintptr(hwnd), uintptr(wMsgFilterMin), uintptr(wMsgFilterMax), uintptr(wRemoveMsg))
+	return r != 0
+}
+
+func PostQuitMessage(exitCode uintptr) {
+	_PostQuitMessage.Call(exitCode)
+}
+
+func PostMessage(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) error {
+	r, _, err := _PostMessage.Call(uintptr(hwnd), uintptr(msg), wParam, lParam)
+	if r == 0 {
+		return fmt.Errorf("PostMessage failed: %v", err)
+	}
+	return nil
+}
+
+func ReleaseCapture() bool {
+	r, _, _ := _ReleaseCapture.Call()
+	return r != 0
+}
+
+func RegisterClassEx(cls *WndClassEx) (uint16, error) {
+	a, _, err := _RegisterClassExW.Call(uintptr(unsafe.Pointer(cls)))
+	if a == 0 {
+		return 0, fmt.Errorf("RegisterClassExW failed: %v", err)
+	}
+	return uint16(a), nil
+}
+
+func ReleaseDC(hdc syscall.Handle) {
+	_ReleaseDC.Call(uintptr(hdc))
+}
+
+func SetForegroundWindow(hwnd syscall.Handle) {
+	_SetForegroundWindow.Call(uintptr(hwnd))
+}
+
+func SetFocus(hwnd syscall.Handle) {
+	_SetFocus.Call(uintptr(hwnd))
+}
+
+func SetProcessDPIAware() {
+	_SetProcessDPIAware.Call()
+}
+
+func SetCapture(hwnd syscall.Handle) syscall.Handle {
+	r, _, _ := _SetCapture.Call(uintptr(hwnd))
+	return syscall.Handle(r)
+}
+
+func SetClipboardData(format uint32, mem syscall.Handle) error {
+	r, _, err := _SetClipboardData.Call(uintptr(format), uintptr(mem))
+	if r == 0 {
+		return fmt.Errorf("SetClipboardData: %v", err)
+	}
+	return nil
+}
+
+func SetCursor(h syscall.Handle) {
+	_SetCursor.Call(uintptr(h))
+}
+
+func SetTimer(hwnd syscall.Handle, nIDEvent uintptr, uElapse uint32, timerProc uintptr) error {
+	r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), uintptr(uElapse), timerProc)
+	if r == 0 {
+		return fmt.Errorf("SetTimer failed: %v", err)
+	}
+	return nil
+}
+
+func ScreenToClient(hwnd syscall.Handle, p *Point) {
+	_ScreenToClient.Call(uintptr(hwnd), uintptr(unsafe.Pointer(p)))
+}
+
+func ShowWindow(hwnd syscall.Handle, nCmdShow int32) {
+	_ShowWindow.Call(uintptr(hwnd), uintptr(nCmdShow))
+}
+
+func TranslateMessage(m *Msg) {
+	_TranslateMessage.Call(uintptr(unsafe.Pointer(m)))
+}
+
+func UnregisterClass(cls uint16, hInst syscall.Handle) {
+	_UnregisterClass.Call(uintptr(cls), uintptr(hInst))
+}
+
+func UpdateWindow(hwnd syscall.Handle) {
+	_UpdateWindow.Call(uintptr(hwnd))
+}
+
+func (p WindowPlacement) Rect() Rect {
+	return p.rcNormalPosition
+}
+
+func (p WindowPlacement) IsMinimized() bool {
+	return p.showCmd == SW_SHOWMINIMIZED
+}
+
+func (p WindowPlacement) IsMaximized() bool {
+	return p.showCmd == SW_SHOWMAXIMIZED
+}
+
+func (p *WindowPlacement) Set(Left, Top, Right, Bottom int) {
+	p.rcNormalPosition.Left = int32(Left)
+	p.rcNormalPosition.Top = int32(Top)
+	p.rcNormalPosition.Right = int32(Right)
+	p.rcNormalPosition.Bottom = int32(Bottom)
+}

+ 375 - 0
vendor/gioui.org/app/internal/xkb/xkb_unix.go

@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build (linux && !android) || freebsd || openbsd
+// +build linux,!android freebsd openbsd
+
+// Package xkb implements a Go interface for the X Keyboard Extension library.
+package xkb
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"syscall"
+	"unicode"
+	"unicode/utf8"
+	"unsafe"
+
+	"gioui.org/io/event"
+	"gioui.org/io/key"
+)
+
+/*
+#cgo linux pkg-config: xkbcommon
+#cgo freebsd openbsd CFLAGS: -I/usr/local/include
+#cgo freebsd openbsd LDFLAGS: -L/usr/local/lib -lxkbcommon
+
+#include <stdlib.h>
+#include <xkbcommon/xkbcommon.h>
+#include <xkbcommon/xkbcommon-compose.h>
+*/
+import "C"
+
+type Context struct {
+	Ctx       *C.struct_xkb_context
+	keyMap    *C.struct_xkb_keymap
+	state     *C.struct_xkb_state
+	compTable *C.struct_xkb_compose_table
+	compState *C.struct_xkb_compose_state
+	utf8Buf   []byte
+}
+
+var (
+	_XKB_MOD_NAME_CTRL  = []byte("Control\x00")
+	_XKB_MOD_NAME_SHIFT = []byte("Shift\x00")
+	_XKB_MOD_NAME_ALT   = []byte("Mod1\x00")
+	_XKB_MOD_NAME_LOGO  = []byte("Mod4\x00")
+)
+
+func (x *Context) Destroy() {
+	if x.compState != nil {
+		C.xkb_compose_state_unref(x.compState)
+		x.compState = nil
+	}
+	if x.compTable != nil {
+		C.xkb_compose_table_unref(x.compTable)
+		x.compTable = nil
+	}
+	x.DestroyKeymapState()
+	if x.Ctx != nil {
+		C.xkb_context_unref(x.Ctx)
+		x.Ctx = nil
+	}
+}
+
+func New() (*Context, error) {
+	ctx := &Context{
+		Ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS),
+	}
+	if ctx.Ctx == nil {
+		return nil, errors.New("newXKB: xkb_context_new failed")
+	}
+	locale := os.Getenv("LC_ALL")
+	if locale == "" {
+		locale = os.Getenv("LC_CTYPE")
+	}
+	if locale == "" {
+		locale = os.Getenv("LANG")
+	}
+	if locale == "" {
+		locale = "C"
+	}
+	cloc := C.CString(locale)
+	defer C.free(unsafe.Pointer(cloc))
+	ctx.compTable = C.xkb_compose_table_new_from_locale(ctx.Ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS)
+	if ctx.compTable == nil {
+		ctx.Destroy()
+		return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed")
+	}
+	ctx.compState = C.xkb_compose_state_new(ctx.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS)
+	if ctx.compState == nil {
+		ctx.Destroy()
+		return nil, errors.New("newXKB: xkb_compose_state_new failed")
+	}
+	return ctx, nil
+}
+
+func (x *Context) DestroyKeymapState() {
+	if x.state != nil {
+		C.xkb_state_unref(x.state)
+		x.state = nil
+	}
+	if x.keyMap != nil {
+		C.xkb_keymap_unref(x.keyMap)
+		x.keyMap = nil
+	}
+}
+
+// SetKeymap sets the keymap and state. The context takes ownership of the
+// keymap and state and frees them in Destroy.
+func (x *Context) SetKeymap(xkbKeyMap, xkbState unsafe.Pointer) {
+	x.DestroyKeymapState()
+	x.keyMap = (*C.struct_xkb_keymap)(xkbKeyMap)
+	x.state = (*C.struct_xkb_state)(xkbState)
+}
+
+func (x *Context) LoadKeymap(format int, fd int, size int) error {
+	x.DestroyKeymapState()
+	mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
+	if err != nil {
+		return fmt.Errorf("newXKB: mmap of keymap failed: %v", err)
+	}
+	defer syscall.Munmap(mapData)
+	keyMap := C.xkb_keymap_new_from_buffer(x.Ctx, (*C.char)(unsafe.Pointer(&mapData[0])), C.size_t(size-1), C.XKB_KEYMAP_FORMAT_TEXT_V1, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
+	if keyMap == nil {
+		return errors.New("newXKB: xkb_keymap_new_from_buffer failed")
+	}
+	state := C.xkb_state_new(keyMap)
+	if state == nil {
+		C.xkb_keymap_unref(keyMap)
+		return errors.New("newXKB: xkb_state_new failed")
+	}
+	x.keyMap = keyMap
+	x.state = state
+	return nil
+}
+
+func (x *Context) Modifiers() key.Modifiers {
+	var mods key.Modifiers
+	if x.state == nil {
+		return mods
+	}
+
+	if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_CTRL[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
+		mods |= key.ModCtrl
+	}
+	if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_SHIFT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
+		mods |= key.ModShift
+	}
+	if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_ALT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
+		mods |= key.ModAlt
+	}
+	if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_LOGO[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
+		mods |= key.ModSuper
+	}
+	return mods
+}
+
+func (x *Context) DispatchKey(keyCode uint32, state key.State) (events []event.Event) {
+	if x.state == nil {
+		return
+	}
+	kc := C.xkb_keycode_t(keyCode)
+	if len(x.utf8Buf) == 0 {
+		x.utf8Buf = make([]byte, 1)
+	}
+	sym := C.xkb_state_key_get_one_sym(x.state, kc)
+	if name, ok := convertKeysym(sym); ok {
+		cmd := key.Event{
+			Name:      name,
+			Modifiers: x.Modifiers(),
+			State:     state,
+		}
+		// Ensure that a physical backtab key is translated to
+		// Shift-Tab.
+		if sym == C.XKB_KEY_ISO_Left_Tab {
+			cmd.Modifiers |= key.ModShift
+		}
+		events = append(events, cmd)
+	}
+	C.xkb_compose_state_feed(x.compState, sym)
+	var str []byte
+	switch C.xkb_compose_state_get_status(x.compState) {
+	case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING:
+		return
+	case C.XKB_COMPOSE_COMPOSED:
+		size := C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
+		if int(size) >= len(x.utf8Buf) {
+			x.utf8Buf = make([]byte, size+1)
+			size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
+		}
+		C.xkb_compose_state_reset(x.compState)
+		str = x.utf8Buf[:size]
+	case C.XKB_COMPOSE_NOTHING:
+		mod := x.Modifiers()
+		if mod&(key.ModCtrl|key.ModAlt|key.ModSuper) == 0 {
+			str = x.charsForKeycode(kc)
+		}
+	}
+	// Report only printable runes.
+	var n int
+	for n < len(str) {
+		r, s := utf8.DecodeRune(str)
+		if unicode.IsPrint(r) {
+			n += s
+		} else {
+			copy(str[n:], str[n+s:])
+			str = str[:len(str)-s]
+		}
+	}
+	if state == key.Press && len(str) > 0 {
+		events = append(events, key.EditEvent{Text: string(str)})
+	}
+	return
+}
+
+func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte {
+	size := C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
+	if int(size) >= len(x.utf8Buf) {
+		x.utf8Buf = make([]byte, size+1)
+		size = C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
+	}
+	return x.utf8Buf[:size]
+}
+
+func (x *Context) IsRepeatKey(keyCode uint32) bool {
+	if x.state == nil {
+		return false
+	}
+	kc := C.xkb_keycode_t(keyCode)
+	return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
+}
+
+func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latchedGroup, lockedGroup uint32) {
+	if x.state == nil {
+		return
+	}
+	C.xkb_state_update_mask(x.state, C.xkb_mod_mask_t(depressed), C.xkb_mod_mask_t(latched), C.xkb_mod_mask_t(locked),
+		C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
+}
+
+func convertKeysym(s C.xkb_keysym_t) (key.Name, bool) {
+	if 'a' <= s && s <= 'z' {
+		return key.Name(rune(s - 'a' + 'A')), true
+	}
+	if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
+		return key.Name(rune(s - C.XKB_KEY_KP_0 + '0')), true
+	}
+	if ' ' < s && s <= '~' {
+		return key.Name(rune(s)), true
+	}
+	var n key.Name
+	switch s {
+	case C.XKB_KEY_Escape:
+		n = key.NameEscape
+	case C.XKB_KEY_Left:
+		n = key.NameLeftArrow
+	case C.XKB_KEY_Right:
+		n = key.NameRightArrow
+	case C.XKB_KEY_Return:
+		n = key.NameReturn
+	case C.XKB_KEY_Up:
+		n = key.NameUpArrow
+	case C.XKB_KEY_Down:
+		n = key.NameDownArrow
+	case C.XKB_KEY_Home:
+		n = key.NameHome
+	case C.XKB_KEY_End:
+		n = key.NameEnd
+	case C.XKB_KEY_BackSpace:
+		n = key.NameDeleteBackward
+	case C.XKB_KEY_Delete:
+		n = key.NameDeleteForward
+	case C.XKB_KEY_Page_Up:
+		n = key.NamePageUp
+	case C.XKB_KEY_Page_Down:
+		n = key.NamePageDown
+	case C.XKB_KEY_F1:
+		n = key.NameF1
+	case C.XKB_KEY_F2:
+		n = key.NameF2
+	case C.XKB_KEY_F3:
+		n = key.NameF3
+	case C.XKB_KEY_F4:
+		n = key.NameF4
+	case C.XKB_KEY_F5:
+		n = key.NameF5
+	case C.XKB_KEY_F6:
+		n = key.NameF6
+	case C.XKB_KEY_F7:
+		n = key.NameF7
+	case C.XKB_KEY_F8:
+		n = key.NameF8
+	case C.XKB_KEY_F9:
+		n = key.NameF9
+	case C.XKB_KEY_F10:
+		n = key.NameF10
+	case C.XKB_KEY_F11:
+		n = key.NameF11
+	case C.XKB_KEY_F12:
+		n = key.NameF12
+	case C.XKB_KEY_Tab, C.XKB_KEY_ISO_Left_Tab:
+		n = key.NameTab
+	case 0x20:
+		n = key.NameSpace
+	case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R:
+		n = key.NameCtrl
+	case C.XKB_KEY_Shift_L, C.XKB_KEY_Shift_R:
+		n = key.NameShift
+	case C.XKB_KEY_Alt_L, C.XKB_KEY_Alt_R:
+		n = key.NameAlt
+	case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R:
+		n = key.NameSuper
+
+	case C.XKB_KEY_KP_Space:
+		n = key.NameSpace
+	case C.XKB_KEY_KP_Tab:
+		n = key.NameTab
+	case C.XKB_KEY_KP_Enter:
+		n = key.NameEnter
+	case C.XKB_KEY_KP_F1:
+		n = key.NameF1
+	case C.XKB_KEY_KP_F2:
+		n = key.NameF2
+	case C.XKB_KEY_KP_F3:
+		n = key.NameF3
+	case C.XKB_KEY_KP_F4:
+		n = key.NameF4
+	case C.XKB_KEY_KP_Home:
+		n = key.NameHome
+	case C.XKB_KEY_KP_Left:
+		n = key.NameLeftArrow
+	case C.XKB_KEY_KP_Up:
+		n = key.NameUpArrow
+	case C.XKB_KEY_KP_Right:
+		n = key.NameRightArrow
+	case C.XKB_KEY_KP_Down:
+		n = key.NameDownArrow
+	case C.XKB_KEY_KP_Prior:
+		// not supported
+		return "", false
+	case C.XKB_KEY_KP_Next:
+		// not supported
+		return "", false
+	case C.XKB_KEY_KP_End:
+		n = key.NameEnd
+	case C.XKB_KEY_KP_Begin:
+		n = key.NameHome
+	case C.XKB_KEY_KP_Insert:
+		// not supported
+		return "", false
+	case C.XKB_KEY_KP_Delete:
+		n = key.NameDeleteForward
+	case C.XKB_KEY_KP_Multiply:
+		n = "*"
+	case C.XKB_KEY_KP_Add:
+		n = "+"
+	case C.XKB_KEY_KP_Separator:
+		// not supported
+		return "", false
+	case C.XKB_KEY_KP_Subtract:
+		n = "-"
+	case C.XKB_KEY_KP_Decimal:
+		// TODO(dh): does a German keyboard layout also translate the numpad key to XKB_KEY_KP_DECIMAL? Because in
+		// German, the decimal is a comma, not a period.
+		n = "."
+	case C.XKB_KEY_KP_Divide:
+		n = "/"
+	case C.XKB_KEY_KP_Equal:
+		n = "="
+
+	default:
+		return "", false
+	}
+	return n, true
+}

+ 87 - 0
vendor/gioui.org/app/log_android.go

@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+/*
+#cgo LDFLAGS: -llog
+
+#include <stdlib.h>
+#include <android/log.h>
+*/
+import "C"
+
+import (
+	"bufio"
+	"log"
+	"os"
+	"runtime"
+	"syscall"
+	"unsafe"
+)
+
+// 1024 is the truncation limit from android/log.h, plus a \n.
+const logLineLimit = 1024
+
+var logTag = C.CString(ID)
+
+func init() {
+	// Android's logcat already includes timestamps.
+	log.SetFlags(log.Flags() &^ log.LstdFlags)
+	log.SetOutput(new(androidLogWriter))
+
+	// Redirect stdout and stderr to the Android logger.
+	logFd(os.Stdout.Fd())
+	logFd(os.Stderr.Fd())
+}
+
+type androidLogWriter struct {
+	// buf has room for the maximum log line, plus a terminating '\0'.
+	buf [logLineLimit + 1]byte
+}
+
+func (w *androidLogWriter) Write(data []byte) (int, error) {
+	n := 0
+	for len(data) > 0 {
+		msg := data
+		// Truncate the buffer, leaving space for the '\0'.
+		if max := len(w.buf) - 1; len(msg) > max {
+			msg = msg[:max]
+		}
+		buf := w.buf[:len(msg)+1]
+		copy(buf, msg)
+		// Terminating '\0'.
+		buf[len(msg)] = 0
+		C.__android_log_write(C.ANDROID_LOG_INFO, logTag, (*C.char)(unsafe.Pointer(&buf[0])))
+		n += len(msg)
+		data = data[len(msg):]
+	}
+	return n, nil
+}
+
+func logFd(fd uintptr) {
+	r, w, err := os.Pipe()
+	if err != nil {
+		panic(err)
+	}
+	if err := syscall.Dup3(int(w.Fd()), int(fd), syscall.O_CLOEXEC); err != nil {
+		panic(err)
+	}
+	go func() {
+		lineBuf := bufio.NewReaderSize(r, logLineLimit)
+		// The buffer to pass to C, including the terminating '\0'.
+		buf := make([]byte, lineBuf.Size()+1)
+		cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
+		for {
+			line, _, err := lineBuf.ReadLine()
+			if err != nil {
+				break
+			}
+			copy(buf, line)
+			buf[len(line)] = 0
+			C.__android_log_write(C.ANDROID_LOG_INFO, logTag, cbuf)
+		}
+		// The garbage collector doesn't know that w's fd was dup'ed.
+		// Avoid finalizing w, and thereby avoid its finalizer closing its fd.
+		runtime.KeepAlive(w)
+	}()
+}

+ 54 - 0
vendor/gioui.org/app/log_ios.go

@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build darwin && ios
+// +build darwin,ios
+
+package app
+
+/*
+#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
+
+@import Foundation;
+
+static void nslog(char *str) {
+	NSLog(@"%@", @(str));
+}
+*/
+import "C"
+
+import (
+	"bufio"
+	"io"
+	"log"
+	"unsafe"
+
+	_ "gioui.org/internal/cocoainit"
+)
+
+func init() {
+	// macOS Console already includes timestamps.
+	log.SetFlags(log.Flags() &^ log.LstdFlags)
+	log.SetOutput(newNSLogWriter())
+}
+
+func newNSLogWriter() io.Writer {
+	r, w := io.Pipe()
+	go func() {
+		// 1024 is an arbitrary truncation limit, taken from Android's
+		// log buffer size.
+		lineBuf := bufio.NewReaderSize(r, 1024)
+		// The buffer to pass to C, including the terminating '\0'.
+		buf := make([]byte, lineBuf.Size()+1)
+		cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
+		for {
+			line, _, err := lineBuf.ReadLine()
+			if err != nil {
+				break
+			}
+			copy(buf, line)
+			buf[len(line)] = 0
+			C.nslog(cbuf)
+		}
+	}()
+	return w
+}

+ 35 - 0
vendor/gioui.org/app/log_windows.go

@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+import (
+	"log"
+	"unsafe"
+
+	syscall "golang.org/x/sys/windows"
+)
+
+type logger struct{}
+
+var (
+	kernel32           = syscall.NewLazySystemDLL("kernel32")
+	outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
+	debugView          *logger
+)
+
+func init() {
+	// Windows DebugView already includes timestamps.
+	if syscall.Stderr == 0 {
+		log.SetFlags(log.Flags() &^ log.LstdFlags)
+		log.SetOutput(debugView)
+	}
+}
+
+func (l *logger) Write(buf []byte) (int, error) {
+	p, err := syscall.UTF16PtrFromString(string(buf))
+	if err != nil {
+		return 0, err
+	}
+	outputDebugStringW.Call(uintptr(unsafe.Pointer(p)))
+	return len(buf), nil
+}

+ 174 - 0
vendor/gioui.org/app/metal_darwin.go

@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build !nometal
+// +build !nometal
+
+package app
+
+import (
+	"errors"
+
+	"gioui.org/gpu"
+)
+
+/*
+#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
+#cgo LDFLAGS: -framework QuartzCore -framework Metal
+
+#import <Metal/Metal.h>
+#import <QuartzCore/CAMetalLayer.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+
+static CFTypeRef createMetalDevice(void) {
+	@autoreleasepool {
+		id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
+		return CFBridgingRetain(dev);
+	}
+}
+
+static void setupLayer(CFTypeRef layerRef, CFTypeRef devRef) {
+	@autoreleasepool {
+		CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
+		id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
+		layer.device = dev;
+		// Package gpu assumes an sRGB-encoded framebuffer.
+		layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
+		if (@available(iOS 11.0, *)) {
+			// Never let nextDrawable time out and return nil.
+			layer.allowsNextDrawableTimeout = NO;
+		}
+	}
+}
+
+static CFTypeRef nextDrawable(CFTypeRef layerRef) {
+	@autoreleasepool {
+		CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
+		return CFBridgingRetain([layer nextDrawable]);
+	}
+}
+
+static CFTypeRef drawableTexture(CFTypeRef drawableRef) {
+	@autoreleasepool {
+		id<CAMetalDrawable> drawable = (__bridge id<CAMetalDrawable>)drawableRef;
+		return CFBridgingRetain(drawable.texture);
+	}
+}
+
+static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
+	@autoreleasepool {
+		id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
+		id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
+		id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
+		[cmdBuffer commit];
+		[cmdBuffer waitUntilScheduled];
+		[drawable present];
+	}
+}
+
+static CFTypeRef newCommandQueue(CFTypeRef devRef) {
+	@autoreleasepool {
+		id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
+		return CFBridgingRetain([dev newCommandQueue]);
+	}
+}
+*/
+import "C"
+
+type mtlContext struct {
+	dev      C.CFTypeRef
+	view     C.CFTypeRef
+	layer    C.CFTypeRef
+	queue    C.CFTypeRef
+	drawable C.CFTypeRef
+	texture  C.CFTypeRef
+}
+
+func newMtlContext(w *window) (*mtlContext, error) {
+	dev := C.createMetalDevice()
+	if dev == 0 {
+		return nil, errors.New("metal: MTLCreateSystemDefaultDevice failed")
+	}
+	view := w.contextView()
+	layer := getMetalLayer(view)
+	if layer == 0 {
+		C.CFRelease(dev)
+		return nil, errors.New("metal: CAMetalLayer construction failed")
+	}
+	queue := C.newCommandQueue(dev)
+	if layer == 0 {
+		C.CFRelease(dev)
+		C.CFRelease(layer)
+		return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
+	}
+	C.setupLayer(layer, dev)
+	c := &mtlContext{
+		dev:   dev,
+		view:  view,
+		layer: layer,
+		queue: queue,
+	}
+	return c, nil
+}
+
+func (c *mtlContext) RenderTarget() (gpu.RenderTarget, error) {
+	if c.drawable != 0 || c.texture != 0 {
+		return nil, errors.New("metal:a previous RenderTarget wasn't Presented")
+	}
+	c.drawable = C.nextDrawable(c.layer)
+	if c.drawable == 0 {
+		return nil, errors.New("metal: [CAMetalLayer nextDrawable] failed")
+	}
+	c.texture = C.drawableTexture(c.drawable)
+	if c.texture == 0 {
+		return nil, errors.New("metal: CADrawable.texture is nil")
+	}
+	return gpu.MetalRenderTarget{
+		Texture: uintptr(c.texture),
+	}, nil
+}
+
+func (c *mtlContext) API() gpu.API {
+	return gpu.Metal{
+		Device:      uintptr(c.dev),
+		Queue:       uintptr(c.queue),
+		PixelFormat: int(C.MTLPixelFormatBGRA8Unorm_sRGB),
+	}
+}
+
+func (c *mtlContext) Release() {
+	C.CFRelease(c.queue)
+	C.CFRelease(c.dev)
+	C.CFRelease(c.layer)
+	if c.drawable != 0 {
+		C.CFRelease(c.drawable)
+	}
+	if c.texture != 0 {
+		C.CFRelease(c.texture)
+	}
+	*c = mtlContext{}
+}
+
+func (c *mtlContext) Present() error {
+	C.CFRelease(c.texture)
+	c.texture = 0
+	C.presentDrawable(c.queue, c.drawable)
+	C.CFRelease(c.drawable)
+	c.drawable = 0
+	return nil
+}
+
+func (c *mtlContext) Lock() error {
+	return nil
+}
+
+func (c *mtlContext) Unlock() {}
+
+func (c *mtlContext) Refresh() error {
+	resizeDrawable(c.view, c.layer)
+	return nil
+}
+
+func (w *window) NewContext() (context, error) {
+	return newMtlContext(w)
+}

+ 51 - 0
vendor/gioui.org/app/metal_ios.go

@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build !nometal
+// +build !nometal
+
+package app
+
+/*
+#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
+
+@import UIKit;
+
+@import QuartzCore.CAMetalLayer;
+
+#include <CoreFoundation/CoreFoundation.h>
+
+Class gio_layerClass(void) {
+	return [CAMetalLayer class];
+}
+
+static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
+	@autoreleasepool {
+		UIView *view = (__bridge UIView *)viewRef;
+		CAMetalLayer *l = (CAMetalLayer *)view.layer;
+		l.needsDisplayOnBoundsChange = YES;
+		l.presentsWithTransaction = YES;
+		return CFBridgingRetain(l);
+	}
+}
+
+static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
+	@autoreleasepool {
+		UIView *view = (__bridge UIView *)viewRef;
+		CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
+		layer.contentsScale = view.contentScaleFactor;
+		CGSize size = layer.bounds.size;
+		size.width *= layer.contentsScale;
+		size.height *= layer.contentsScale;
+		layer.drawableSize = size;
+	}
+}
+*/
+import "C"
+
+func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
+	return C.getMetalLayer(view)
+}
+
+func resizeDrawable(view, layer C.CFTypeRef) {
+	C.resizeDrawable(view, layer)
+}

+ 51 - 0
vendor/gioui.org/app/metal_macos.go

@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build darwin && !ios && !nometal
+// +build darwin,!ios,!nometal
+
+package app
+
+/*
+#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
+
+#import <AppKit/AppKit.h>
+#import <QuartzCore/CAMetalLayer.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+CALayer *gio_layerFactory(BOOL presentWithTrans) {
+	@autoreleasepool {
+		CAMetalLayer *l = [CAMetalLayer layer];
+		l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
+		l.needsDisplayOnBoundsChange = YES;
+		l.presentsWithTransaction = presentWithTrans;
+		return l;
+	}
+}
+
+static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
+	@autoreleasepool {
+		NSView *view = (__bridge NSView *)viewRef;
+		return CFBridgingRetain(view.layer);
+	}
+}
+
+static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
+	@autoreleasepool {
+		NSView *view = (__bridge NSView *)viewRef;
+		CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
+		CGSize size = layer.bounds.size;
+		size.width *= layer.contentsScale;
+		size.height *= layer.contentsScale;
+		layer.drawableSize = size;
+	}
+}
+*/
+import "C"
+
+func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
+	return C.getMetalLayer(view)
+}
+
+func resizeDrawable(view, layer C.CFTypeRef) {
+	C.resizeDrawable(view, layer)
+}

+ 365 - 0
vendor/gioui.org/app/os.go

@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+import (
+	"errors"
+	"image"
+	"image/color"
+
+	"gioui.org/io/event"
+	"gioui.org/io/key"
+	"gioui.org/op"
+
+	"gioui.org/gpu"
+	"gioui.org/io/pointer"
+	"gioui.org/io/system"
+	"gioui.org/unit"
+)
+
+// errOutOfDate is reported when the GPU surface dimensions or properties no
+// longer match the window.
+var errOutOfDate = errors.New("app: GPU surface out of date")
+
+// Config describes a Window configuration.
+type Config struct {
+	// Size is the window dimensions (Width, Height).
+	Size image.Point
+	// MaxSize is the window maximum allowed dimensions.
+	MaxSize image.Point
+	// MinSize is the window minimum allowed dimensions.
+	MinSize image.Point
+	// Title is the window title displayed in its decoration bar.
+	Title string
+	// WindowMode is the window mode.
+	Mode WindowMode
+	// StatusColor is the color of the Android status bar.
+	StatusColor color.NRGBA
+	// NavigationColor is the color of the navigation bar
+	// on Android, or the address bar in browsers.
+	NavigationColor color.NRGBA
+	// Orientation is the current window orientation.
+	Orientation Orientation
+	// CustomRenderer is true when the window content is rendered by the
+	// client.
+	CustomRenderer bool
+	// Decorated reports whether window decorations are provided automatically.
+	Decorated bool
+	// Focused reports whether has the keyboard focus.
+	Focused bool
+	// decoHeight is the height of the fallback decoration for platforms such
+	// as Wayland that may need fallback client-side decorations.
+	decoHeight unit.Dp
+}
+
+// ConfigEvent is sent whenever the configuration of a Window changes.
+type ConfigEvent struct {
+	Config Config
+}
+
+func (c *Config) apply(m unit.Metric, options []Option) {
+	for _, o := range options {
+		o(m, c)
+	}
+}
+
+type wakeupEvent struct{}
+
+// WindowMode is the window mode (WindowMode.Option sets it).
+// Note that mode can be changed programatically as well as by the user
+// clicking on the minimize/maximize buttons on the window's title bar.
+type WindowMode uint8
+
+const (
+	// Windowed is the normal window mode with OS specific window decorations.
+	Windowed WindowMode = iota
+	// Fullscreen is the full screen window mode.
+	Fullscreen
+	// Minimized is for systems where the window can be minimized to an icon.
+	Minimized
+	// Maximized is for systems where the window can be made to fill the available monitor area.
+	Maximized
+)
+
+// Option changes the mode of a Window.
+func (m WindowMode) Option() Option {
+	return func(_ unit.Metric, cnf *Config) {
+		cnf.Mode = m
+	}
+}
+
+// String returns the mode name.
+func (m WindowMode) String() string {
+	switch m {
+	case Windowed:
+		return "windowed"
+	case Fullscreen:
+		return "fullscreen"
+	case Minimized:
+		return "minimized"
+	case Maximized:
+		return "maximized"
+	}
+	return ""
+}
+
+// Orientation is the orientation of the app (Orientation.Option sets it).
+//
+// Supported platforms are Android and JS.
+type Orientation uint8
+
+const (
+	// AnyOrientation allows the window to be freely orientated.
+	AnyOrientation Orientation = iota
+	// LandscapeOrientation constrains the window to landscape orientations.
+	LandscapeOrientation
+	// PortraitOrientation constrains the window to portrait orientations.
+	PortraitOrientation
+)
+
+func (o Orientation) Option() Option {
+	return func(_ unit.Metric, cnf *Config) {
+		cnf.Orientation = o
+	}
+}
+
+func (o Orientation) String() string {
+	switch o {
+	case AnyOrientation:
+		return "any"
+	case LandscapeOrientation:
+		return "landscape"
+	case PortraitOrientation:
+		return "portrait"
+	}
+	return ""
+}
+
+// eventLoop implements the functionality required for drivers where
+// window event loops must run on a separate thread.
+type eventLoop struct {
+	win *callbacks
+	// wakeup is the callback to wake up the event loop.
+	wakeup func()
+	// driverFuncs is a channel of functions to run the next
+	// time the window loop waits for events.
+	driverFuncs chan func()
+	// invalidates is notified when an invalidate is requested by the client.
+	invalidates chan struct{}
+	// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
+	immediateInvalidates chan struct{}
+	// events is where the platform backend delivers events bound for the
+	// user program.
+	events   chan event.Event
+	frames   chan *op.Ops
+	frameAck chan struct{}
+	// delivering avoids re-entrant event delivery.
+	delivering bool
+}
+
+type frameEvent struct {
+	FrameEvent
+
+	Sync bool
+}
+
+type context interface {
+	API() gpu.API
+	RenderTarget() (gpu.RenderTarget, error)
+	Present() error
+	Refresh() error
+	Release()
+	Lock() error
+	Unlock()
+}
+
+// driver is the interface for the platform implementation
+// of a window.
+type driver interface {
+	// Event blocks until an event is available and returns it.
+	Event() event.Event
+	// Invalidate requests a FrameEvent.
+	Invalidate()
+	// SetAnimating sets the animation flag. When the window is animating,
+	// FrameEvents are delivered as fast as the display can handle them.
+	SetAnimating(anim bool)
+	// ShowTextInput updates the virtual keyboard state.
+	ShowTextInput(show bool)
+	SetInputHint(mode key.InputHint)
+	NewContext() (context, error)
+	// ReadClipboard requests the clipboard content.
+	ReadClipboard()
+	// WriteClipboard requests a clipboard write.
+	WriteClipboard(mime string, s []byte)
+	// Configure the window.
+	Configure([]Option)
+	// SetCursor updates the current cursor to name.
+	SetCursor(cursor pointer.Cursor)
+	// Perform actions on the window.
+	Perform(system.Action)
+	// EditorStateChanged notifies the driver that the editor state changed.
+	EditorStateChanged(old, new editorState)
+	// Run a function on the window thread.
+	Run(f func())
+	// Frame receives a frame.
+	Frame(frame *op.Ops)
+	// ProcessEvent processes an event.
+	ProcessEvent(e event.Event)
+}
+
+type windowRendezvous struct {
+	in      chan windowAndConfig
+	out     chan windowAndConfig
+	windows chan struct{}
+}
+
+type windowAndConfig struct {
+	window  *callbacks
+	options []Option
+}
+
+func newWindowRendezvous() *windowRendezvous {
+	wr := &windowRendezvous{
+		in:      make(chan windowAndConfig),
+		out:     make(chan windowAndConfig),
+		windows: make(chan struct{}),
+	}
+	go func() {
+		in := wr.in
+		var window windowAndConfig
+		var out chan windowAndConfig
+		for {
+			select {
+			case w := <-in:
+				window = w
+				out = wr.out
+			case out <- window:
+			}
+		}
+	}()
+	return wr
+}
+
+func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
+	return &eventLoop{
+		win:                  w,
+		wakeup:               wakeup,
+		events:               make(chan event.Event),
+		invalidates:          make(chan struct{}, 1),
+		immediateInvalidates: make(chan struct{}),
+		frames:               make(chan *op.Ops),
+		frameAck:             make(chan struct{}),
+		driverFuncs:          make(chan func(), 1),
+	}
+}
+
+// Frame receives a frame and waits for its processing. It is called by
+// the client goroutine.
+func (e *eventLoop) Frame(frame *op.Ops) {
+	e.frames <- frame
+	<-e.frameAck
+}
+
+// Event returns the next available event. It is called by the client
+// goroutine.
+func (e *eventLoop) Event() event.Event {
+	for {
+		evt := <-e.events
+		// Receiving a flushEvent indicates to the platform backend that
+		// all previous events have been processed by the user program.
+		if _, ok := evt.(flushEvent); ok {
+			continue
+		}
+		return evt
+	}
+}
+
+// Invalidate requests invalidation of the window. It is called by the client
+// goroutine.
+func (e *eventLoop) Invalidate() {
+	select {
+	case e.immediateInvalidates <- struct{}{}:
+		// The event loop was waiting, no need for a wakeup.
+	case e.invalidates <- struct{}{}:
+		// The event loop is sleeping, wake it up.
+		e.wakeup()
+	default:
+		// A redraw is pending.
+	}
+}
+
+// Run f in the window loop thread. It is called by the client goroutine.
+func (e *eventLoop) Run(f func()) {
+	e.driverFuncs <- f
+	e.wakeup()
+}
+
+// FlushEvents delivers pending events to the client.
+func (e *eventLoop) FlushEvents() {
+	if e.delivering {
+		return
+	}
+	e.delivering = true
+	defer func() { e.delivering = false }()
+	for {
+		evt, ok := e.win.nextEvent()
+		if !ok {
+			break
+		}
+		e.deliverEvent(evt)
+	}
+}
+
+func (e *eventLoop) deliverEvent(evt event.Event) {
+	var frames <-chan *op.Ops
+	for {
+		select {
+		case f := <-e.driverFuncs:
+			f()
+		case frame := <-frames:
+			// The client called FrameEvent.Frame.
+			frames = nil
+			e.win.ProcessFrame(frame, e.frameAck)
+		case e.events <- evt:
+			switch evt.(type) {
+			case flushEvent, DestroyEvent:
+				// DestroyEvents are not flushed.
+				return
+			case FrameEvent:
+				frames = e.frames
+			}
+			evt = theFlushEvent
+		case <-e.invalidates:
+			e.win.Invalidate()
+		case <-e.immediateInvalidates:
+			e.win.Invalidate()
+		}
+	}
+}
+
+func (e *eventLoop) Wakeup() {
+	for {
+		select {
+		case f := <-e.driverFuncs:
+			f()
+		case <-e.invalidates:
+			e.win.Invalidate()
+		case <-e.immediateInvalidates:
+			e.win.Invalidate()
+		default:
+			return
+		}
+	}
+}
+
+func walkActions(actions system.Action, do func(system.Action)) {
+	for a := system.Action(1); actions != 0; a <<= 1 {
+		if actions&a != 0 {
+			actions &^= a
+			do(a)
+		}
+	}
+}
+
+func (wakeupEvent) ImplementsEvent() {}
+func (ConfigEvent) ImplementsEvent() {}

+ 1500 - 0
vendor/gioui.org/app/os_android.go

@@ -0,0 +1,1500 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+/*
+#cgo CFLAGS: -Werror
+#cgo LDFLAGS: -landroid
+
+#include <android/native_window_jni.h>
+#include <android/configuration.h>
+#include <android/keycodes.h>
+#include <android/input.h>
+#include <stdlib.h>
+
+static jint jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
+	return (*vm)->GetEnv(vm, (void **)env, version);
+}
+
+static jint jni_GetJavaVM(JNIEnv *env, JavaVM **jvm) {
+	return (*env)->GetJavaVM(env, jvm);
+}
+
+static jint jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
+	return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
+}
+
+static jint jni_DetachCurrentThread(JavaVM *vm) {
+	return (*vm)->DetachCurrentThread(vm);
+}
+
+static jobject jni_NewGlobalRef(JNIEnv *env, jobject obj) {
+	return (*env)->NewGlobalRef(env, obj);
+}
+
+static void jni_DeleteGlobalRef(JNIEnv *env, jobject obj) {
+	(*env)->DeleteGlobalRef(env, obj);
+}
+
+static jclass jni_GetObjectClass(JNIEnv *env, jobject obj) {
+	return (*env)->GetObjectClass(env, obj);
+}
+
+static jmethodID jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
+	return (*env)->GetMethodID(env, clazz, name, sig);
+}
+
+static jmethodID jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
+	return (*env)->GetStaticMethodID(env, clazz, name, sig);
+}
+
+static jfloat jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
+	return (*env)->CallFloatMethod(env, obj, methodID);
+}
+
+static jint jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
+	return (*env)->CallIntMethod(env, obj, methodID);
+}
+
+static void jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args) {
+	(*env)->CallStaticVoidMethodA(env, cls, methodID, args);
+}
+
+static void jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) {
+	(*env)->CallVoidMethodA(env, obj, methodID, args);
+}
+
+static jboolean jni_CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) {
+	return (*env)->CallBooleanMethodA(env, obj, methodID, args);
+}
+
+static jbyte *jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) {
+	return (*env)->GetByteArrayElements(env, arr, NULL);
+}
+
+static void jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) {
+	(*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT);
+}
+
+static jsize jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
+	return (*env)->GetArrayLength(env, arr);
+}
+
+static jstring jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) {
+	return (*env)->NewString(env, unicodeChars, len);
+}
+
+static jsize jni_GetStringLength(JNIEnv *env, jstring str) {
+	return (*env)->GetStringLength(env, str);
+}
+
+static const jchar *jni_GetStringChars(JNIEnv *env, jstring str) {
+	return (*env)->GetStringChars(env, str, NULL);
+}
+
+static jthrowable jni_ExceptionOccurred(JNIEnv *env) {
+	return (*env)->ExceptionOccurred(env);
+}
+
+static void jni_ExceptionClear(JNIEnv *env) {
+	(*env)->ExceptionClear(env);
+}
+
+static jobject jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) {
+	return (*env)->CallObjectMethodA(env, obj, method, args);
+}
+
+static jobject jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) {
+	return (*env)->CallStaticObjectMethodA(env, cls, method, args);
+}
+
+static jclass jni_FindClass(JNIEnv *env, char *name) {
+	return (*env)->FindClass(env, name);
+}
+
+static jobject jni_NewObjectA(JNIEnv *env, jclass cls, jmethodID cons, jvalue *args) {
+	return (*env)->NewObjectA(env, cls, cons, args);
+}
+*/
+import "C"
+
+import (
+	"errors"
+	"fmt"
+	"image"
+	"image/color"
+	"io"
+	"math"
+	"os"
+	"path/filepath"
+	"runtime"
+	"runtime/cgo"
+	"runtime/debug"
+	"strings"
+	"sync"
+	"time"
+	"unicode/utf16"
+	"unsafe"
+
+	"gioui.org/internal/f32color"
+	"gioui.org/op"
+
+	"gioui.org/f32"
+	"gioui.org/io/event"
+	"gioui.org/io/input"
+	"gioui.org/io/key"
+	"gioui.org/io/pointer"
+	"gioui.org/io/semantic"
+	"gioui.org/io/system"
+	"gioui.org/io/transfer"
+	"gioui.org/unit"
+)
+
+type window struct {
+	callbacks *callbacks
+	loop      *eventLoop
+
+	view   C.jobject
+	handle cgo.Handle
+
+	dpi       int
+	fontScale float32
+	insets    pixelInsets
+
+	visible   bool
+	started   bool
+	animating bool
+
+	win       *C.ANativeWindow
+	config    Config
+	inputHint key.InputHint
+
+	semantic struct {
+		hoverID input.SemanticID
+		rootID  input.SemanticID
+		focusID input.SemanticID
+		diffs   []input.SemanticID
+	}
+}
+
+// gioView hold cached JNI methods for GioView.
+var gioView struct {
+	once               sync.Once
+	getDensity         C.jmethodID
+	getFontScale       C.jmethodID
+	showTextInput      C.jmethodID
+	hideTextInput      C.jmethodID
+	setInputHint       C.jmethodID
+	postFrameCallback  C.jmethodID
+	invalidate         C.jmethodID // requests draw, called from UI thread
+	setCursor          C.jmethodID
+	setOrientation     C.jmethodID
+	setNavigationColor C.jmethodID
+	setStatusColor     C.jmethodID
+	setFullscreen      C.jmethodID
+	unregister         C.jmethodID
+	sendA11yEvent      C.jmethodID
+	sendA11yChange     C.jmethodID
+	isA11yActive       C.jmethodID
+	restartInput       C.jmethodID
+	updateSelection    C.jmethodID
+	updateCaret        C.jmethodID
+}
+
+type pixelInsets struct {
+	top, bottom, left, right int
+}
+
+// AndroidViewEvent is sent whenever the Window's underlying Android view
+// changes.
+type AndroidViewEvent struct {
+	// View is a JNI global reference to the android.view.View
+	// instance backing the Window. The reference is valid until
+	// the next ViewEvent is received.
+	// A zero View means that there is currently no view attached.
+	View uintptr
+}
+
+type jvalue uint64 // The largest JNI type fits in 64 bits.
+
+var dataDirChan = make(chan string, 1)
+
+var android struct {
+	// mu protects all fields of this structure. However, once a
+	// non-nil jvm is returned from javaVM, all the other fields may
+	// be accessed unlocked.
+	mu  sync.Mutex
+	jvm *C.JavaVM
+
+	// appCtx is the global Android App context.
+	appCtx C.jobject
+	// gioCls is the class of the Gio class.
+	gioCls C.jclass
+
+	mwriteClipboard   C.jmethodID
+	mreadClipboard    C.jmethodID
+	mwakeupMainThread C.jmethodID
+
+	// android.view.accessibility.AccessibilityNodeInfo class.
+	accessibilityNodeInfo struct {
+		cls C.jclass
+		// addChild(View, int)
+		addChild C.jmethodID
+		// setBoundsInScreen(Rect)
+		setBoundsInScreen C.jmethodID
+		// setText(CharSequence)
+		setText C.jmethodID
+		// setContentDescription(CharSequence)
+		setContentDescription C.jmethodID
+		// setParent(View, int)
+		setParent C.jmethodID
+		// addAction(int)
+		addAction C.jmethodID
+		// setClassName(CharSequence)
+		setClassName C.jmethodID
+		// setCheckable(boolean)
+		setCheckable C.jmethodID
+		// setSelected(boolean)
+		setSelected C.jmethodID
+		// setChecked(boolean)
+		setChecked C.jmethodID
+		// setEnabled(boolean)
+		setEnabled C.jmethodID
+		// setAccessibilityFocused(boolean)
+		setAccessibilityFocused C.jmethodID
+	}
+
+	// android.graphics.Rect class.
+	rect struct {
+		cls C.jclass
+		// (int, int, int, int) constructor.
+		cons C.jmethodID
+	}
+
+	strings struct {
+		// "android.view.View"
+		androidViewView C.jstring
+		// "android.widget.Button"
+		androidWidgetButton C.jstring
+		// "android.widget.CheckBox"
+		androidWidgetCheckBox C.jstring
+		// "android.widget.EditText"
+		androidWidgetEditText C.jstring
+		// "android.widget.RadioButton"
+		androidWidgetRadioButton C.jstring
+		// "android.widget.Switch"
+		androidWidgetSwitch C.jstring
+	}
+}
+
+var windows = make(map[*callbacks]*window)
+
+var mainWindow = newWindowRendezvous()
+
+var mainFuncs = make(chan func(env *C.JNIEnv), 1)
+
+var (
+	dataDirOnce sync.Once
+	dataPath    string
+)
+
+var (
+	newAndroidVulkanContext func(w *window) (context, error)
+	newAndroidGLESContext   func(w *window) (context, error)
+)
+
+// AccessibilityNodeProvider.HOST_VIEW_ID.
+const HOST_VIEW_ID = -1
+
+const (
+	// AccessibilityEvent constants.
+	TYPE_VIEW_HOVER_ENTER = 128
+	TYPE_VIEW_HOVER_EXIT  = 256
+)
+
+const (
+	// AccessibilityNodeInfo constants.
+	ACTION_ACCESSIBILITY_FOCUS       = 64
+	ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128
+	ACTION_CLICK                     = 16
+)
+
+func (w *window) NewContext() (context, error) {
+	funcs := []func(w *window) (context, error){newAndroidGLESContext, newAndroidVulkanContext}
+	var firstErr error
+	for _, f := range funcs {
+		if f == nil {
+			continue
+		}
+		c, err := f(w)
+		if err != nil {
+			if firstErr == nil {
+				firstErr = err
+			}
+			continue
+		}
+		return c, nil
+	}
+	if firstErr != nil {
+		return nil, firstErr
+	}
+	return nil, errors.New("x11: no available GPU backends")
+}
+
+func dataDir() (string, error) {
+	dataDirOnce.Do(func() {
+		dataPath = <-dataDirChan
+	})
+	return dataPath, nil
+}
+
+func getMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
+	m := C.CString(method)
+	defer C.free(unsafe.Pointer(m))
+	s := C.CString(sig)
+	defer C.free(unsafe.Pointer(s))
+	jm := C.jni_GetMethodID(env, class, m, s)
+	if err := exception(env); err != nil {
+		panic(err)
+	}
+	return jm
+}
+
+func getStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
+	m := C.CString(method)
+	defer C.free(unsafe.Pointer(m))
+	s := C.CString(sig)
+	defer C.free(unsafe.Pointer(s))
+	jm := C.jni_GetStaticMethodID(env, class, m, s)
+	if err := exception(env); err != nil {
+		panic(err)
+	}
+	return jm
+}
+
+//export Java_org_gioui_Gio_runGoMain
+func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) {
+	initJVM(env, class, context)
+	dirBytes := C.jni_GetByteArrayElements(env, jdataDir)
+	if dirBytes == nil {
+		panic("runGoMain: GetByteArrayElements failed")
+	}
+	n := C.jni_GetArrayLength(env, jdataDir)
+	dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
+
+	// Set XDG_CACHE_HOME to make os.UserCacheDir work.
+	if _, exists := os.LookupEnv("XDG_CACHE_HOME"); !exists {
+		cachePath := filepath.Join(dataDir, "cache")
+		os.Setenv("XDG_CACHE_HOME", cachePath)
+	}
+	// Set XDG_CONFIG_HOME to make os.UserConfigDir work.
+	if _, exists := os.LookupEnv("XDG_CONFIG_HOME"); !exists {
+		cfgPath := filepath.Join(dataDir, "config")
+		os.Setenv("XDG_CONFIG_HOME", cfgPath)
+	}
+	// Set HOME to make os.UserHomeDir work.
+	if _, exists := os.LookupEnv("HOME"); !exists {
+		os.Setenv("HOME", dataDir)
+	}
+
+	dataDirChan <- dataDir
+	C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
+
+	runMain()
+}
+
+func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) {
+	android.mu.Lock()
+	defer android.mu.Unlock()
+	if res := C.jni_GetJavaVM(env, &android.jvm); res != 0 {
+		panic("gio: GetJavaVM failed")
+	}
+	android.appCtx = C.jni_NewGlobalRef(env, ctx)
+	android.gioCls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(gio)))
+
+	cls := findClass(env, "android/view/accessibility/AccessibilityNodeInfo")
+	android.accessibilityNodeInfo.cls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(cls)))
+	android.accessibilityNodeInfo.addChild = getMethodID(env, cls, "addChild", "(Landroid/view/View;I)V")
+	android.accessibilityNodeInfo.setBoundsInScreen = getMethodID(env, cls, "setBoundsInScreen", "(Landroid/graphics/Rect;)V")
+	android.accessibilityNodeInfo.setText = getMethodID(env, cls, "setText", "(Ljava/lang/CharSequence;)V")
+	android.accessibilityNodeInfo.setContentDescription = getMethodID(env, cls, "setContentDescription", "(Ljava/lang/CharSequence;)V")
+	android.accessibilityNodeInfo.setParent = getMethodID(env, cls, "setParent", "(Landroid/view/View;I)V")
+	android.accessibilityNodeInfo.addAction = getMethodID(env, cls, "addAction", "(I)V")
+	android.accessibilityNodeInfo.setClassName = getMethodID(env, cls, "setClassName", "(Ljava/lang/CharSequence;)V")
+	android.accessibilityNodeInfo.setCheckable = getMethodID(env, cls, "setCheckable", "(Z)V")
+	android.accessibilityNodeInfo.setSelected = getMethodID(env, cls, "setSelected", "(Z)V")
+	android.accessibilityNodeInfo.setChecked = getMethodID(env, cls, "setChecked", "(Z)V")
+	android.accessibilityNodeInfo.setEnabled = getMethodID(env, cls, "setEnabled", "(Z)V")
+	android.accessibilityNodeInfo.setAccessibilityFocused = getMethodID(env, cls, "setAccessibilityFocused", "(Z)V")
+
+	cls = findClass(env, "android/graphics/Rect")
+	android.rect.cls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(cls)))
+	android.rect.cons = getMethodID(env, cls, "<init>", "(IIII)V")
+	android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V")
+	android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;")
+	android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V")
+
+	intern := func(s string) C.jstring {
+		ref := C.jni_NewGlobalRef(env, C.jobject(javaString(env, s)))
+		return C.jstring(ref)
+	}
+	android.strings.androidViewView = intern("android.view.View")
+	android.strings.androidWidgetButton = intern("android.widget.Button")
+	android.strings.androidWidgetCheckBox = intern("android.widget.CheckBox")
+	android.strings.androidWidgetEditText = intern("android.widget.EditText")
+	android.strings.androidWidgetRadioButton = intern("android.widget.RadioButton")
+	android.strings.androidWidgetSwitch = intern("android.widget.Switch")
+}
+
+// JavaVM returns the global JNI JavaVM.
+func JavaVM() uintptr {
+	jvm := javaVM()
+	return uintptr(unsafe.Pointer(jvm))
+}
+
+func javaVM() *C.JavaVM {
+	android.mu.Lock()
+	defer android.mu.Unlock()
+	return android.jvm
+}
+
+// AppContext returns the global Application context as a JNI jobject.
+func AppContext() uintptr {
+	android.mu.Lock()
+	defer android.mu.Unlock()
+	return uintptr(android.appCtx)
+}
+
+//export Java_org_gioui_GioView_onCreateView
+func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
+	gioView.once.Do(func() {
+		m := &gioView
+		m.getDensity = getMethodID(env, class, "getDensity", "()I")
+		m.getFontScale = getMethodID(env, class, "getFontScale", "()F")
+		m.showTextInput = getMethodID(env, class, "showTextInput", "()V")
+		m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V")
+		m.setInputHint = getMethodID(env, class, "setInputHint", "(I)V")
+		m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V")
+		m.invalidate = getMethodID(env, class, "invalidate", "()V")
+		m.setCursor = getMethodID(env, class, "setCursor", "(I)V")
+		m.setOrientation = getMethodID(env, class, "setOrientation", "(II)V")
+		m.setNavigationColor = getMethodID(env, class, "setNavigationColor", "(II)V")
+		m.setStatusColor = getMethodID(env, class, "setStatusColor", "(II)V")
+		m.setFullscreen = getMethodID(env, class, "setFullscreen", "(Z)V")
+		m.unregister = getMethodID(env, class, "unregister", "()V")
+		m.sendA11yEvent = getMethodID(env, class, "sendA11yEvent", "(II)V")
+		m.sendA11yChange = getMethodID(env, class, "sendA11yChange", "(I)V")
+		m.isA11yActive = getMethodID(env, class, "isA11yActive", "()Z")
+		m.restartInput = getMethodID(env, class, "restartInput", "()V")
+		m.updateSelection = getMethodID(env, class, "updateSelection", "()V")
+		m.updateCaret = getMethodID(env, class, "updateCaret", "(FFFFFFFFFF)V")
+	})
+	view = C.jni_NewGlobalRef(env, view)
+	wopts := <-mainWindow.out
+	var cnf Config
+	w, ok := windows[wopts.window]
+	if !ok {
+		w = &window{
+			callbacks: wopts.window,
+		}
+		w.loop = newEventLoop(w.callbacks, w.wakeup)
+		w.callbacks.SetDriver(w)
+		cnf.apply(unit.Metric{}, wopts.options)
+		windows[wopts.window] = w
+	} else {
+		cnf = w.config
+	}
+	mainWindow.windows <- struct{}{}
+	if w.view != 0 {
+		w.detach(env)
+	}
+	w.view = view
+	w.visible = false
+	w.handle = cgo.NewHandle(w)
+	w.loadConfig(env, class)
+	w.setConfig(env, cnf)
+	w.SetInputHint(w.inputHint)
+	w.processEvent(AndroidViewEvent{View: uintptr(view)})
+	return C.jlong(w.handle)
+}
+
+//export Java_org_gioui_GioView_onDestroyView
+func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
+	w := cgo.Handle(handle).Value().(*window)
+	w.detach(env)
+}
+
+//export Java_org_gioui_GioView_onStopView
+func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
+	w := cgo.Handle(handle).Value().(*window)
+	w.started = false
+	w.visible = false
+}
+
+//export Java_org_gioui_GioView_onStartView
+func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
+	w := cgo.Handle(handle).Value().(*window)
+	w.started = true
+	if w.win != nil {
+		w.setVisible(env)
+	}
+}
+
+//export Java_org_gioui_GioView_onSurfaceDestroyed
+func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
+	w := cgo.Handle(handle).Value().(*window)
+	w.win = nil
+	w.visible = false
+}
+
+//export Java_org_gioui_GioView_onSurfaceChanged
+func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
+	w := cgo.Handle(handle).Value().(*window)
+	w.win = C.ANativeWindow_fromSurface(env, surf)
+	if w.started {
+		w.setVisible(env)
+	}
+}
+
+//export Java_org_gioui_GioView_onLowMemory
+func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
+	runtime.GC()
+	debug.FreeOSMemory()
+}
+
+//export Java_org_gioui_GioView_onConfigurationChanged
+func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
+	w := cgo.Handle(view).Value().(*window)
+	w.loadConfig(env, class)
+	w.draw(env, true)
+}
+
+//export Java_org_gioui_GioView_onFrameCallback
+func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong) {
+	w, exist := cgo.Handle(view).Value().(*window)
+	if !exist {
+		return
+	}
+	w.draw(env, false)
+}
+
+//export Java_org_gioui_GioView_onBack
+func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
+	w := cgo.Handle(view).Value().(*window)
+	if w.processEvent(key.Event{Name: key.NameBack}) {
+		return C.JNI_TRUE
+	}
+	return C.JNI_FALSE
+}
+
+//export Java_org_gioui_GioView_onFocusChange
+func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
+	w := cgo.Handle(view).Value().(*window)
+	w.config.Focused = focus == C.JNI_TRUE
+	w.processEvent(ConfigEvent{Config: w.config})
+}
+
+//export Java_org_gioui_GioView_onWindowInsets
+func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
+	w := cgo.Handle(view).Value().(*window)
+	w.insets = pixelInsets{
+		top:    int(top),
+		bottom: int(bottom),
+		left:   int(left),
+		right:  int(right),
+	}
+	w.draw(env, true)
+}
+
+//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
+func Java_org_gioui_GioView_initializeAccessibilityNodeInfo(env *C.JNIEnv, class C.jclass, view C.jlong, virtID, screenX, screenY C.jint, info C.jobject) C.jobject {
+	w := cgo.Handle(view).Value().(*window)
+	semID := w.semIDFor(virtID)
+	sem, found := w.callbacks.LookupSemantic(semID)
+	if found {
+		off := image.Pt(int(screenX), int(screenY))
+		if err := w.initAccessibilityNodeInfo(env, sem, off, info); err != nil {
+			panic(err)
+		}
+	}
+	return info
+}
+
+//export Java_org_gioui_GioView_onTouchExploration
+func Java_org_gioui_GioView_onTouchExploration(env *C.JNIEnv, class C.jclass, view C.jlong, x, y C.jfloat) {
+	w := cgo.Handle(view).Value().(*window)
+	semID, _ := w.callbacks.SemanticAt(f32.Pt(float32(x), float32(y)))
+	if w.semantic.hoverID == semID {
+		return
+	}
+	// Android expects ENTER before EXIT.
+	if semID != 0 {
+		callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_ENTER, jvalue(w.virtualIDFor(semID)))
+	}
+	if prevID := w.semantic.hoverID; prevID != 0 {
+		callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_EXIT, jvalue(w.virtualIDFor(prevID)))
+	}
+	w.semantic.hoverID = semID
+}
+
+//export Java_org_gioui_GioView_onExitTouchExploration
+func Java_org_gioui_GioView_onExitTouchExploration(env *C.JNIEnv, class C.jclass, view C.jlong) {
+	w := cgo.Handle(view).Value().(*window)
+	if w.semantic.hoverID != 0 {
+		callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_EXIT, jvalue(w.virtualIDFor(w.semantic.hoverID)))
+		w.semantic.hoverID = 0
+	}
+}
+
+//export Java_org_gioui_GioView_onA11yFocus
+func Java_org_gioui_GioView_onA11yFocus(env *C.JNIEnv, class C.jclass, view C.jlong, virtID C.jint) {
+	w := cgo.Handle(view).Value().(*window)
+	if semID := w.semIDFor(virtID); semID != w.semantic.focusID {
+		w.semantic.focusID = semID
+		// Android needs invalidate to refresh the TalkBack focus indicator.
+		callVoidMethod(env, w.view, gioView.invalidate)
+	}
+}
+
+//export Java_org_gioui_GioView_onClearA11yFocus
+func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view C.jlong, virtID C.jint) {
+	w := cgo.Handle(view).Value().(*window)
+	if w.semantic.focusID == w.semIDFor(virtID) {
+		w.semantic.focusID = 0
+	}
+}
+
+func (w *window) ProcessEvent(e event.Event) {
+	w.processEvent(e)
+}
+
+func (w *window) processEvent(e event.Event) bool {
+	if !w.callbacks.ProcessEvent(e) {
+		return false
+	}
+	w.loop.FlushEvents()
+	return true
+}
+
+func (w *window) Event() event.Event {
+	return w.loop.Event()
+}
+
+func (w *window) Invalidate() {
+	w.loop.Invalidate()
+}
+
+func (w *window) Run(f func()) {
+	w.loop.Run(f)
+}
+
+func (w *window) Frame(frame *op.Ops) {
+	w.loop.Frame(frame)
+}
+
+func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error {
+	for _, ch := range sem.Children {
+		err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
+		if err != nil {
+			return err
+		}
+	}
+	if sem.ParentID != 0 {
+		if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setParent, jvalue(w.view), jvalue(w.virtualIDFor(sem.ParentID))); err != nil {
+			return err
+		}
+		b := sem.Desc.Bounds.Add(off)
+		rect, err := newObject(env, android.rect.cls, android.rect.cons,
+			jvalue(b.Min.X),
+			jvalue(b.Min.Y),
+			jvalue(b.Max.X),
+			jvalue(b.Max.Y),
+		)
+		if err != nil {
+			return err
+		}
+		if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setBoundsInScreen, jvalue(rect)); err != nil {
+			return err
+		}
+	}
+	d := sem.Desc
+	if l := d.Label; l != "" {
+		jlbl := javaString(env, l)
+		if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setText, jvalue(jlbl)); err != nil {
+			return err
+		}
+	}
+	if d.Description != "" {
+		jd := javaString(env, d.Description)
+		if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setContentDescription, jvalue(jd)); err != nil {
+			return err
+		}
+	}
+	addAction := func(act C.jint) {
+		if err := callVoidMethod(env, info, android.accessibilityNodeInfo.addAction, jvalue(act)); err != nil {
+			panic(err)
+		}
+	}
+	if d.Gestures&input.ClickGesture != 0 {
+		addAction(ACTION_CLICK)
+	}
+	clsName := android.strings.androidViewView
+	selectMethod := android.accessibilityNodeInfo.setChecked
+	checkable := false
+	switch d.Class {
+	case semantic.Button:
+		clsName = android.strings.androidWidgetButton
+	case semantic.CheckBox:
+		checkable = true
+		clsName = android.strings.androidWidgetCheckBox
+	case semantic.Editor:
+		clsName = android.strings.androidWidgetEditText
+	case semantic.RadioButton:
+		selectMethod = android.accessibilityNodeInfo.setSelected
+		clsName = android.strings.androidWidgetRadioButton
+	case semantic.Switch:
+		checkable = true
+		clsName = android.strings.androidWidgetSwitch
+	}
+	if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setClassName, jvalue(clsName)); err != nil {
+		panic(err)
+	}
+	if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setCheckable, jvalue(javaBool(checkable))); err != nil {
+		panic(err)
+	}
+	if err := callVoidMethod(env, info, selectMethod, jvalue(javaBool(d.Selected))); err != nil {
+		panic(err)
+	}
+	if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setEnabled, jvalue(javaBool(!d.Disabled))); err != nil {
+		panic(err)
+	}
+	isFocus := w.semantic.focusID == sem.ID
+	if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setAccessibilityFocused, jvalue(javaBool(isFocus))); err != nil {
+		panic(err)
+	}
+	if isFocus {
+		addAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS)
+	} else {
+		addAction(ACTION_ACCESSIBILITY_FOCUS)
+	}
+	return nil
+}
+
+func (w *window) virtualIDFor(id input.SemanticID) C.jint {
+	if id == w.semantic.rootID {
+		return HOST_VIEW_ID
+	}
+	return C.jint(id)
+}
+
+func (w *window) semIDFor(virtID C.jint) input.SemanticID {
+	if virtID == HOST_VIEW_ID {
+		return w.semantic.rootID
+	}
+	return input.SemanticID(virtID)
+}
+
+func (w *window) detach(env *C.JNIEnv) {
+	callVoidMethod(env, w.view, gioView.unregister)
+	w.processEvent(AndroidViewEvent{})
+	w.handle.Delete()
+	C.jni_DeleteGlobalRef(env, w.view)
+	w.view = 0
+}
+
+func (w *window) setVisible(env *C.JNIEnv) {
+	width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
+	if width == 0 || height == 0 {
+		return
+	}
+	w.visible = true
+	w.draw(env, true)
+}
+
+func (w *window) setVisual(visID int) error {
+	if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
+		return errors.New("ANativeWindow_setBuffersGeometry failed")
+	}
+	return nil
+}
+
+func (w *window) nativeWindow() (*C.ANativeWindow, int, int) {
+	width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
+	return w.win, int(width), int(height)
+}
+
+func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
+	dpi := int(C.jni_CallIntMethod(env, w.view, gioView.getDensity))
+	w.fontScale = float32(C.jni_CallFloatMethod(env, w.view, gioView.getFontScale))
+	switch dpi {
+	case C.ACONFIGURATION_DENSITY_NONE,
+		C.ACONFIGURATION_DENSITY_DEFAULT,
+		C.ACONFIGURATION_DENSITY_ANY:
+		// Assume standard density.
+		w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
+	default:
+		w.dpi = int(dpi)
+	}
+}
+
+func (w *window) SetAnimating(anim bool) {
+	w.animating = anim
+	if anim {
+		runInJVM(javaVM(), func(env *C.JNIEnv) {
+			callVoidMethod(env, w.view, gioView.postFrameCallback)
+		})
+	}
+}
+
+func (w *window) draw(env *C.JNIEnv, sync bool) {
+	if !w.visible {
+		return
+	}
+	size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
+	if size != w.config.Size {
+		w.config.Size = size
+		w.processEvent(ConfigEvent{Config: w.config})
+	}
+	if size.X == 0 || size.Y == 0 {
+		return
+	}
+	const inchPrDp = 1.0 / 160
+	ppdp := float32(w.dpi) * inchPrDp
+	dppp := unit.Dp(1.0 / ppdp)
+	insets := Insets{
+		Top:    unit.Dp(w.insets.top) * dppp,
+		Bottom: unit.Dp(w.insets.bottom) * dppp,
+		Left:   unit.Dp(w.insets.left) * dppp,
+		Right:  unit.Dp(w.insets.right) * dppp,
+	}
+	w.processEvent(frameEvent{
+		FrameEvent: FrameEvent{
+			Now:    time.Now(),
+			Size:   w.config.Size,
+			Insets: insets,
+			Metric: unit.Metric{
+				PxPerDp: ppdp,
+				PxPerSp: w.fontScale * ppdp,
+			},
+		},
+		Sync: sync,
+	})
+	if w.animating {
+		callVoidMethod(env, w.view, gioView.postFrameCallback)
+	}
+	a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
+	if err != nil {
+		panic(err)
+	}
+	if a11yActive {
+		if newR, oldR := w.callbacks.SemanticRoot(), w.semantic.rootID; newR != oldR {
+			// Remap focus and hover.
+			if oldR == w.semantic.hoverID {
+				w.semantic.hoverID = newR
+			}
+			if oldR == w.semantic.focusID {
+				w.semantic.focusID = newR
+			}
+			w.semantic.rootID = newR
+			callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(newR)))
+		}
+		w.semantic.diffs = w.callbacks.AppendSemanticDiffs(w.semantic.diffs[:0])
+		for _, id := range w.semantic.diffs {
+			callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(id)))
+		}
+	}
+}
+
+func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
+	if jvm == nil {
+		panic("nil JVM")
+	}
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+	var env *C.JNIEnv
+	if res := C.jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
+		if res != C.JNI_EDETACHED {
+			panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
+		}
+		if C.jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK {
+			panic(errors.New("runInJVM: AttachCurrentThread failed"))
+		}
+		defer C.jni_DetachCurrentThread(jvm)
+	}
+
+	f(env)
+}
+
+func convertKeyCode(code C.jint) (key.Name, bool) {
+	var n key.Name
+	switch code {
+	case C.AKEYCODE_FORWARD_DEL:
+		n = key.NameDeleteForward
+	case C.AKEYCODE_DEL:
+		n = key.NameDeleteBackward
+	case C.AKEYCODE_NUMPAD_ENTER:
+		n = key.NameEnter
+	case C.AKEYCODE_ENTER:
+		n = key.NameReturn
+	case C.AKEYCODE_CTRL_LEFT, C.AKEYCODE_CTRL_RIGHT:
+		n = key.NameCtrl
+	case C.AKEYCODE_SHIFT_LEFT, C.AKEYCODE_SHIFT_RIGHT:
+		n = key.NameShift
+	case C.AKEYCODE_ALT_LEFT, C.AKEYCODE_ALT_RIGHT:
+		n = key.NameAlt
+	case C.AKEYCODE_META_LEFT, C.AKEYCODE_META_RIGHT:
+		n = key.NameSuper
+	case C.AKEYCODE_DPAD_UP:
+		n = key.NameUpArrow
+	case C.AKEYCODE_DPAD_DOWN:
+		n = key.NameDownArrow
+	case C.AKEYCODE_DPAD_LEFT:
+		n = key.NameLeftArrow
+	case C.AKEYCODE_DPAD_RIGHT:
+		n = key.NameRightArrow
+	default:
+		return "", false
+	}
+	return n, true
+}
+
+//export Java_org_gioui_GioView_onKeyEvent
+func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, pressed C.jboolean, t C.jlong) {
+	w := cgo.Handle(handle).Value().(*window)
+	if pressed == C.JNI_TRUE && keyCode == C.AKEYCODE_DPAD_CENTER {
+		w.callbacks.ClickFocus()
+		return
+	}
+	if n, ok := convertKeyCode(keyCode); ok {
+		state := key.Release
+		if pressed == C.JNI_TRUE {
+			state = key.Press
+		}
+		w.processEvent(key.Event{Name: n, State: state})
+	}
+	if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
+		w.callbacks.EditorInsert(string(rune(r)))
+	}
+}
+
+//export Java_org_gioui_GioView_onTouchEvent
+func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) {
+	w := cgo.Handle(handle).Value().(*window)
+	var kind pointer.Kind
+	switch action {
+	case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
+		kind = pointer.Press
+	case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
+		kind = pointer.Release
+	case C.AMOTION_EVENT_ACTION_CANCEL:
+		kind = pointer.Cancel
+	case C.AMOTION_EVENT_ACTION_MOVE:
+		kind = pointer.Move
+	case C.AMOTION_EVENT_ACTION_SCROLL:
+		kind = pointer.Scroll
+	default:
+		return
+	}
+	var src pointer.Source
+	var btns pointer.Buttons
+	if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 {
+		btns |= pointer.ButtonPrimary
+	}
+	if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 {
+		btns |= pointer.ButtonSecondary
+	}
+	if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 {
+		btns |= pointer.ButtonTertiary
+	}
+	switch tool {
+	case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
+		src = pointer.Touch
+	case C.AMOTION_EVENT_TOOL_TYPE_STYLUS:
+		src = pointer.Touch
+	case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
+		src = pointer.Mouse
+	case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
+		// For example, triggered via 'adb shell input tap'.
+		// Instead of discarding it, treat it as a touch event.
+		src = pointer.Touch
+	default:
+		return
+	}
+	w.processEvent(pointer.Event{
+		Kind:      kind,
+		Source:    src,
+		Buttons:   btns,
+		PointerID: pointer.ID(pointerID),
+		Time:      time.Duration(t) * time.Millisecond,
+		Position:  f32.Point{X: float32(x), Y: float32(y)},
+		Scroll:    f32.Pt(float32(scrollX), float32(scrollY)),
+	})
+}
+
+//export Java_org_gioui_GioView_imeSelectionStart
+func Java_org_gioui_GioView_imeSelectionStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint {
+	w := cgo.Handle(handle).Value().(*window)
+	sel := w.callbacks.EditorState().Selection
+	start := sel.Start
+	if sel.End < sel.Start {
+		start = sel.End
+	}
+	return C.jint(start)
+}
+
+//export Java_org_gioui_GioView_imeSelectionEnd
+func Java_org_gioui_GioView_imeSelectionEnd(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint {
+	w := cgo.Handle(handle).Value().(*window)
+	sel := w.callbacks.EditorState().Selection
+	end := sel.End
+	if sel.End < sel.Start {
+		end = sel.Start
+	}
+	return C.jint(end)
+}
+
+//export Java_org_gioui_GioView_imeComposingStart
+func Java_org_gioui_GioView_imeComposingStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint {
+	w := cgo.Handle(handle).Value().(*window)
+	comp := w.callbacks.EditorState().compose
+	start := comp.Start
+	if e := comp.End; e < start {
+		start = e
+	}
+	return C.jint(start)
+}
+
+//export Java_org_gioui_GioView_imeComposingEnd
+func Java_org_gioui_GioView_imeComposingEnd(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint {
+	w := cgo.Handle(handle).Value().(*window)
+	comp := w.callbacks.EditorState().compose
+	end := comp.End
+	if s := comp.Start; s > end {
+		end = s
+	}
+	return C.jint(end)
+}
+
+//export Java_org_gioui_GioView_imeSnippet
+func Java_org_gioui_GioView_imeSnippet(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jstring {
+	w := cgo.Handle(handle).Value().(*window)
+	snip := w.callbacks.EditorState().Snippet.Text
+	return javaString(env, snip)
+}
+
+//export Java_org_gioui_GioView_imeSnippetStart
+func Java_org_gioui_GioView_imeSnippetStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint {
+	w := cgo.Handle(handle).Value().(*window)
+	return C.jint(w.callbacks.EditorState().Snippet.Start)
+}
+
+//export Java_org_gioui_GioView_imeSetSnippet
+func Java_org_gioui_GioView_imeSetSnippet(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) {
+	w := cgo.Handle(handle).Value().(*window)
+	if start < 0 {
+		start = 0
+	}
+	if end < start {
+		end = start
+	}
+	r := key.Range{Start: int(start), End: int(end)}
+	w.callbacks.SetEditorSnippet(r)
+}
+
+//export Java_org_gioui_GioView_imeSetSelection
+func Java_org_gioui_GioView_imeSetSelection(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) {
+	w := cgo.Handle(handle).Value().(*window)
+	r := key.Range{Start: int(start), End: int(end)}
+	w.callbacks.SetEditorSelection(r)
+}
+
+//export Java_org_gioui_GioView_imeSetComposingRegion
+func Java_org_gioui_GioView_imeSetComposingRegion(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) {
+	w := cgo.Handle(handle).Value().(*window)
+	w.callbacks.SetComposingRegion(key.Range{
+		Start: int(start),
+		End:   int(end),
+	})
+}
+
+//export Java_org_gioui_GioView_imeReplace
+func Java_org_gioui_GioView_imeReplace(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint, jtext C.jstring) {
+	w := cgo.Handle(handle).Value().(*window)
+	r := key.Range{Start: int(start), End: int(end)}
+	text := goString(env, jtext)
+	w.callbacks.EditorReplace(r, text)
+}
+
+//export Java_org_gioui_GioView_imeToRunes
+func Java_org_gioui_GioView_imeToRunes(env *C.JNIEnv, class C.jclass, handle C.jlong, chars C.jint) C.jint {
+	w := cgo.Handle(handle).Value().(*window)
+	state := w.callbacks.EditorState()
+	return C.jint(state.RunesIndex(int(chars)))
+}
+
+//export Java_org_gioui_GioView_imeToUTF16
+func Java_org_gioui_GioView_imeToUTF16(env *C.JNIEnv, class C.jclass, handle C.jlong, runes C.jint) C.jint {
+	w := cgo.Handle(handle).Value().(*window)
+	state := w.callbacks.EditorState()
+	return C.jint(state.UTF16Index(int(runes)))
+}
+
+func (w *window) EditorStateChanged(old, new editorState) {
+	runInJVM(javaVM(), func(env *C.JNIEnv) {
+		if old.Snippet != new.Snippet {
+			callVoidMethod(env, w.view, gioView.restartInput)
+			return
+		}
+		if old.Selection.Range != new.Selection.Range {
+			w.callbacks.SetComposingRegion(key.Range{Start: -1, End: -1})
+			callVoidMethod(env, w.view, gioView.updateSelection)
+		}
+		if old.Selection.Transform != new.Selection.Transform || old.Selection.Caret != new.Selection.Caret {
+			sel := new.Selection
+			m00, m01, m02, m10, m11, m12 := sel.Transform.Elems()
+			f := func(v float32) jvalue {
+				return jvalue(math.Float32bits(v))
+			}
+			c := sel.Caret
+			callVoidMethod(env, w.view, gioView.updateCaret, f(m00), f(m01), f(m02), f(m10), f(m11), f(m12), f(c.Pos.X), f(c.Pos.Y-c.Ascent), f(c.Pos.Y), f(c.Pos.Y+c.Descent))
+		}
+	})
+}
+
+func (w *window) ShowTextInput(show bool) {
+	runInJVM(javaVM(), func(env *C.JNIEnv) {
+		if show {
+			callVoidMethod(env, w.view, gioView.showTextInput)
+		} else {
+			callVoidMethod(env, w.view, gioView.hideTextInput)
+		}
+	})
+}
+
+func (w *window) SetInputHint(mode key.InputHint) {
+	w.inputHint = mode
+
+	// Constants defined at https://developer.android.com/reference/android/text/InputType.
+	const (
+		TYPE_NULL = 0
+
+		TYPE_CLASS_TEXT                   = 1
+		TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 32
+		TYPE_TEXT_VARIATION_URI           = 16
+		TYPE_TEXT_VARIATION_PASSWORD      = 128
+		TYPE_TEXT_FLAG_CAP_SENTENCES      = 16384
+		TYPE_TEXT_FLAG_AUTO_CORRECT       = 32768
+
+		TYPE_CLASS_NUMBER        = 2
+		TYPE_NUMBER_FLAG_DECIMAL = 8192
+		TYPE_NUMBER_FLAG_SIGNED  = 4096
+
+		TYPE_CLASS_PHONE = 3
+	)
+
+	runInJVM(javaVM(), func(env *C.JNIEnv) {
+		var m jvalue
+		switch mode {
+		case key.HintText:
+			m = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_AUTO_CORRECT | TYPE_TEXT_FLAG_CAP_SENTENCES
+		case key.HintNumeric:
+			m = TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED
+		case key.HintEmail:
+			m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+		case key.HintURL:
+			m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_URI
+		case key.HintTelephone:
+			m = TYPE_CLASS_PHONE
+		case key.HintPassword:
+			m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD
+		default:
+			m = TYPE_CLASS_TEXT
+		}
+
+		callVoidMethod(env, w.view, gioView.setInputHint, m)
+	})
+}
+
+func javaBool(b bool) C.jboolean {
+	if b {
+		return C.JNI_TRUE
+	} else {
+		return C.JNI_FALSE
+	}
+}
+
+func javaString(env *C.JNIEnv, str string) C.jstring {
+	utf16Chars := utf16.Encode([]rune(str))
+	var ptr *C.jchar
+	if len(utf16Chars) > 0 {
+		ptr = (*C.jchar)(unsafe.Pointer(&utf16Chars[0]))
+	}
+	return C.jni_NewString(env, ptr, C.int(len(utf16Chars)))
+}
+
+func varArgs(args []jvalue) *C.jvalue {
+	if len(args) == 0 {
+		return nil
+	}
+	return (*C.jvalue)(unsafe.Pointer(&args[0]))
+}
+
+func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error {
+	C.jni_CallStaticVoidMethodA(env, cls, method, varArgs(args))
+	return exception(env)
+}
+
+func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
+	res := C.jni_CallStaticObjectMethodA(env, cls, method, varArgs(args))
+	return res, exception(env)
+}
+
+func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error {
+	C.jni_CallVoidMethodA(env, obj, method, varArgs(args))
+	return exception(env)
+}
+
+func callBooleanMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (bool, error) {
+	res := C.jni_CallBooleanMethodA(env, obj, method, varArgs(args))
+	return res == C.JNI_TRUE, exception(env)
+}
+
+func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) {
+	res := C.jni_CallObjectMethodA(env, obj, method, varArgs(args))
+	return res, exception(env)
+}
+
+func newObject(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
+	res := C.jni_NewObjectA(env, cls, method, varArgs(args))
+	return res, exception(env)
+}
+
+// exception returns an error corresponding to the pending
+// exception, or nil if no exception is pending. The pending
+// exception is cleared.
+func exception(env *C.JNIEnv) error {
+	thr := C.jni_ExceptionOccurred(env)
+	if thr == 0 {
+		return nil
+	}
+	C.jni_ExceptionClear(env)
+	cls := getObjectClass(env, C.jobject(thr))
+	toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;")
+	msg, err := callObjectMethod(env, C.jobject(thr), toString)
+	if err != nil {
+		return err
+	}
+	return errors.New(goString(env, C.jstring(msg)))
+}
+
+func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass {
+	if obj == 0 {
+		panic("null object")
+	}
+	cls := C.jni_GetObjectClass(env, C.jobject(obj))
+	if err := exception(env); err != nil {
+		// GetObjectClass should never fail.
+		panic(err)
+	}
+	return cls
+}
+
+// goString converts the JVM jstring to a Go string.
+func goString(env *C.JNIEnv, str C.jstring) string {
+	if str == 0 {
+		return ""
+	}
+	strlen := C.jni_GetStringLength(env, C.jstring(str))
+	chars := C.jni_GetStringChars(env, C.jstring(str))
+	utf16Chars := unsafe.Slice((*uint16)(unsafe.Pointer(chars)), strlen)
+	utf8 := utf16.Decode(utf16Chars)
+	return string(utf8)
+}
+
+func findClass(env *C.JNIEnv, name string) C.jclass {
+	cn := C.CString(name)
+	defer C.free(unsafe.Pointer(cn))
+	return C.jni_FindClass(env, cn)
+}
+
+func osMain() {
+}
+
+func newWindow(window *callbacks, options []Option) {
+	mainWindow.in <- windowAndConfig{window, options}
+	<-mainWindow.windows
+}
+
+func (w *window) WriteClipboard(mime string, s []byte) {
+	runInJVM(javaVM(), func(env *C.JNIEnv) {
+		jstr := javaString(env, string(s))
+		callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
+			jvalue(android.appCtx), jvalue(jstr))
+	})
+}
+
+func (w *window) ReadClipboard() {
+	runInJVM(javaVM(), func(env *C.JNIEnv) {
+		c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard,
+			jvalue(android.appCtx))
+		if err != nil {
+			return
+		}
+		content := goString(env, C.jstring(c))
+		w.processEvent(transfer.DataEvent{
+			Type: "application/text",
+			Open: func() io.ReadCloser {
+				return io.NopCloser(strings.NewReader(content))
+			},
+		})
+	})
+}
+
+func (w *window) Configure(options []Option) {
+	cnf := w.config
+	cnf.apply(unit.Metric{}, options)
+	runInJVM(javaVM(), func(env *C.JNIEnv) {
+		w.setConfig(env, cnf)
+	})
+}
+
+func (w *window) setConfig(env *C.JNIEnv, cnf Config) {
+	prev := w.config
+	// Decorations are never disabled.
+	cnf.Decorated = true
+
+	if prev.Orientation != cnf.Orientation {
+		w.config.Orientation = cnf.Orientation
+		setOrientation(env, w.view, cnf.Orientation)
+	}
+	if prev.NavigationColor != cnf.NavigationColor {
+		w.config.NavigationColor = cnf.NavigationColor
+		setNavigationColor(env, w.view, cnf.NavigationColor)
+	}
+	if prev.StatusColor != cnf.StatusColor {
+		w.config.StatusColor = cnf.StatusColor
+		setStatusColor(env, w.view, cnf.StatusColor)
+	}
+	if prev.Mode != cnf.Mode {
+		switch cnf.Mode {
+		case Fullscreen:
+			callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
+			w.config.Mode = Fullscreen
+		case Windowed:
+			callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
+			w.config.Mode = Windowed
+		}
+	}
+	if cnf.Decorated != prev.Decorated {
+		w.config.Decorated = cnf.Decorated
+	}
+	w.processEvent(ConfigEvent{Config: w.config})
+}
+
+func (w *window) Perform(system.Action) {}
+
+func (w *window) SetCursor(cursor pointer.Cursor) {
+	runInJVM(javaVM(), func(env *C.JNIEnv) {
+		setCursor(env, w.view, cursor)
+	})
+}
+
+func (w *window) wakeup() {
+	runOnMain(func(env *C.JNIEnv) {
+		w.loop.Wakeup()
+		w.loop.FlushEvents()
+	})
+}
+
+var androidCursor = [...]uint16{
+	pointer.CursorDefault:                  1000, // TYPE_ARROW
+	pointer.CursorNone:                     0,
+	pointer.CursorText:                     1008, // TYPE_TEXT
+	pointer.CursorVerticalText:             1009, // TYPE_VERTICAL_TEXT
+	pointer.CursorPointer:                  1002, // TYPE_HAND
+	pointer.CursorCrosshair:                1007, // TYPE_CROSSHAIR
+	pointer.CursorAllScroll:                1013, // TYPE_ALL_SCROLL
+	pointer.CursorColResize:                1014, // TYPE_HORIZONTAL_DOUBLE_ARROW
+	pointer.CursorRowResize:                1015, // TYPE_VERTICAL_DOUBLE_ARROW
+	pointer.CursorGrab:                     1020, // TYPE_GRAB
+	pointer.CursorGrabbing:                 1021, // TYPE_GRABBING
+	pointer.CursorNotAllowed:               1012, // TYPE_NO_DROP
+	pointer.CursorWait:                     1004, // TYPE_WAIT
+	pointer.CursorProgress:                 1000, // TYPE_ARROW
+	pointer.CursorNorthWestResize:          1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
+	pointer.CursorNorthEastResize:          1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
+	pointer.CursorSouthWestResize:          1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
+	pointer.CursorSouthEastResize:          1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
+	pointer.CursorNorthSouthResize:         1015, // TYPE_VERTICAL_DOUBLE_ARROW
+	pointer.CursorEastWestResize:           1014, // TYPE_HORIZONTAL_DOUBLE_ARROW
+	pointer.CursorWestResize:               1014, // TYPE_HORIZONTAL_DOUBLE_ARROW
+	pointer.CursorEastResize:               1014, // TYPE_HORIZONTAL_DOUBLE_ARROW
+	pointer.CursorNorthResize:              1015, // TYPE_VERTICAL_DOUBLE_ARROW
+	pointer.CursorSouthResize:              1015, // TYPE_VERTICAL_DOUBLE_ARROW
+	pointer.CursorNorthEastSouthWestResize: 1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
+	pointer.CursorNorthWestSouthEastResize: 1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
+}
+
+func setCursor(env *C.JNIEnv, view C.jobject, cursor pointer.Cursor) {
+	curID := androidCursor[cursor]
+	callVoidMethod(env, view, gioView.setCursor, jvalue(curID))
+}
+
+func setOrientation(env *C.JNIEnv, view C.jobject, mode Orientation) {
+	var (
+		id         int
+		idFallback int // Used only for SDK 17 or older.
+	)
+	// Constants defined at https://developer.android.com/reference/android/content/pm/ActivityInfo.
+	switch mode {
+	case AnyOrientation:
+		id, idFallback = 2, 2 // SCREEN_ORIENTATION_USER
+	case LandscapeOrientation:
+		id, idFallback = 11, 0 // SCREEN_ORIENTATION_USER_LANDSCAPE (or SCREEN_ORIENTATION_LANDSCAPE)
+	case PortraitOrientation:
+		id, idFallback = 12, 1 // SCREEN_ORIENTATION_USER_PORTRAIT (or SCREEN_ORIENTATION_PORTRAIT)
+	}
+	callVoidMethod(env, view, gioView.setOrientation, jvalue(id), jvalue(idFallback))
+}
+
+func setStatusColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
+	callVoidMethod(env, view, gioView.setStatusColor,
+		jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)),
+		jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)),
+	)
+}
+
+func setNavigationColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
+	callVoidMethod(env, view, gioView.setNavigationColor,
+		jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)),
+		jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)),
+	)
+}
+
+// runOnMain runs a function on the Java main thread.
+func runOnMain(f func(env *C.JNIEnv)) {
+	go func() {
+		mainFuncs <- f
+		runInJVM(javaVM(), func(env *C.JNIEnv) {
+			callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread)
+		})
+	}()
+}
+
+//export Java_org_gioui_Gio_scheduleMainFuncs
+func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
+	for {
+		select {
+		case f := <-mainFuncs:
+			f(env)
+		default:
+			return
+		}
+	}
+}
+
+func (AndroidViewEvent) implementsViewEvent() {}
+func (AndroidViewEvent) ImplementsEvent()     {}
+func (a AndroidViewEvent) Valid() bool {
+	return a != (AndroidViewEvent{})
+}

+ 272 - 0
vendor/gioui.org/app/os_darwin.go

@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+/*
+#include <Foundation/Foundation.h>
+
+__attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
+__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
+__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
+__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
+__attribute__ ((visibility ("hidden"))) int gio_stopDisplayLink(CFTypeRef dl);
+__attribute__ ((visibility ("hidden"))) void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did);
+__attribute__ ((visibility ("hidden"))) void gio_hideCursor();
+__attribute__ ((visibility ("hidden"))) void gio_showCursor();
+__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
+
+static bool isMainThread() {
+	return [NSThread isMainThread];
+}
+
+static NSUInteger nsstringLength(CFTypeRef cstr) {
+	NSString *str = (__bridge NSString *)cstr;
+	return [str length];
+}
+
+static void nsstringGetCharacters(CFTypeRef cstr, unichar *chars, NSUInteger loc, NSUInteger length) {
+	NSString *str = (__bridge NSString *)cstr;
+	[str getCharacters:chars range:NSMakeRange(loc, length)];
+}
+
+static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
+	@autoreleasepool {
+		NSString *s = [NSString string];
+		if (length > 0) {
+			s = [NSString stringWithCharacters:chars length:length];
+		}
+		return CFBridgingRetain(s);
+	}
+}
+*/
+import "C"
+import (
+	"errors"
+	"sync"
+	"sync/atomic"
+	"time"
+	"unicode/utf16"
+	"unsafe"
+
+	"gioui.org/io/pointer"
+)
+
+// displayLink is the state for a display link (CVDisplayLinkRef on macOS,
+// CADisplayLink on iOS). It runs a state-machine goroutine that keeps the
+// display link running for a while after being stopped to avoid the thread
+// start/stop overhead and because the CVDisplayLink sometimes fails to
+// start, stop and start again within a short duration.
+type displayLink struct {
+	callback func()
+	// states is for starting or stopping the display link.
+	states chan bool
+	// done is closed when the display link is destroyed.
+	done chan struct{}
+	// dids receives the display id when the callback owner is moved
+	// to a different screen.
+	dids chan uint64
+	// running tracks the desired state of the link. running is accessed
+	// with atomic.
+	running uint32
+}
+
+// displayLinks maps CFTypeRefs to *displayLinks.
+var displayLinks sync.Map
+
+var mainFuncs = make(chan func(), 1)
+
+func isMainThread() bool {
+	return bool(C.isMainThread())
+}
+
+// runOnMain runs the function on the main thread.
+func runOnMain(f func()) {
+	if isMainThread() {
+		f()
+		return
+	}
+	go func() {
+		mainFuncs <- f
+		C.gio_wakeupMainThread()
+	}()
+}
+
+//export gio_dispatchMainFuncs
+func gio_dispatchMainFuncs() {
+	for {
+		select {
+		case f := <-mainFuncs:
+			f()
+		default:
+			return
+		}
+	}
+}
+
+// nsstringToString converts a NSString to a Go string.
+func nsstringToString(str C.CFTypeRef) string {
+	if str == 0 {
+		return ""
+	}
+	n := C.nsstringLength(str)
+	if n == 0 {
+		return ""
+	}
+	chars := make([]uint16, n)
+	C.nsstringGetCharacters(str, (*C.unichar)(unsafe.Pointer(&chars[0])), 0, n)
+	utf8 := utf16.Decode(chars)
+	return string(utf8)
+}
+
+// stringToNSString converts a Go string to a retained NSString.
+func stringToNSString(str string) C.CFTypeRef {
+	u16 := utf16.Encode([]rune(str))
+	var chars *C.unichar
+	if len(u16) > 0 {
+		chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
+	}
+	return C.newNSString(chars, C.NSUInteger(len(u16)))
+}
+
+func newDisplayLink(callback func()) (*displayLink, error) {
+	d := &displayLink{
+		callback: callback,
+		done:     make(chan struct{}),
+		states:   make(chan bool),
+		dids:     make(chan uint64),
+	}
+	dl := C.gio_createDisplayLink()
+	if dl == 0 {
+		return nil, errors.New("app: failed to create display link")
+	}
+	go d.run(dl)
+	return d, nil
+}
+
+func (d *displayLink) run(dl C.CFTypeRef) {
+	defer C.gio_releaseDisplayLink(dl)
+	displayLinks.Store(dl, d)
+	defer displayLinks.Delete(dl)
+	var stopTimer *time.Timer
+	var tchan <-chan time.Time
+	started := false
+	for {
+		select {
+		case <-tchan:
+			tchan = nil
+			started = false
+			C.gio_stopDisplayLink(dl)
+		case start := <-d.states:
+			switch {
+			case !start && tchan == nil:
+				// stopTimeout is the delay before stopping the display link to
+				// avoid the overhead of frequently starting and stopping the
+				// link thread.
+				const stopTimeout = 500 * time.Millisecond
+				if stopTimer == nil {
+					stopTimer = time.NewTimer(stopTimeout)
+				} else {
+					// stopTimer is always drained when tchan == nil.
+					stopTimer.Reset(stopTimeout)
+				}
+				tchan = stopTimer.C
+				atomic.StoreUint32(&d.running, 0)
+			case start:
+				if tchan != nil && !stopTimer.Stop() {
+					<-tchan
+				}
+				tchan = nil
+				atomic.StoreUint32(&d.running, 1)
+				if !started {
+					started = true
+					C.gio_startDisplayLink(dl)
+				}
+			}
+		case did := <-d.dids:
+			C.gio_setDisplayLinkDisplay(dl, C.uint64_t(did))
+		case <-d.done:
+			return
+		}
+	}
+}
+
+func (d *displayLink) Start() {
+	d.states <- true
+}
+
+func (d *displayLink) Stop() {
+	d.states <- false
+}
+
+func (d *displayLink) Close() {
+	close(d.done)
+}
+
+func (d *displayLink) SetDisplayID(did uint64) {
+	d.dids <- did
+}
+
+//export gio_onFrameCallback
+func gio_onFrameCallback(ref C.CFTypeRef) {
+	d, exists := displayLinks.Load(ref)
+	if !exists {
+		return
+	}
+	dl := d.(*displayLink)
+	if atomic.LoadUint32(&dl.running) != 0 {
+		dl.callback()
+	}
+}
+
+var macosCursorID = [...]byte{
+	pointer.CursorDefault:                  0,
+	pointer.CursorNone:                     1,
+	pointer.CursorText:                     2,
+	pointer.CursorVerticalText:             3,
+	pointer.CursorPointer:                  4,
+	pointer.CursorCrosshair:                5,
+	pointer.CursorAllScroll:                6,
+	pointer.CursorColResize:                7,
+	pointer.CursorRowResize:                8,
+	pointer.CursorGrab:                     9,
+	pointer.CursorGrabbing:                 10,
+	pointer.CursorNotAllowed:               11,
+	pointer.CursorWait:                     12,
+	pointer.CursorProgress:                 13,
+	pointer.CursorNorthWestResize:          14,
+	pointer.CursorNorthEastResize:          15,
+	pointer.CursorSouthWestResize:          16,
+	pointer.CursorSouthEastResize:          17,
+	pointer.CursorNorthSouthResize:         18,
+	pointer.CursorEastWestResize:           19,
+	pointer.CursorWestResize:               20,
+	pointer.CursorEastResize:               21,
+	pointer.CursorNorthResize:              22,
+	pointer.CursorSouthResize:              23,
+	pointer.CursorNorthEastSouthWestResize: 24,
+	pointer.CursorNorthWestSouthEastResize: 25,
+}
+
+// windowSetCursor updates the cursor from the current one to a new one
+// and returns the new one.
+func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
+	if from == to {
+		return to
+	}
+	if to == pointer.CursorNone {
+		C.gio_hideCursor()
+		return to
+	}
+	if from == pointer.CursorNone {
+		C.gio_showCursor()
+	}
+	C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
+	return to
+}
+
+func (w *window) wakeup() {
+	runOnMain(func() {
+		w.loop.Wakeup()
+		w.loop.FlushEvents()
+	})
+}

+ 11 - 0
vendor/gioui.org/app/os_darwin.m

@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+#import <Foundation/Foundation.h>
+
+#include "_cgo_export.h"
+
+void gio_wakeupMainThread(void) {
+	dispatch_async(dispatch_get_main_queue(), ^{
+		gio_dispatchMainFuncs();
+	});
+}

+ 446 - 0
vendor/gioui.org/app/os_ios.go

@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build darwin && ios
+// +build darwin,ios
+
+package app
+
+/*
+#cgo CFLAGS: -DGLES_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <UIKit/UIKit.h>
+#include <stdint.h>
+
+__attribute__ ((visibility ("hidden"))) int gio_applicationMain(int argc, char *argv[]);
+__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
+
+struct drawParams {
+	CGFloat dpi, sdpi;
+	CGFloat width, height;
+	CGFloat top, right, bottom, left;
+};
+
+static void writeClipboard(unichar *chars, NSUInteger length) {
+#if !TARGET_OS_TV
+	@autoreleasepool {
+		NSString *s = [NSString string];
+		if (length > 0) {
+			s = [NSString stringWithCharacters:chars length:length];
+		}
+		UIPasteboard *p = UIPasteboard.generalPasteboard;
+		p.string = s;
+	}
+#endif
+}
+
+static CFTypeRef readClipboard(void) {
+#if !TARGET_OS_TV
+	@autoreleasepool {
+		UIPasteboard *p = UIPasteboard.generalPasteboard;
+		return (__bridge_retained CFTypeRef)p.string;
+	}
+#else
+	return nil;
+#endif
+}
+
+static void showTextInput(CFTypeRef viewRef) {
+	UIView *view = (__bridge UIView *)viewRef;
+	[view becomeFirstResponder];
+}
+
+static void hideTextInput(CFTypeRef viewRef) {
+	UIView *view = (__bridge UIView *)viewRef;
+	[view resignFirstResponder];
+}
+
+static struct drawParams viewDrawParams(CFTypeRef viewRef) {
+	UIView *v = (__bridge UIView *)viewRef;
+	struct drawParams params;
+	CGFloat scale = v.layer.contentsScale;
+	// Use 163 as the standard ppi on iOS.
+	params.dpi = 163*scale;
+	params.sdpi = params.dpi;
+	UIEdgeInsets insets = v.layoutMargins;
+	if (@available(iOS 11.0, tvOS 11.0, *)) {
+		UIFontMetrics *metrics = [UIFontMetrics defaultMetrics];
+		params.sdpi = [metrics scaledValueForValue:params.sdpi];
+		insets = v.safeAreaInsets;
+	}
+	params.width = v.bounds.size.width*scale;
+	params.height = v.bounds.size.height*scale;
+	params.top = insets.top*scale;
+	params.right = insets.right*scale;
+	params.bottom = insets.bottom*scale;
+	params.left = insets.left*scale;
+	return params;
+}
+*/
+import "C"
+
+import (
+	"image"
+	"io"
+	"os"
+	"runtime"
+	"runtime/cgo"
+	"runtime/debug"
+	"strings"
+	"time"
+	"unicode/utf16"
+	"unsafe"
+
+	"gioui.org/f32"
+	"gioui.org/io/event"
+	"gioui.org/io/key"
+	"gioui.org/io/pointer"
+	"gioui.org/io/system"
+	"gioui.org/io/transfer"
+	"gioui.org/op"
+	"gioui.org/unit"
+)
+
+type UIKitViewEvent struct {
+	// ViewController is a CFTypeRef for the UIViewController backing a Window.
+	ViewController uintptr
+}
+
+type window struct {
+	view        C.CFTypeRef
+	w           *callbacks
+	displayLink *displayLink
+	loop        *eventLoop
+
+	hidden bool
+	cursor pointer.Cursor
+	config Config
+
+	pointerMap []C.CFTypeRef
+}
+
+var mainWindow = newWindowRendezvous()
+
+func init() {
+	// Darwin requires UI operations happen on the main thread only.
+	runtime.LockOSThread()
+}
+
+//export onCreate
+func onCreate(view, controller C.CFTypeRef) {
+	wopts := <-mainWindow.out
+	w := &window{
+		view: view,
+		w:    wopts.window,
+	}
+	w.loop = newEventLoop(w.w, w.wakeup)
+	w.w.SetDriver(w)
+	mainWindow.windows <- struct{}{}
+	dl, err := newDisplayLink(func() {
+		w.draw(false)
+	})
+	if err != nil {
+		w.w.ProcessEvent(DestroyEvent{Err: err})
+		return
+	}
+	w.displayLink = dl
+	C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
+	w.Configure(wopts.options)
+	w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
+}
+
+func viewFor(h C.uintptr_t) *window {
+	return cgo.Handle(h).Value().(*window)
+}
+
+//export gio_onDraw
+func gio_onDraw(h C.uintptr_t) {
+	w := viewFor(h)
+	w.draw(true)
+}
+
+func (w *window) draw(sync bool) {
+	if w.hidden {
+		return
+	}
+	params := C.viewDrawParams(w.view)
+	if params.width == 0 || params.height == 0 {
+		return
+	}
+	const inchPrDp = 1.0 / 163
+	m := unit.Metric{
+		PxPerDp: float32(params.dpi) * inchPrDp,
+		PxPerSp: float32(params.sdpi) * inchPrDp,
+	}
+	dppp := unit.Dp(1. / m.PxPerDp)
+	w.ProcessEvent(frameEvent{
+		FrameEvent: FrameEvent{
+			Now: time.Now(),
+			Size: image.Point{
+				X: int(params.width + .5),
+				Y: int(params.height + .5),
+			},
+			Insets: Insets{
+				Top:    unit.Dp(params.top) * dppp,
+				Bottom: unit.Dp(params.bottom) * dppp,
+				Left:   unit.Dp(params.left) * dppp,
+				Right:  unit.Dp(params.right) * dppp,
+			},
+			Metric: m,
+		},
+		Sync: sync,
+	})
+}
+
+//export onStop
+func onStop(h C.uintptr_t) {
+	w := viewFor(h)
+	w.hidden = true
+}
+
+//export onStart
+func onStart(h C.uintptr_t) {
+	w := viewFor(h)
+	w.hidden = false
+	w.draw(true)
+}
+
+//export onDestroy
+func onDestroy(h C.uintptr_t) {
+	w := viewFor(h)
+	w.ProcessEvent(UIKitViewEvent{})
+	w.ProcessEvent(DestroyEvent{})
+	w.displayLink.Close()
+	w.displayLink = nil
+	cgo.Handle(h).Delete()
+	w.view = 0
+}
+
+//export onFocus
+func onFocus(h C.uintptr_t, focus int) {
+	w := viewFor(h)
+	w.config.Focused = focus != 0
+	w.ProcessEvent(ConfigEvent{Config: w.config})
+}
+
+//export onLowMemory
+func onLowMemory() {
+	runtime.GC()
+	debug.FreeOSMemory()
+}
+
+//export onUpArrow
+func onUpArrow(h C.uintptr_t) {
+	viewFor(h).onKeyCommand(key.NameUpArrow)
+}
+
+//export onDownArrow
+func onDownArrow(h C.uintptr_t) {
+	viewFor(h).onKeyCommand(key.NameDownArrow)
+}
+
+//export onLeftArrow
+func onLeftArrow(h C.uintptr_t) {
+	viewFor(h).onKeyCommand(key.NameLeftArrow)
+}
+
+//export onRightArrow
+func onRightArrow(h C.uintptr_t) {
+	viewFor(h).onKeyCommand(key.NameRightArrow)
+}
+
+//export onDeleteBackward
+func onDeleteBackward(h C.uintptr_t) {
+	viewFor(h).onKeyCommand(key.NameDeleteBackward)
+}
+
+//export onText
+func onText(h C.uintptr_t, str C.CFTypeRef) {
+	w := viewFor(h)
+	w.w.EditorInsert(nsstringToString(str))
+}
+
+//export onTouch
+func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
+	var kind pointer.Kind
+	switch phase {
+	case C.UITouchPhaseBegan:
+		kind = pointer.Press
+	case C.UITouchPhaseMoved:
+		kind = pointer.Move
+	case C.UITouchPhaseEnded:
+		kind = pointer.Release
+	case C.UITouchPhaseCancelled:
+		kind = pointer.Cancel
+	default:
+		return
+	}
+	w := viewFor(h)
+	t := time.Duration(float64(ti) * float64(time.Second))
+	p := f32.Point{X: float32(x), Y: float32(y)}
+	w.ProcessEvent(pointer.Event{
+		Kind:      kind,
+		Source:    pointer.Touch,
+		PointerID: w.lookupTouch(last != 0, touchRef),
+		Position:  p,
+		Time:      t,
+	})
+}
+
+func (w *window) ReadClipboard() {
+	cstr := C.readClipboard()
+	defer C.CFRelease(cstr)
+	content := nsstringToString(cstr)
+	w.ProcessEvent(transfer.DataEvent{
+		Type: "application/text",
+		Open: func() io.ReadCloser {
+			return io.NopCloser(strings.NewReader(content))
+		},
+	})
+}
+
+func (w *window) WriteClipboard(mime string, s []byte) {
+	u16 := utf16.Encode([]rune(string(s)))
+	var chars *C.unichar
+	if len(u16) > 0 {
+		chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
+	}
+	C.writeClipboard(chars, C.NSUInteger(len(u16)))
+}
+
+func (w *window) Configure([]Option) {
+	// Decorations are never disabled.
+	w.config.Decorated = true
+	w.ProcessEvent(ConfigEvent{Config: w.config})
+}
+
+func (w *window) EditorStateChanged(old, new editorState) {}
+
+func (w *window) Perform(system.Action) {}
+
+func (w *window) SetAnimating(anim bool) {
+	if anim {
+		w.displayLink.Start()
+	} else {
+		w.displayLink.Stop()
+	}
+}
+
+func (w *window) SetCursor(cursor pointer.Cursor) {
+	w.cursor = windowSetCursor(w.cursor, cursor)
+}
+
+func (w *window) onKeyCommand(name key.Name) {
+	w.ProcessEvent(key.Event{
+		Name: name,
+	})
+}
+
+// lookupTouch maps an UITouch pointer value to an index. If
+// last is set, the map is cleared.
+func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID {
+	id := -1
+	for i, ref := range w.pointerMap {
+		if ref == touch {
+			id = i
+			break
+		}
+	}
+	if id == -1 {
+		id = len(w.pointerMap)
+		w.pointerMap = append(w.pointerMap, touch)
+	}
+	if last {
+		w.pointerMap = w.pointerMap[:0]
+	}
+	return pointer.ID(id)
+}
+
+func (w *window) contextView() C.CFTypeRef {
+	return w.view
+}
+
+func (w *window) ShowTextInput(show bool) {
+	if show {
+		C.showTextInput(w.view)
+	} else {
+		C.hideTextInput(w.view)
+	}
+}
+
+func (w *window) SetInputHint(_ key.InputHint) {}
+
+func (w *window) ProcessEvent(e event.Event) {
+	w.w.ProcessEvent(e)
+	w.loop.FlushEvents()
+}
+
+func (w *window) Event() event.Event {
+	return w.loop.Event()
+}
+
+func (w *window) Invalidate() {
+	w.loop.Invalidate()
+}
+
+func (w *window) Run(f func()) {
+	w.loop.Run(f)
+}
+
+func (w *window) Frame(frame *op.Ops) {
+	w.loop.Frame(frame)
+}
+
+func newWindow(win *callbacks, options []Option) {
+	mainWindow.in <- windowAndConfig{win, options}
+	<-mainWindow.windows
+}
+
+var mainMode = mainModeUndefined
+
+const (
+	mainModeUndefined = iota
+	mainModeExe
+	mainModeLibrary
+)
+
+func osMain() {
+	if !isMainThread() {
+		panic("app.Main must be run on the main goroutine")
+	}
+	switch mainMode {
+	case mainModeUndefined:
+		mainMode = mainModeExe
+		var argv []*C.char
+		for _, arg := range os.Args {
+			a := C.CString(arg)
+			defer C.free(unsafe.Pointer(a))
+			argv = append(argv, a)
+		}
+		C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
+	case mainModeExe:
+		panic("app.Main may be called only once")
+	case mainModeLibrary:
+		// Do nothing, we're embedded as a library.
+	}
+}
+
+//export gio_runMain
+func gio_runMain() {
+	if !isMainThread() {
+		panic("app.Main must be run on the main goroutine")
+	}
+	switch mainMode {
+	case mainModeUndefined:
+		mainMode = mainModeLibrary
+		runMain()
+	case mainModeExe:
+		// Do nothing, main has already been called.
+	}
+}
+
+func (UIKitViewEvent) implementsViewEvent() {}
+func (UIKitViewEvent) ImplementsEvent()     {}
+func (u UIKitViewEvent) Valid() bool {
+	return u != (UIKitViewEvent{})
+}

+ 302 - 0
vendor/gioui.org/app/os_ios.m

@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build darwin,ios
+
+@import UIKit;
+
+#include <stdint.h>
+#include "_cgo_export.h"
+#include "framework_ios.h"
+
+__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
+
+@interface GioView: UIView <UIKeyInput>
+@property uintptr_t handle;
+@end
+
+@implementation GioViewController
+
+CGFloat _keyboardHeight;
+
+- (void)loadView {
+	gio_runMain();
+
+	CGRect zeroFrame = CGRectMake(0, 0, 0, 0);
+	self.view = [[UIView alloc] initWithFrame:zeroFrame];
+	self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
+	UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
+	[self.view addSubview: drawView];
+#if !TARGET_OS_TV
+	drawView.multipleTouchEnabled = YES;
+#endif
+	drawView.preservesSuperviewLayoutMargins = YES;
+	drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
+	onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
+#if !TARGET_OS_TV
+	[[NSNotificationCenter defaultCenter] addObserver:self
+											 selector:@selector(keyboardWillChange:)
+												 name:UIKeyboardWillShowNotification
+											   object:nil];
+	[[NSNotificationCenter defaultCenter] addObserver:self
+											 selector:@selector(keyboardWillChange:)
+												 name:UIKeyboardWillChangeFrameNotification
+											   object:nil];
+	[[NSNotificationCenter defaultCenter] addObserver:self
+											 selector:@selector(keyboardWillHide:)
+												 name:UIKeyboardWillHideNotification
+											   object:nil];
+#endif
+	[[NSNotificationCenter defaultCenter] addObserver: self
+											 selector: @selector(applicationDidEnterBackground:)
+												 name: UIApplicationDidEnterBackgroundNotification
+											   object: nil];
+	[[NSNotificationCenter defaultCenter] addObserver: self
+											 selector: @selector(applicationWillEnterForeground:)
+												 name: UIApplicationWillEnterForegroundNotification
+											   object: nil];
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+	GioView *view = (GioView *)self.view.subviews[0];
+	if (view != nil) {
+		onStart(view.handle);
+	}
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+	GioView *view = (GioView *)self.view.subviews[0];
+	if (view != nil) {
+		onStop(view.handle);
+	}
+}
+
+- (void)viewDidDisappear:(BOOL)animated {
+	[super viewDidDisappear:animated];
+	GioView *view = (GioView *)self.view.subviews[0];
+	onDestroy(view.handle);
+}
+
+- (void)viewDidLayoutSubviews {
+	[super viewDidLayoutSubviews];
+	GioView *view = (GioView *)self.view.subviews[0];
+	CGRect frame = self.view.bounds;
+	// Adjust view bounds to make room for the keyboard.
+	frame.size.height -= _keyboardHeight;
+	view.frame = frame;
+	gio_onDraw(view.handle);
+}
+
+- (void)didReceiveMemoryWarning {
+	onLowMemory();
+	[super didReceiveMemoryWarning];
+}
+
+#if !TARGET_OS_TV
+- (void)keyboardWillChange:(NSNotification *)note {
+	NSDictionary *userInfo = note.userInfo;
+	CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
+	_keyboardHeight = f.size.height;
+	[self.view setNeedsLayout];
+}
+
+- (void)keyboardWillHide:(NSNotification *)note {
+	_keyboardHeight = 0.0;
+	[self.view setNeedsLayout];
+}
+#endif
+@end
+
+static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
+	CGFloat scale = view.contentScaleFactor;
+	NSUInteger i = 0;
+	NSUInteger n = [touches count];
+	for (UITouch *touch in touches) {
+		CFTypeRef touchRef = (__bridge CFTypeRef)touch;
+		i++;
+		NSArray<UITouch *> *coalescedTouches = [event coalescedTouchesForTouch:touch];
+		NSUInteger j = 0;
+		NSUInteger m = [coalescedTouches count];
+		for (UITouch *coalescedTouch in [event coalescedTouchesForTouch:touch]) {
+			CGPoint loc = [coalescedTouch locationInView:view];
+			j++;
+			int lastTouch = last && i == n && j == m;
+			onTouch(view.handle, lastTouch, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
+		}
+	}
+}
+
+@implementation GioView
+NSArray<UIKeyCommand *> *_keyCommands;
++ (void)onFrameCallback:(CADisplayLink *)link {
+       gio_onFrameCallback((__bridge CFTypeRef)link);
+}
++ (Class)layerClass {
+    return gio_layerClass();
+}
+- (void)willMoveToWindow:(UIWindow *)newWindow {
+	if (self.window != nil) {
+		[[NSNotificationCenter defaultCenter] removeObserver:self
+														name:UIWindowDidBecomeKeyNotification
+													  object:self.window];
+		[[NSNotificationCenter defaultCenter] removeObserver:self
+														name:UIWindowDidResignKeyNotification
+													  object:self.window];
+	}
+	self.contentScaleFactor = newWindow.screen.nativeScale;
+	[[NSNotificationCenter defaultCenter] addObserver:self
+											 selector:@selector(onWindowDidBecomeKey:)
+												 name:UIWindowDidBecomeKeyNotification
+											   object:newWindow];
+	[[NSNotificationCenter defaultCenter] addObserver:self
+											 selector:@selector(onWindowDidResignKey:)
+												 name:UIWindowDidResignKeyNotification
+											   object:newWindow];
+}
+
+- (void)onWindowDidBecomeKey:(NSNotification *)note {
+	if (self.isFirstResponder) {
+		onFocus(self.handle, YES);
+	}
+}
+
+- (void)onWindowDidResignKey:(NSNotification *)note {
+	if (self.isFirstResponder) {
+		onFocus(self.handle, NO);
+	}
+}
+
+- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+	handleTouches(0, self, touches, event);
+}
+
+- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+	handleTouches(0, self, touches, event);
+}
+
+- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+	handleTouches(1, self, touches, event);
+}
+
+- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+	handleTouches(1, self, touches, event);
+}
+
+- (void)insertText:(NSString *)text {
+	onText(self.handle, (__bridge CFTypeRef)text);
+}
+
+- (BOOL)canBecomeFirstResponder {
+	return YES;
+}
+
+- (BOOL)hasText {
+	return YES;
+}
+
+- (void)deleteBackward {
+	onDeleteBackward(self.handle);
+}
+
+- (void)onUpArrow {
+	onUpArrow(self.handle);
+}
+
+- (void)onDownArrow {
+	onDownArrow(self.handle);
+}
+
+- (void)onLeftArrow {
+	onLeftArrow(self.handle);
+}
+
+- (void)onRightArrow {
+	onRightArrow(self.handle);
+}
+
+- (NSArray<UIKeyCommand *> *)keyCommands {
+	if (_keyCommands == nil) {
+		_keyCommands = @[
+			[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow
+								modifierFlags:0
+									   action:@selector(onUpArrow)],
+			[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow
+								modifierFlags:0
+									   action:@selector(onDownArrow)],
+			[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow
+								modifierFlags:0
+									   action:@selector(onLeftArrow)],
+			[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow
+								modifierFlags:0
+									   action:@selector(onRightArrow)]
+		];
+	}
+	return _keyCommands;
+}
+@end
+
+CFTypeRef gio_createDisplayLink(void) {
+	CADisplayLink *dl = [CADisplayLink displayLinkWithTarget:[GioView class] selector:@selector(onFrameCallback:)];
+	dl.paused = YES;
+	NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
+	[dl addToRunLoop:runLoop forMode:[runLoop currentMode]];
+	return (__bridge_retained CFTypeRef)dl;
+}
+
+int gio_startDisplayLink(CFTypeRef dlref) {
+	CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
+	dl.paused = NO;
+	return 0;
+}
+
+int gio_stopDisplayLink(CFTypeRef dlref) {
+	CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
+	dl.paused = YES;
+	return 0;
+}
+
+void gio_releaseDisplayLink(CFTypeRef dlref) {
+	CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
+	[dl invalidate];
+	CFRelease(dlref);
+}
+
+void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
+	// Nothing to do on iOS.
+}
+
+void gio_hideCursor() {
+	// Not supported.
+}
+
+void gio_showCursor() {
+	// Not supported.
+}
+
+void gio_setCursor(NSUInteger curID) {
+	// Not supported.
+}
+
+void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
+	GioView *v = (__bridge GioView *)viewRef;
+	v.handle = handle;
+}
+
+@interface _gioAppDelegate : UIResponder <UIApplicationDelegate>
+@property (strong, nonatomic) UIWindow *window;
+@end
+
+@implementation _gioAppDelegate
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+	self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+	GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
+	self.window.rootViewController = controller;
+	[self.window makeKeyAndVisible];
+	return YES;
+}
+@end
+
+int gio_applicationMain(int argc, char *argv[]) {
+	@autoreleasepool {
+		return UIApplicationMain(argc, argv, nil, NSStringFromClass([_gioAppDelegate class]));
+	}
+}

+ 827 - 0
vendor/gioui.org/app/os_js.go

@@ -0,0 +1,827 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+import (
+	"fmt"
+	"image"
+	"image/color"
+	"io"
+	"strings"
+	"syscall/js"
+	"time"
+	"unicode"
+	"unicode/utf8"
+
+	"gioui.org/internal/f32color"
+	"gioui.org/op"
+
+	"gioui.org/f32"
+	"gioui.org/io/event"
+	"gioui.org/io/key"
+	"gioui.org/io/pointer"
+	"gioui.org/io/system"
+	"gioui.org/io/transfer"
+	"gioui.org/unit"
+)
+
+type JSViewEvent struct {
+	Element js.Value
+}
+
+type contextStatus int
+
+const (
+	contextStatusOkay contextStatus = iota
+	contextStatusLost
+	contextStatusRestored
+)
+
+type window struct {
+	window                js.Value
+	document              js.Value
+	head                  js.Value
+	clipboard             js.Value
+	cnv                   js.Value
+	tarea                 js.Value
+	w                     *callbacks
+	redraw                js.Func
+	clipboardCallback     js.Func
+	requestAnimationFrame js.Value
+	browserHistory        js.Value
+	visualViewport        js.Value
+	screenOrientation     js.Value
+	cleanfuncs            []func()
+	touches               []js.Value
+	composing             bool
+	requestFocus          bool
+
+	config    Config
+	inset     f32.Point
+	scale     float32
+	animating bool
+	// animRequested tracks whether a requestAnimationFrame callback
+	// is pending.
+	animRequested bool
+	wakeups       chan struct{}
+
+	contextStatus contextStatus
+}
+
+func newWindow(win *callbacks, options []Option) {
+	doc := js.Global().Get("document")
+	cont := getContainer(doc)
+	cnv := createCanvas(doc)
+	cont.Call("appendChild", cnv)
+	tarea := createTextArea(doc)
+	cont.Call("appendChild", tarea)
+	w := &window{
+		cnv:       cnv,
+		document:  doc,
+		tarea:     tarea,
+		window:    js.Global().Get("window"),
+		head:      doc.Get("head"),
+		clipboard: js.Global().Get("navigator").Get("clipboard"),
+		wakeups:   make(chan struct{}, 1),
+		w:         win,
+	}
+	w.w.SetDriver(w)
+	w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
+	w.browserHistory = w.window.Get("history")
+	w.visualViewport = w.window.Get("visualViewport")
+	if w.visualViewport.IsUndefined() {
+		w.visualViewport = w.window
+	}
+	if screen := w.window.Get("screen"); screen.Truthy() {
+		w.screenOrientation = screen.Get("orientation")
+	}
+	w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
+		w.draw(false)
+		return nil
+	})
+	w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
+		content := args[0].String()
+		w.processEvent(transfer.DataEvent{
+			Type: "application/text",
+			Open: func() io.ReadCloser {
+				return io.NopCloser(strings.NewReader(content))
+			},
+		})
+		return nil
+	})
+	w.addEventListeners()
+	w.addHistory()
+
+	w.Configure(options)
+	w.blur()
+	w.processEvent(JSViewEvent{Element: cont})
+	w.resize()
+	w.draw(true)
+}
+
+func getContainer(doc js.Value) js.Value {
+	cont := doc.Call("getElementById", "giowindow")
+	if !cont.IsNull() {
+		return cont
+	}
+	cont = doc.Call("createElement", "DIV")
+	doc.Get("body").Call("appendChild", cont)
+	return cont
+}
+
+func createTextArea(doc js.Value) js.Value {
+	tarea := doc.Call("createElement", "input")
+	style := tarea.Get("style")
+	style.Set("width", "1px")
+	style.Set("height", "1px")
+	style.Set("opacity", "0")
+	style.Set("border", "0")
+	style.Set("padding", "0")
+	tarea.Set("autocomplete", "off")
+	tarea.Set("autocorrect", "off")
+	tarea.Set("autocapitalize", "off")
+	tarea.Set("spellcheck", false)
+	return tarea
+}
+
+func createCanvas(doc js.Value) js.Value {
+	cnv := doc.Call("createElement", "canvas")
+	style := cnv.Get("style")
+	style.Set("position", "fixed")
+	style.Set("width", "100%")
+	style.Set("height", "100%")
+	return cnv
+}
+
+func (w *window) cleanup() {
+	// Cleanup in the opposite order of
+	// construction.
+	for i := len(w.cleanfuncs) - 1; i >= 0; i-- {
+		w.cleanfuncs[i]()
+	}
+	w.cleanfuncs = nil
+}
+
+func (w *window) addEventListeners() {
+	w.addEventListener(w.cnv, "webglcontextlost", func(this js.Value, args []js.Value) interface{} {
+		args[0].Call("preventDefault")
+		w.contextStatus = contextStatusLost
+		return nil
+	})
+	w.addEventListener(w.cnv, "webglcontextrestored", func(this js.Value, args []js.Value) interface{} {
+		args[0].Call("preventDefault")
+		w.contextStatus = contextStatusRestored
+
+		// Resize is required to force update the canvas content when restored.
+		w.cnv.Set("width", 0)
+		w.cnv.Set("height", 0)
+		w.resize()
+		w.draw(true)
+		return nil
+	})
+	w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
+		w.resize()
+		w.draw(true)
+		return nil
+	})
+	w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
+		args[0].Call("preventDefault")
+		return nil
+	})
+	w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
+		if w.processEvent(key.Event{Name: key.NameBack}) {
+			return w.browserHistory.Call("forward")
+		}
+		return w.browserHistory.Call("back")
+	})
+	w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
+		w.pointerEvent(pointer.Move, 0, 0, args[0])
+		return nil
+	})
+	w.addEventListener(w.cnv, "mousedown", func(this js.Value, args []js.Value) interface{} {
+		w.pointerEvent(pointer.Press, 0, 0, args[0])
+		if w.requestFocus {
+			w.focus()
+			w.requestFocus = false
+		}
+		return nil
+	})
+	w.addEventListener(w.cnv, "mouseup", func(this js.Value, args []js.Value) interface{} {
+		w.pointerEvent(pointer.Release, 0, 0, args[0])
+		return nil
+	})
+	w.addEventListener(w.cnv, "wheel", func(this js.Value, args []js.Value) interface{} {
+		e := args[0]
+		dx, dy := e.Get("deltaX").Float(), e.Get("deltaY").Float()
+		// horizontal scroll if shift is pressed.
+		if e.Get("shiftKey").Bool() {
+			dx, dy = dy, dx
+		}
+		mode := e.Get("deltaMode").Int()
+		switch mode {
+		case 0x01: // DOM_DELTA_LINE
+			dx *= 10
+			dy *= 10
+		case 0x02: // DOM_DELTA_PAGE
+			dx *= 120
+			dy *= 120
+		}
+		w.pointerEvent(pointer.Scroll, float32(dx), float32(dy), e)
+		return nil
+	})
+	w.addEventListener(w.cnv, "touchstart", func(this js.Value, args []js.Value) interface{} {
+		w.touchEvent(pointer.Press, args[0])
+		if w.requestFocus {
+			w.focus() // iOS can only focus inside a Touch event.
+			w.requestFocus = false
+		}
+		return nil
+	})
+	w.addEventListener(w.cnv, "touchend", func(this js.Value, args []js.Value) interface{} {
+		w.touchEvent(pointer.Release, args[0])
+		return nil
+	})
+	w.addEventListener(w.cnv, "touchmove", func(this js.Value, args []js.Value) interface{} {
+		w.touchEvent(pointer.Move, args[0])
+		return nil
+	})
+	w.addEventListener(w.cnv, "touchcancel", func(this js.Value, args []js.Value) interface{} {
+		// Cancel all touches even if only one touch was cancelled.
+		for i := range w.touches {
+			w.touches[i] = js.Null()
+		}
+		w.touches = w.touches[:0]
+		w.processEvent(pointer.Event{
+			Kind:   pointer.Cancel,
+			Source: pointer.Touch,
+		})
+		return nil
+	})
+	w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
+		w.config.Focused = true
+		w.processEvent(ConfigEvent{Config: w.config})
+		return nil
+	})
+	w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
+		w.config.Focused = false
+		w.processEvent(ConfigEvent{Config: w.config})
+		w.blur()
+		return nil
+	})
+	w.addEventListener(w.tarea, "keydown", func(this js.Value, args []js.Value) interface{} {
+		w.keyEvent(args[0], key.Press)
+		return nil
+	})
+	w.addEventListener(w.tarea, "keyup", func(this js.Value, args []js.Value) interface{} {
+		w.keyEvent(args[0], key.Release)
+		return nil
+	})
+	w.addEventListener(w.tarea, "compositionstart", func(this js.Value, args []js.Value) interface{} {
+		w.composing = true
+		return nil
+	})
+	w.addEventListener(w.tarea, "compositionend", func(this js.Value, args []js.Value) interface{} {
+		w.composing = false
+		w.flushInput()
+		return nil
+	})
+	w.addEventListener(w.tarea, "input", func(this js.Value, args []js.Value) interface{} {
+		if w.composing {
+			return nil
+		}
+		w.flushInput()
+		return nil
+	})
+	w.addEventListener(w.tarea, "paste", func(this js.Value, args []js.Value) interface{} {
+		if w.clipboard.IsUndefined() {
+			return nil
+		}
+		// Prevents duplicated-paste, since "paste" is already handled through Clipboard API.
+		args[0].Call("preventDefault")
+		return nil
+	})
+}
+
+func (w *window) addHistory() {
+	w.browserHistory.Call("pushState", nil, nil, w.window.Get("location").Get("href"))
+}
+
+func (w *window) flushInput() {
+	val := w.tarea.Get("value").String()
+	w.tarea.Set("value", "")
+	w.w.EditorInsert(string(val))
+}
+
+func (w *window) blur() {
+	w.tarea.Call("blur")
+	w.requestFocus = false
+}
+
+func (w *window) focus() {
+	w.tarea.Call("focus")
+	w.requestFocus = true
+}
+
+func (w *window) keyboard(hint key.InputHint) {
+	var m string
+	switch hint {
+	case key.HintAny:
+		m = "text"
+	case key.HintText:
+		m = "text"
+	case key.HintNumeric:
+		m = "decimal"
+	case key.HintEmail:
+		m = "email"
+	case key.HintURL:
+		m = "url"
+	case key.HintTelephone:
+		m = "tel"
+	case key.HintPassword:
+		m = "password"
+	default:
+		m = "text"
+	}
+	w.tarea.Set("inputMode", m)
+}
+
+func (w *window) keyEvent(e js.Value, ks key.State) {
+	k := e.Get("key").String()
+	if n, ok := translateKey(k); ok {
+		cmd := key.Event{
+			Name:      n,
+			Modifiers: modifiersFor(e),
+			State:     ks,
+		}
+		w.processEvent(cmd)
+	}
+}
+
+func (w *window) ProcessEvent(e event.Event) {
+	w.processEvent(e)
+}
+
+func (w *window) processEvent(e event.Event) bool {
+	if !w.w.ProcessEvent(e) {
+		return false
+	}
+	select {
+	case w.wakeups <- struct{}{}:
+	default:
+	}
+	return true
+}
+
+func (w *window) Event() event.Event {
+	for {
+		evt, ok := w.w.nextEvent()
+		if ok {
+			if _, destroy := evt.(DestroyEvent); destroy {
+				w.cleanup()
+			}
+			return evt
+		}
+		<-w.wakeups
+	}
+}
+
+func (w *window) Invalidate() {
+	w.w.Invalidate()
+}
+
+func (w *window) Run(f func()) {
+	f()
+}
+
+func (w *window) Frame(frame *op.Ops) {
+	w.w.ProcessFrame(frame, nil)
+}
+
+// modifiersFor returns the modifier set for a DOM MouseEvent or
+// KeyEvent.
+func modifiersFor(e js.Value) key.Modifiers {
+	var mods key.Modifiers
+	if e.Get("getModifierState").IsUndefined() {
+		// Some browsers doesn't support getModifierState.
+		return mods
+	}
+	if e.Call("getModifierState", "Alt").Bool() {
+		mods |= key.ModAlt
+	}
+	if e.Call("getModifierState", "Control").Bool() {
+		mods |= key.ModCtrl
+	}
+	if e.Call("getModifierState", "Shift").Bool() {
+		mods |= key.ModShift
+	}
+	return mods
+}
+
+func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
+	e.Call("preventDefault")
+	t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
+	changedTouches := e.Get("changedTouches")
+	n := changedTouches.Length()
+	rect := w.cnv.Call("getBoundingClientRect")
+	scale := w.scale
+	var mods key.Modifiers
+	if e.Get("shiftKey").Bool() {
+		mods |= key.ModShift
+	}
+	if e.Get("altKey").Bool() {
+		mods |= key.ModAlt
+	}
+	if e.Get("ctrlKey").Bool() {
+		mods |= key.ModCtrl
+	}
+	for i := 0; i < n; i++ {
+		touch := changedTouches.Index(i)
+		pid := w.touchIDFor(touch)
+		x, y := touch.Get("clientX").Float(), touch.Get("clientY").Float()
+		x -= rect.Get("left").Float()
+		y -= rect.Get("top").Float()
+		pos := f32.Point{
+			X: float32(x) * scale,
+			Y: float32(y) * scale,
+		}
+		w.processEvent(pointer.Event{
+			Kind:      kind,
+			Source:    pointer.Touch,
+			Position:  pos,
+			PointerID: pid,
+			Time:      t,
+			Modifiers: mods,
+		})
+	}
+}
+
+func (w *window) touchIDFor(touch js.Value) pointer.ID {
+	id := touch.Get("identifier")
+	for i, id2 := range w.touches {
+		if id2.Equal(id) {
+			return pointer.ID(i)
+		}
+	}
+	pid := pointer.ID(len(w.touches))
+	w.touches = append(w.touches, id)
+	return pid
+}
+
+func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
+	e.Call("preventDefault")
+	x, y := e.Get("clientX").Float(), e.Get("clientY").Float()
+	rect := w.cnv.Call("getBoundingClientRect")
+	x -= rect.Get("left").Float()
+	y -= rect.Get("top").Float()
+	scale := w.scale
+	pos := f32.Point{
+		X: float32(x) * scale,
+		Y: float32(y) * scale,
+	}
+	scroll := f32.Point{
+		X: dx * scale,
+		Y: dy * scale,
+	}
+	t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
+	jbtns := e.Get("buttons").Int()
+	var btns pointer.Buttons
+	if jbtns&1 != 0 {
+		btns |= pointer.ButtonPrimary
+	}
+	if jbtns&2 != 0 {
+		btns |= pointer.ButtonSecondary
+	}
+	if jbtns&4 != 0 {
+		btns |= pointer.ButtonTertiary
+	}
+	w.processEvent(pointer.Event{
+		Kind:      kind,
+		Source:    pointer.Mouse,
+		Buttons:   btns,
+		Position:  pos,
+		Scroll:    scroll,
+		Time:      t,
+		Modifiers: modifiersFor(e),
+	})
+}
+
+func (w *window) addEventListener(this js.Value, event string, f func(this js.Value, args []js.Value) interface{}) {
+	jsf := w.funcOf(f)
+	this.Call("addEventListener", event, jsf)
+	w.cleanfuncs = append(w.cleanfuncs, func() {
+		this.Call("removeEventListener", event, jsf)
+	})
+}
+
+// funcOf is like js.FuncOf but adds the js.Func to a list of
+// functions to be released during cleanup.
+func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.Func {
+	jsf := js.FuncOf(f)
+	w.cleanfuncs = append(w.cleanfuncs, jsf.Release)
+	return jsf
+}
+
+func (w *window) EditorStateChanged(old, new editorState) {}
+
+func (w *window) SetAnimating(anim bool) {
+	w.animating = anim
+	if anim && !w.animRequested {
+		w.animRequested = true
+		w.requestAnimationFrame.Invoke(w.redraw)
+	}
+}
+
+func (w *window) ReadClipboard() {
+	if w.clipboard.IsUndefined() {
+		return
+	}
+	if w.clipboard.Get("readText").IsUndefined() {
+		return
+	}
+	w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
+}
+
+func (w *window) WriteClipboard(mime string, s []byte) {
+	if w.clipboard.IsUndefined() {
+		return
+	}
+	if w.clipboard.Get("writeText").IsUndefined() {
+		return
+	}
+	w.clipboard.Call("writeText", string(s))
+}
+
+func (w *window) Configure(options []Option) {
+	prev := w.config
+	cnf := w.config
+	cnf.apply(unit.Metric{}, options)
+	// Decorations are never disabled.
+	cnf.Decorated = true
+
+	if prev.Title != cnf.Title {
+		w.config.Title = cnf.Title
+		w.document.Set("title", cnf.Title)
+	}
+	if prev.Mode != cnf.Mode {
+		w.windowMode(cnf.Mode)
+	}
+	if prev.NavigationColor != cnf.NavigationColor {
+		w.config.NavigationColor = cnf.NavigationColor
+		w.navigationColor(cnf.NavigationColor)
+	}
+	if prev.Orientation != cnf.Orientation {
+		w.config.Orientation = cnf.Orientation
+		w.orientation(cnf.Orientation)
+	}
+	if cnf.Decorated != prev.Decorated {
+		w.config.Decorated = cnf.Decorated
+	}
+	w.processEvent(ConfigEvent{Config: w.config})
+}
+
+func (w *window) Perform(system.Action) {}
+
+var webCursor = [...]string{
+	pointer.CursorDefault:                  "default",
+	pointer.CursorNone:                     "none",
+	pointer.CursorText:                     "text",
+	pointer.CursorVerticalText:             "vertical-text",
+	pointer.CursorPointer:                  "pointer",
+	pointer.CursorCrosshair:                "crosshair",
+	pointer.CursorAllScroll:                "all-scroll",
+	pointer.CursorColResize:                "col-resize",
+	pointer.CursorRowResize:                "row-resize",
+	pointer.CursorGrab:                     "grab",
+	pointer.CursorGrabbing:                 "grabbing",
+	pointer.CursorNotAllowed:               "not-allowed",
+	pointer.CursorWait:                     "wait",
+	pointer.CursorProgress:                 "progress",
+	pointer.CursorNorthWestResize:          "nw-resize",
+	pointer.CursorNorthEastResize:          "ne-resize",
+	pointer.CursorSouthWestResize:          "sw-resize",
+	pointer.CursorSouthEastResize:          "se-resize",
+	pointer.CursorNorthSouthResize:         "ns-resize",
+	pointer.CursorEastWestResize:           "ew-resize",
+	pointer.CursorWestResize:               "w-resize",
+	pointer.CursorEastResize:               "e-resize",
+	pointer.CursorNorthResize:              "n-resize",
+	pointer.CursorSouthResize:              "s-resize",
+	pointer.CursorNorthEastSouthWestResize: "nesw-resize",
+	pointer.CursorNorthWestSouthEastResize: "nwse-resize",
+}
+
+func (w *window) SetCursor(cursor pointer.Cursor) {
+	style := w.cnv.Get("style")
+	style.Set("cursor", webCursor[cursor])
+}
+
+func (w *window) ShowTextInput(show bool) {
+	// Run in a goroutine to avoid a deadlock if the
+	// focus change result in an event.
+	if show {
+		w.focus()
+	} else {
+		w.blur()
+	}
+}
+
+func (w *window) SetInputHint(mode key.InputHint) {
+	w.keyboard(mode)
+}
+
+func (w *window) resize() {
+	w.scale = float32(w.window.Get("devicePixelRatio").Float())
+
+	rect := w.cnv.Call("getBoundingClientRect")
+	size := image.Point{
+		X: int(float32(rect.Get("width").Float()) * w.scale),
+		Y: int(float32(rect.Get("height").Float()) * w.scale),
+	}
+	if size != w.config.Size {
+		w.config.Size = size
+		w.processEvent(ConfigEvent{Config: w.config})
+	}
+
+	if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
+		w.inset.X = float32(w.config.Size.X) - float32(vx.Float())*w.scale
+		w.inset.Y = float32(w.config.Size.Y) - float32(vy.Float())*w.scale
+	}
+
+	if w.config.Size.X == 0 || w.config.Size.Y == 0 {
+		return
+	}
+
+	w.cnv.Set("width", w.config.Size.X)
+	w.cnv.Set("height", w.config.Size.Y)
+}
+
+func (w *window) draw(sync bool) {
+	if w.contextStatus == contextStatusLost {
+		return
+	}
+	anim := w.animating
+	w.animRequested = anim
+	if anim {
+		w.requestAnimationFrame.Invoke(w.redraw)
+	} else if !sync {
+		return
+	}
+	size, insets, metric := w.getConfig()
+	if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
+		return
+	}
+
+	w.processEvent(frameEvent{
+		FrameEvent: FrameEvent{
+			Now:    time.Now(),
+			Size:   size,
+			Insets: insets,
+			Metric: metric,
+		},
+		Sync: sync,
+	})
+}
+
+func (w *window) getConfig() (image.Point, Insets, unit.Metric) {
+	invscale := unit.Dp(1. / w.scale)
+	return image.Pt(w.config.Size.X, w.config.Size.Y),
+		Insets{
+			Bottom: unit.Dp(w.inset.Y) * invscale,
+			Right:  unit.Dp(w.inset.X) * invscale,
+		}, unit.Metric{
+			PxPerDp: w.scale,
+			PxPerSp: w.scale,
+		}
+}
+
+func (w *window) windowMode(mode WindowMode) {
+	switch mode {
+	case Windowed:
+		if !w.document.Get("fullscreenElement").Truthy() {
+			return // Browser is already Windowed.
+		}
+		if !w.document.Get("exitFullscreen").Truthy() {
+			return // Browser doesn't support such feature.
+		}
+		w.document.Call("exitFullscreen")
+		w.config.Mode = Windowed
+	case Fullscreen:
+		elem := w.document.Get("documentElement")
+		if !elem.Get("requestFullscreen").Truthy() {
+			return // Browser doesn't support such feature.
+		}
+		elem.Call("requestFullscreen")
+		w.config.Mode = Fullscreen
+	}
+}
+
+func (w *window) orientation(mode Orientation) {
+	if j := w.screenOrientation; !j.Truthy() || !j.Get("unlock").Truthy() || !j.Get("lock").Truthy() {
+		return // Browser don't support Screen Orientation API.
+	}
+
+	switch mode {
+	case AnyOrientation:
+		w.screenOrientation.Call("unlock")
+	case LandscapeOrientation:
+		w.screenOrientation.Call("lock", "landscape").Call("then", w.redraw)
+	case PortraitOrientation:
+		w.screenOrientation.Call("lock", "portrait").Call("then", w.redraw)
+	}
+}
+
+func (w *window) navigationColor(c color.NRGBA) {
+	theme := w.head.Call("querySelector", `meta[name="theme-color"]`)
+	if !theme.Truthy() {
+		theme = w.document.Call("createElement", "meta")
+		theme.Set("name", "theme-color")
+		w.head.Call("appendChild", theme)
+	}
+	rgba := f32color.NRGBAToRGBA(c)
+	theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
+}
+
+func osMain() {
+	select {}
+}
+
+func translateKey(k string) (key.Name, bool) {
+	var n key.Name
+
+	switch k {
+	case "ArrowUp":
+		n = key.NameUpArrow
+	case "ArrowDown":
+		n = key.NameDownArrow
+	case "ArrowLeft":
+		n = key.NameLeftArrow
+	case "ArrowRight":
+		n = key.NameRightArrow
+	case "Escape":
+		n = key.NameEscape
+	case "Enter":
+		n = key.NameReturn
+	case "Backspace":
+		n = key.NameDeleteBackward
+	case "Delete":
+		n = key.NameDeleteForward
+	case "Home":
+		n = key.NameHome
+	case "End":
+		n = key.NameEnd
+	case "PageUp":
+		n = key.NamePageUp
+	case "PageDown":
+		n = key.NamePageDown
+	case "Tab":
+		n = key.NameTab
+	case " ":
+		n = key.NameSpace
+	case "F1":
+		n = key.NameF1
+	case "F2":
+		n = key.NameF2
+	case "F3":
+		n = key.NameF3
+	case "F4":
+		n = key.NameF4
+	case "F5":
+		n = key.NameF5
+	case "F6":
+		n = key.NameF6
+	case "F7":
+		n = key.NameF7
+	case "F8":
+		n = key.NameF8
+	case "F9":
+		n = key.NameF9
+	case "F10":
+		n = key.NameF10
+	case "F11":
+		n = key.NameF11
+	case "F12":
+		n = key.NameF12
+	case "Control":
+		n = key.NameCtrl
+	case "Shift":
+		n = key.NameShift
+	case "Alt":
+		n = key.NameAlt
+	case "OS":
+		n = key.NameSuper
+	default:
+		r, s := utf8.DecodeRuneInString(k)
+		// If there is exactly one printable character, return that.
+		if s == len(k) && unicode.IsPrint(r) {
+			return key.Name(strings.ToUpper(k)), true
+		}
+		return "", false
+	}
+	return n, true
+}
+
+func (JSViewEvent) implementsViewEvent() {}
+func (JSViewEvent) ImplementsEvent()     {}
+func (j JSViewEvent) Valid() bool {
+	return !(j.Element.IsNull() || j.Element.IsUndefined())
+}

+ 1168 - 0
vendor/gioui.org/app/os_macos.go

@@ -0,0 +1,1168 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build darwin && !ios
+// +build darwin,!ios
+
+package app
+
+import (
+	"errors"
+	"image"
+	"io"
+	"runtime"
+	"runtime/cgo"
+	"strings"
+	"time"
+	"unicode"
+	"unicode/utf8"
+
+	"gioui.org/internal/f32"
+	"gioui.org/io/event"
+	"gioui.org/io/key"
+	"gioui.org/io/pointer"
+	"gioui.org/io/system"
+	"gioui.org/io/transfer"
+	"gioui.org/op"
+	"gioui.org/unit"
+
+	_ "gioui.org/internal/cocoainit"
+)
+
+/*
+#cgo CFLAGS: -Werror -Wno-deprecated-declarations -fobjc-arc -x objective-c
+#cgo LDFLAGS: -framework AppKit -framework QuartzCore
+
+#include <AppKit/AppKit.h>
+
+#define MOUSE_MOVE 1
+#define MOUSE_UP 2
+#define MOUSE_DOWN 3
+#define MOUSE_SCROLL 4
+
+__attribute__ ((visibility ("hidden"))) void gio_main(void);
+__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(int presentWithTrans);
+__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
+__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
+
+static void writeClipboard(CFTypeRef str) {
+	@autoreleasepool {
+		NSString *s = (__bridge NSString *)str;
+		NSPasteboard *p = NSPasteboard.generalPasteboard;
+		[p declareTypes:@[NSPasteboardTypeString] owner:nil];
+		[p setString:s forType:NSPasteboardTypeString];
+	}
+}
+
+static CFTypeRef readClipboard(void) {
+	@autoreleasepool {
+		NSPasteboard *p = NSPasteboard.generalPasteboard;
+		NSString *content = [p stringForType:NSPasteboardTypeString];
+		return (__bridge_retained CFTypeRef)content;
+	}
+}
+
+static CGFloat viewHeight(CFTypeRef viewRef) {
+	@autoreleasepool {
+		NSView *view = (__bridge NSView *)viewRef;
+		return [view bounds].size.height;
+	}
+}
+
+static CGFloat viewWidth(CFTypeRef viewRef) {
+	@autoreleasepool {
+		NSView *view = (__bridge NSView *)viewRef;
+		return [view bounds].size.width;
+	}
+}
+
+static CGFloat getScreenBackingScale(void) {
+	@autoreleasepool {
+		return [NSScreen.mainScreen backingScaleFactor];
+	}
+}
+
+static CGFloat getViewBackingScale(CFTypeRef viewRef) {
+	@autoreleasepool {
+		NSView *view = (__bridge NSView *)viewRef;
+		return [view.window backingScaleFactor];
+	}
+}
+
+static void setNeedsDisplay(CFTypeRef viewRef) {
+	@autoreleasepool {
+		NSView *view = (__bridge NSView *)viewRef;
+		[view setNeedsDisplay:YES];
+	}
+}
+
+static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		return [window cascadeTopLeftFromPoint:topLeft];
+	}
+}
+
+static void makeKeyAndOrderFront(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		[window makeKeyAndOrderFront:nil];
+	}
+}
+
+static void makeFirstResponder(CFTypeRef windowRef, CFTypeRef viewRef) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		NSView *view = (__bridge NSView *)viewRef;
+		[window makeFirstResponder:view];
+	}
+}
+
+static void toggleFullScreen(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		[window toggleFullScreen:nil];
+	}
+}
+
+static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		return [window styleMask];
+	}
+}
+
+static void setWindowStyleMask(CFTypeRef windowRef, NSWindowStyleMask mask) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		window.styleMask = mask;
+	}
+}
+
+static void setWindowTitleVisibility(CFTypeRef windowRef, NSWindowTitleVisibility state) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		window.titleVisibility = state;
+	}
+}
+
+static void setWindowTitlebarAppearsTransparent(CFTypeRef windowRef, int transparent) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		window.titlebarAppearsTransparent = (BOOL)transparent;
+	}
+}
+
+static void setWindowStandardButtonHidden(CFTypeRef windowRef, NSWindowButton btn, int hide) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		[window standardWindowButton:btn].hidden = (BOOL)hide;
+	}
+}
+
+static void performWindowDragWithEvent(CFTypeRef windowRef, CFTypeRef evt) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		[window performWindowDragWithEvent:(__bridge NSEvent*)evt];
+	}
+}
+
+static void closeWindow(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow* window = (__bridge NSWindow *)windowRef;
+		[window performClose:nil];
+	}
+}
+
+static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
+	@autoreleasepool {
+		NSWindow* window = (__bridge NSWindow *)windowRef;
+		NSSize size = NSMakeSize(width, height);
+		[window setContentSize:size];
+	}
+}
+
+static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
+	@autoreleasepool {
+		NSWindow* window = (__bridge NSWindow *)windowRef;
+		window.contentMinSize = NSMakeSize(width, height);
+	}
+}
+
+static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
+	@autoreleasepool {
+		NSWindow* window = (__bridge NSWindow *)windowRef;
+		window.contentMaxSize = NSMakeSize(width, height);
+	}
+}
+
+static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
+	@autoreleasepool {
+		NSWindow* window = (__bridge NSWindow *)windowRef;
+		NSRect r = NSMakeRect(x, y, w, h);
+		[window setFrame:r display:YES];
+	}
+}
+
+static void resetLayerFrame(CFTypeRef viewRef) {
+	@autoreleasepool {
+		NSView* view = (__bridge NSView *)viewRef;
+		NSRect r = view.frame;
+		view.layer.frame = r;
+	}
+}
+
+static void hideWindow(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow* window = (__bridge NSWindow *)windowRef;
+		[window miniaturize:window];
+	}
+}
+
+static void unhideWindow(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow* window = (__bridge NSWindow *)windowRef;
+		[window deminiaturize:window];
+	}
+}
+
+static NSRect getScreenFrame(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow* window = (__bridge NSWindow *)windowRef;
+		return [[window screen] frame];
+	}
+}
+
+static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		window.title = (__bridge NSString *)titleRef;
+	}
+}
+
+static int isWindowZoomed(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		return window.zoomed ? 1 : 0;
+	}
+}
+
+static int isWindowMiniaturized(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		return window.miniaturized ? 1 : 0;
+	}
+}
+
+static void zoomWindow(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		[window zoom:nil];
+	}
+}
+
+static CFTypeRef layerForView(CFTypeRef viewRef) {
+	@autoreleasepool {
+		NSView *view = (__bridge NSView *)viewRef;
+		return (__bridge CFTypeRef)view.layer;
+	}
+}
+
+static CFTypeRef windowForView(CFTypeRef viewRef) {
+	@autoreleasepool {
+		NSView *view = (__bridge NSView *)viewRef;
+		return (__bridge CFTypeRef)view.window;
+	}
+}
+
+static void raiseWindow(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
+		if (![currentApp isActive]) {
+			[currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
+		}
+		NSWindow* window = (__bridge NSWindow *)windowRef;
+		[window makeKeyAndOrderFront:nil];
+	}
+}
+
+static CFTypeRef createInputContext(CFTypeRef clientRef) {
+	@autoreleasepool {
+		id<NSTextInputClient> client = (__bridge id<NSTextInputClient>)clientRef;
+		NSTextInputContext *ctx = [[NSTextInputContext alloc] initWithClient:client];
+		return CFBridgingRetain(ctx);
+	}
+}
+
+static void discardMarkedText(CFTypeRef viewRef) {
+	@autoreleasepool {
+		id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
+		NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
+		if (view == [ctx client]) {
+			[ctx discardMarkedText];
+		}
+	}
+}
+
+static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
+	@autoreleasepool {
+		id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
+		NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
+		if (view == [ctx client]) {
+			[ctx invalidateCharacterCoordinates];
+		}
+	}
+}
+
+static void interpretKeyEvents(CFTypeRef viewRef, CFTypeRef eventRef) {
+	@autoreleasepool {
+		NSView *view = (__bridge NSView *)viewRef;
+		NSEvent *event = (__bridge NSEvent *)eventRef;
+		[view interpretKeyEvents:[NSArray arrayWithObject:event]];
+	}
+}
+
+static int isMiniaturized(CFTypeRef windowRef) {
+	@autoreleasepool {
+		NSWindow *window = (__bridge NSWindow *)windowRef;
+		return window.miniaturized ? 1 : 0;
+	}
+}
+*/
+import "C"
+
+func init() {
+	// Darwin requires that UI operations happen on the main thread only.
+	runtime.LockOSThread()
+}
+
+// AppKitViewEvent notifies the client of changes to the window AppKit handles.
+// The handles are retained until another AppKitViewEvent is sent.
+type AppKitViewEvent struct {
+	// View is a CFTypeRef for the NSView for the window.
+	View uintptr
+	// Layer is a CFTypeRef of the CALayer of View.
+	Layer uintptr
+}
+
+type window struct {
+	view        C.CFTypeRef
+	w           *callbacks
+	anim        bool
+	displayLink *displayLink
+	// redraw is a single entry channel for making sure only one
+	// display link redraw request is in flight.
+	redraw      chan struct{}
+	cursor      pointer.Cursor
+	pointerBtns pointer.Buttons
+	loop        *eventLoop
+	lastMods    C.NSUInteger
+
+	scale  float32
+	config Config
+
+	keysDown map[key.Name]struct{}
+	// cmdKeys is for storing the current key event while
+	// waiting for a doCommandBySelector.
+	cmdKeys cmdKeys
+}
+
+type cmdKeys struct {
+	eventStr  string
+	eventMods key.Modifiers
+}
+
+// launched is closed when applicationDidFinishLaunching is called.
+var launched = make(chan struct{})
+
+// nextTopLeft is the offset to use for the next window's call to
+// cascadeTopLeftFromPoint.
+var nextTopLeft C.NSPoint
+
+func windowFor(h C.uintptr_t) *window {
+	return cgo.Handle(h).Value().(*window)
+}
+
+func (w *window) contextView() C.CFTypeRef {
+	return w.view
+}
+
+func (w *window) ReadClipboard() {
+	cstr := C.readClipboard()
+	if cstr != 0 {
+		defer C.CFRelease(cstr)
+	}
+	content := nsstringToString(cstr)
+	w.ProcessEvent(transfer.DataEvent{
+		Type: "application/text",
+		Open: func() io.ReadCloser {
+			return io.NopCloser(strings.NewReader(content))
+		},
+	})
+}
+
+func (w *window) WriteClipboard(mime string, s []byte) {
+	cstr := stringToNSString(string(s))
+	defer C.CFRelease(cstr)
+	C.writeClipboard(cstr)
+}
+
+func (w *window) updateWindowMode() {
+	w.scale = float32(C.getViewBackingScale(w.view))
+	wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
+	w.config.Size = image.Point{
+		X: int(wf*w.scale + .5),
+		Y: int(hf*w.scale + .5),
+	}
+	w.config.Mode = Windowed
+	window := C.windowForView(w.view)
+	if window == 0 {
+		return
+	}
+	style := int(C.getWindowStyleMask(C.windowForView(w.view)))
+	switch {
+	case style&C.NSWindowStyleMaskFullScreen != 0:
+		w.config.Mode = Fullscreen
+	case C.isWindowZoomed(window) != 0:
+		w.config.Mode = Maximized
+	}
+	w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0
+}
+
+func (w *window) Configure(options []Option) {
+	screenScale := float32(C.getScreenBackingScale())
+	cfg := configFor(screenScale)
+	cnf := w.config
+	cnf.apply(cfg, options)
+	window := C.windowForView(w.view)
+
+	mask := C.getWindowStyleMask(window)
+	fullscreen := mask&C.NSWindowStyleMaskFullScreen != 0
+	switch cnf.Mode {
+	case Fullscreen:
+		if C.isWindowMiniaturized(window) != 0 {
+			C.unhideWindow(window)
+		}
+		if !fullscreen {
+			C.toggleFullScreen(window)
+		}
+	case Minimized:
+		C.hideWindow(window)
+	case Maximized:
+		if C.isWindowMiniaturized(window) != 0 {
+			C.unhideWindow(window)
+		}
+		if fullscreen {
+			C.toggleFullScreen(window)
+		}
+		w.setTitle(cnf.Title)
+		if C.isWindowZoomed(window) == 0 {
+			C.zoomWindow(window)
+		}
+	case Windowed:
+		if C.isWindowMiniaturized(window) != 0 {
+			C.unhideWindow(window)
+		}
+		if fullscreen {
+			C.toggleFullScreen(window)
+		}
+		w.setTitle(cnf.Title)
+		w.config.Size = cnf.Size
+		cnf.Size = cnf.Size.Div(int(screenScale))
+		C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
+		w.config.MinSize = cnf.MinSize
+		cnf.MinSize = cnf.MinSize.Div(int(screenScale))
+		C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
+		w.config.MaxSize = cnf.MaxSize
+		cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
+		if cnf.MaxSize != (image.Point{}) {
+			C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
+		}
+		if C.isWindowZoomed(window) != 0 {
+			C.zoomWindow(window)
+		}
+	}
+	style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable)
+	style = C.NSWindowStyleMaskFullSizeContentView
+	mask &^= style
+	barTrans := C.int(C.NO)
+	titleVis := C.NSWindowTitleVisibility(C.NSWindowTitleVisible)
+	if !cnf.Decorated {
+		mask |= style
+		barTrans = C.YES
+		titleVis = C.NSWindowTitleHidden
+	}
+	C.setWindowTitlebarAppearsTransparent(window, barTrans)
+	C.setWindowTitleVisibility(window, titleVis)
+	C.setWindowStyleMask(window, mask)
+	C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
+	C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
+	C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
+	// When toggling the titlebar, the layer doesn't update its frame
+	// until the next resize. Force it.
+	C.resetLayerFrame(w.view)
+}
+
+func (w *window) setTitle(title string) {
+	w.config.Title = title
+	titleC := stringToNSString(title)
+	defer C.CFRelease(titleC)
+	C.setTitle(C.windowForView(w.view), titleC)
+}
+
+func (w *window) Perform(acts system.Action) {
+	window := C.windowForView(w.view)
+	walkActions(acts, func(a system.Action) {
+		switch a {
+		case system.ActionCenter:
+			r := C.getScreenFrame(window) // the screen size of the window
+			screenScale := float32(C.getScreenBackingScale())
+			sz := w.config.Size.Div(int(screenScale))
+			x := (int(r.size.width) - sz.X) / 2
+			y := (int(r.size.height) - sz.Y) / 2
+			C.setScreenFrame(window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y))
+		case system.ActionRaise:
+			C.raiseWindow(window)
+		}
+	})
+	if acts&system.ActionClose != 0 {
+		C.closeWindow(window)
+	}
+}
+
+func (w *window) SetCursor(cursor pointer.Cursor) {
+	w.cursor = windowSetCursor(w.cursor, cursor)
+}
+
+func (w *window) EditorStateChanged(old, new editorState) {
+	if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
+		C.discardMarkedText(w.view)
+		w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
+	}
+	if old.Selection.Caret != new.Selection.Caret || old.Selection.Transform != new.Selection.Transform {
+		C.invalidateCharacterCoordinates(w.view)
+	}
+}
+
+func (w *window) ShowTextInput(show bool) {}
+
+func (w *window) SetInputHint(_ key.InputHint) {}
+
+func (w *window) SetAnimating(anim bool) {
+	w.anim = anim
+	window := C.windowForView(w.view)
+	if w.anim && window != 0 && C.isMiniaturized(window) == 0 {
+		w.displayLink.Start()
+	} else {
+		w.displayLink.Stop()
+	}
+}
+
+func (w *window) runOnMain(f func()) {
+	runOnMain(func() {
+		// Make sure the view is still valid. The window might've been closed
+		// during the switch to the main thread.
+		if w.view != 0 {
+			f()
+		}
+	})
+}
+
+//export gio_onKeys
+func gio_onKeys(h C.uintptr_t, event C.CFTypeRef, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
+	w := windowFor(h)
+	if w.keysDown == nil {
+		w.keysDown = make(map[key.Name]struct{})
+	}
+	str := nsstringToString(cstr)
+	kmods := convertMods(mods)
+	ks := key.Release
+	if keyDown {
+		ks = key.Press
+		w.cmdKeys.eventStr = str
+		w.cmdKeys.eventMods = kmods
+		C.interpretKeyEvents(w.view, event)
+	}
+	for _, k := range str {
+		if n, ok := convertKey(k); ok {
+			ke := key.Event{
+				Name:      n,
+				Modifiers: kmods,
+				State:     ks,
+			}
+			if keyDown {
+				w.keysDown[ke.Name] = struct{}{}
+				if _, isCmd := convertCommandKey(k); isCmd || kmods.Contain(key.ModCommand) {
+					// doCommandBySelector already processed the event.
+					return
+				}
+			} else {
+				if _, pressed := w.keysDown[n]; !pressed {
+					continue
+				}
+				delete(w.keysDown, n)
+			}
+			w.ProcessEvent(ke)
+		}
+	}
+}
+
+//export gio_onCommandBySelector
+func gio_onCommandBySelector(h C.uintptr_t) C.bool {
+	w := windowFor(h)
+	ev := w.cmdKeys
+	w.cmdKeys = cmdKeys{}
+	handled := false
+	for _, k := range ev.eventStr {
+		n, ok := convertCommandKey(k)
+		if !ok && ev.eventMods.Contain(key.ModCommand) {
+			n, ok = convertKey(k)
+		}
+		if !ok {
+			continue
+		}
+		ke := key.Event{
+			Name:      n,
+			Modifiers: ev.eventMods,
+			State:     key.Press,
+		}
+		handled = w.processEvent(ke) || handled
+	}
+	return C.bool(handled)
+}
+
+//export gio_onFlagsChanged
+func gio_onFlagsChanged(h C.uintptr_t, curMods C.NSUInteger) {
+	w := windowFor(h)
+
+	mods := []C.NSUInteger{C.NSControlKeyMask, C.NSAlternateKeyMask, C.NSShiftKeyMask, C.NSCommandKeyMask}
+	keys := []key.Name{key.NameCtrl, key.NameAlt, key.NameShift, key.NameCommand}
+
+	for i, mod := range mods {
+		wasPressed := w.lastMods&mod != 0
+		isPressed := curMods&mod != 0
+
+		if wasPressed != isPressed {
+			st := key.Release
+			if isPressed {
+				st = key.Press
+			}
+			w.ProcessEvent(key.Event{
+				Name:  keys[i],
+				State: st,
+			})
+		}
+	}
+
+	w.lastMods = curMods
+}
+
+//export gio_onText
+func gio_onText(h C.uintptr_t, cstr C.CFTypeRef) {
+	str := nsstringToString(cstr)
+	w := windowFor(h)
+	w.w.EditorInsert(str)
+}
+
+//export gio_onMouse
+func gio_onMouse(h C.uintptr_t, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
+	w := windowFor(h)
+	t := time.Duration(float64(ti)*float64(time.Second) + .5)
+	xf, yf := float32(x)*w.scale, float32(y)*w.scale
+	dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
+	pos := f32.Point{X: xf, Y: yf}
+	var btn pointer.Buttons
+	switch cbtn {
+	case 0:
+		btn = pointer.ButtonPrimary
+	case 1:
+		btn = pointer.ButtonSecondary
+	case 2:
+		btn = pointer.ButtonTertiary
+	}
+	var typ pointer.Kind
+	switch cdir {
+	case C.MOUSE_MOVE:
+		typ = pointer.Move
+	case C.MOUSE_UP:
+		typ = pointer.Release
+		w.pointerBtns &^= btn
+	case C.MOUSE_DOWN:
+		typ = pointer.Press
+		w.pointerBtns |= btn
+		act, ok := w.w.ActionAt(pos)
+		if ok && w.config.Mode != Fullscreen {
+			switch act {
+			case system.ActionMove:
+				C.performWindowDragWithEvent(C.windowForView(w.view), evt)
+				return
+			}
+		}
+	case C.MOUSE_SCROLL:
+		typ = pointer.Scroll
+	default:
+		panic("invalid direction")
+	}
+	w.ProcessEvent(pointer.Event{
+		Kind:      typ,
+		Source:    pointer.Mouse,
+		Time:      t,
+		Buttons:   w.pointerBtns,
+		Position:  pos,
+		Scroll:    f32.Point{X: dxf, Y: dyf},
+		Modifiers: convertMods(mods),
+	})
+}
+
+//export gio_onDraw
+func gio_onDraw(h C.uintptr_t) {
+	w := windowFor(h)
+	w.draw()
+}
+
+//export gio_onFocus
+func gio_onFocus(h C.uintptr_t, focus C.int) {
+	w := windowFor(h)
+	w.SetCursor(w.cursor)
+	w.config.Focused = focus == 1
+	w.ProcessEvent(ConfigEvent{Config: w.config})
+}
+
+//export gio_onChangeScreen
+func gio_onChangeScreen(h C.uintptr_t, did uint64) {
+	w := windowFor(h)
+	w.displayLink.SetDisplayID(did)
+	C.setNeedsDisplay(w.view)
+}
+
+//export gio_hasMarkedText
+func gio_hasMarkedText(h C.uintptr_t) C.int {
+	w := windowFor(h)
+	state := w.w.EditorState()
+	if state.compose.Start != -1 {
+		return 1
+	}
+	return 0
+}
+
+//export gio_markedRange
+func gio_markedRange(h C.uintptr_t) C.NSRange {
+	w := windowFor(h)
+	state := w.w.EditorState()
+	rng := state.compose
+	start, end := rng.Start, rng.End
+	if start == -1 {
+		return C.NSMakeRange(C.NSNotFound, 0)
+	}
+	u16start := state.UTF16Index(start)
+	return C.NSMakeRange(
+		C.NSUInteger(u16start),
+		C.NSUInteger(state.UTF16Index(end)-u16start),
+	)
+}
+
+//export gio_selectedRange
+func gio_selectedRange(h C.uintptr_t) C.NSRange {
+	w := windowFor(h)
+	state := w.w.EditorState()
+	rng := state.Selection
+	start, end := rng.Start, rng.End
+	if start > end {
+		start, end = end, start
+	}
+	u16start := state.UTF16Index(start)
+	return C.NSMakeRange(
+		C.NSUInteger(u16start),
+		C.NSUInteger(state.UTF16Index(end)-u16start),
+	)
+}
+
+//export gio_unmarkText
+func gio_unmarkText(h C.uintptr_t) {
+	w := windowFor(h)
+	w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
+}
+
+//export gio_setMarkedText
+func gio_setMarkedText(h C.uintptr_t, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) {
+	w := windowFor(h)
+	str := nsstringToString(cstr)
+	state := w.w.EditorState()
+	rng := state.compose
+	if rng.Start == -1 {
+		rng = state.Selection.Range
+	}
+	if replaceRange.location != C.NSNotFound {
+		// replaceRange is relative to marked (or selected) text.
+		offset := state.UTF16Index(rng.Start)
+		start := state.RunesIndex(int(replaceRange.location) + offset)
+		end := state.RunesIndex(int(replaceRange.location+replaceRange.length) + offset)
+		rng = key.Range{
+			Start: start,
+			End:   end,
+		}
+	}
+	w.w.EditorReplace(rng, str)
+	comp := key.Range{
+		Start: rng.Start,
+		End:   rng.Start + utf8.RuneCountInString(str),
+	}
+	w.w.SetComposingRegion(comp)
+
+	sel := key.Range{Start: comp.End, End: comp.End}
+	if selRange.location != C.NSNotFound {
+		// selRange is relative to inserted text.
+		offset := state.UTF16Index(rng.Start)
+		start := state.RunesIndex(int(selRange.location) + offset)
+		end := state.RunesIndex(int(selRange.location+selRange.length) + offset)
+		sel = key.Range{
+			Start: start,
+			End:   end,
+		}
+	}
+	w.w.SetEditorSelection(sel)
+}
+
+//export gio_substringForProposedRange
+func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef {
+	w := windowFor(h)
+	state := w.w.EditorState()
+	start, end := state.Snippet.Start, state.Snippet.End
+	if start > end {
+		start, end = end, start
+	}
+	rng := key.Range{
+		Start: state.RunesIndex(int(crng.location)),
+		End:   state.RunesIndex(int(crng.location + crng.length)),
+	}
+	if rng.Start < start || end < rng.End {
+		w.w.SetEditorSnippet(rng)
+	}
+	u16start := state.UTF16Index(start)
+	actual.location = C.NSUInteger(u16start)
+	actual.length = C.NSUInteger(state.UTF16Index(end) - u16start)
+	return stringToNSString(state.Snippet.Text)
+}
+
+//export gio_insertText
+func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
+	w := windowFor(h)
+	str := nsstringToString(cstr)
+	// macOS IME in some cases calls insertText for command keys such as backspace
+	// instead of doCommandBySelector.
+	for _, r := range str {
+		if _, ok := convertCommandKey(r); ok {
+			w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
+			return
+		}
+	}
+	state := w.w.EditorState()
+	rng := state.compose
+	if rng.Start == -1 {
+		rng = state.Selection.Range
+	}
+	if crng.location != C.NSNotFound {
+		rng = key.Range{
+			Start: state.RunesIndex(int(crng.location)),
+			End:   state.RunesIndex(int(crng.location + crng.length)),
+		}
+	}
+	w.w.EditorReplace(rng, str)
+	w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
+	start := rng.Start
+	if rng.End < start {
+		start = rng.End
+	}
+	pos := start + utf8.RuneCountInString(str)
+	w.w.SetEditorSelection(key.Range{Start: pos, End: pos})
+}
+
+//export gio_characterIndexForPoint
+func gio_characterIndexForPoint(h C.uintptr_t, p C.NSPoint) C.NSUInteger {
+	return C.NSNotFound
+}
+
+//export gio_firstRectForCharacterRange
+func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.NSRect {
+	w := windowFor(h)
+	state := w.w.EditorState()
+	sel := state.Selection
+	u16start := state.UTF16Index(sel.Start)
+	actual.location = C.NSUInteger(u16start)
+	actual.length = 0
+	// Transform to NSView local coordinates (lower left origin, undo backing scale).
+	scale := 1. / float32(C.getViewBackingScale(w.view))
+	height := float32(C.viewHeight(w.view))
+	local := f32.Affine2D{}.Scale(f32.Pt(0, 0), f32.Pt(scale, -scale)).Offset(f32.Pt(0, height))
+	t := local.Mul(sel.Transform)
+	bounds := f32.Rectangle{
+		Min: t.Transform(sel.Pos.Sub(f32.Pt(0, sel.Ascent))),
+		Max: t.Transform(sel.Pos.Add(f32.Pt(0, sel.Descent))),
+	}.Canon()
+	sz := bounds.Size()
+	return C.NSMakeRect(
+		C.CGFloat(bounds.Min.X), C.CGFloat(bounds.Min.Y),
+		C.CGFloat(sz.X), C.CGFloat(sz.Y),
+	)
+}
+
+func (w *window) draw() {
+	cnf := w.config
+	w.updateWindowMode()
+	if w.config != cnf {
+		w.ProcessEvent(ConfigEvent{Config: w.config})
+	}
+	select {
+	case <-w.redraw:
+	default:
+	}
+	if w.anim {
+		w.SetAnimating(w.anim)
+	}
+	sz := w.config.Size
+	if sz.X == 0 || sz.Y == 0 {
+		return
+	}
+	cfg := configFor(w.scale)
+	w.ProcessEvent(frameEvent{
+		FrameEvent: FrameEvent{
+			Now:    time.Now(),
+			Size:   sz,
+			Metric: cfg,
+		},
+		Sync: true,
+	})
+}
+
+func (w *window) ProcessEvent(e event.Event) {
+	w.processEvent(e)
+}
+
+func (w *window) processEvent(e event.Event) bool {
+	handled := w.w.ProcessEvent(e)
+	w.loop.FlushEvents()
+	return handled
+}
+
+func (w *window) Event() event.Event {
+	return w.loop.Event()
+}
+
+func (w *window) Invalidate() {
+	w.loop.Invalidate()
+}
+
+func (w *window) Run(f func()) {
+	w.loop.Run(f)
+}
+
+func (w *window) Frame(frame *op.Ops) {
+	w.loop.Frame(frame)
+}
+
+func configFor(scale float32) unit.Metric {
+	return unit.Metric{
+		PxPerDp: scale,
+		PxPerSp: scale,
+	}
+}
+
+//export gio_onAttached
+func gio_onAttached(h C.uintptr_t, attached C.int) {
+	w := windowFor(h)
+	if attached != 0 {
+		layer := C.layerForView(w.view)
+		w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
+	} else {
+		w.ProcessEvent(AppKitViewEvent{})
+		w.SetAnimating(w.anim)
+	}
+}
+
+//export gio_onDestroy
+func gio_onDestroy(h C.uintptr_t) {
+	w := windowFor(h)
+	w.ProcessEvent(DestroyEvent{})
+	w.displayLink.Close()
+	w.displayLink = nil
+	cgo.Handle(h).Delete()
+	w.view = 0
+}
+
+//export gio_onFinishLaunching
+func gio_onFinishLaunching() {
+	close(launched)
+}
+
+func newWindow(win *callbacks, options []Option) {
+	<-launched
+	res := make(chan struct{})
+	runOnMain(func() {
+		w := &window{
+			redraw: make(chan struct{}, 1),
+			w:      win,
+		}
+		w.loop = newEventLoop(w.w, w.wakeup)
+		win.SetDriver(w)
+		res <- struct{}{}
+		var cnf Config
+		cnf.apply(unit.Metric{}, options)
+		if err := w.init(cnf.CustomRenderer); err != nil {
+			w.ProcessEvent(DestroyEvent{Err: err})
+			return
+		}
+		window := C.gio_createWindow(w.view, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y), 0, 0, 0, 0)
+		// Release our reference now that the NSWindow has it.
+		C.CFRelease(w.view)
+		w.Configure(options)
+		if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
+			// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
+			// and just returns the offset we need for the first window.
+			nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
+		}
+		nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
+		C.makeFirstResponder(window, w.view)
+		// makeKeyAndOrderFront assumes ownership of our window reference.
+		C.makeKeyAndOrderFront(window)
+	})
+	<-res
+}
+
+func (w *window) init(customRenderer bool) error {
+	presentWithTrans := 1
+	if customRenderer {
+		presentWithTrans = 0
+	}
+	view := C.gio_createView(C.int(presentWithTrans))
+	if view == 0 {
+		return errors.New("newOSWindow: failed to create view")
+	}
+	scale := float32(C.getViewBackingScale(view))
+	w.scale = scale
+	dl, err := newDisplayLink(func() {
+		select {
+		case w.redraw <- struct{}{}:
+		default:
+			return
+		}
+		w.runOnMain(func() {
+			C.setNeedsDisplay(w.view)
+		})
+	})
+	w.displayLink = dl
+	if err != nil {
+		C.CFRelease(view)
+		return err
+	}
+	C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
+	w.view = view
+	return nil
+}
+
+func osMain() {
+	if !isMainThread() {
+		panic("app.Main must run on the main goroutine")
+	}
+	C.gio_main()
+}
+
+func convertCommandKey(k rune) (key.Name, bool) {
+	var n key.Name
+	switch k {
+	case '\x1b': // ASCII escape.
+		n = key.NameEscape
+	case C.NSLeftArrowFunctionKey:
+		n = key.NameLeftArrow
+	case C.NSRightArrowFunctionKey:
+		n = key.NameRightArrow
+	case C.NSUpArrowFunctionKey:
+		n = key.NameUpArrow
+	case C.NSDownArrowFunctionKey:
+		n = key.NameDownArrow
+	case '\r':
+		n = key.NameReturn
+	case '\x03':
+		n = key.NameEnter
+	case C.NSHomeFunctionKey:
+		n = key.NameHome
+	case C.NSEndFunctionKey:
+		n = key.NameEnd
+	case '\x7f', '\b':
+		n = key.NameDeleteBackward
+	case C.NSDeleteFunctionKey:
+		n = key.NameDeleteForward
+	case '\t', 0x19:
+		n = key.NameTab
+	case C.NSPageUpFunctionKey:
+		n = key.NamePageUp
+	case C.NSPageDownFunctionKey:
+		n = key.NamePageDown
+	default:
+		return "", false
+	}
+	return n, true
+}
+
+func convertKey(k rune) (key.Name, bool) {
+	if n, ok := convertCommandKey(k); ok {
+		return n, true
+	}
+	var n key.Name
+	switch k {
+	case C.NSF1FunctionKey:
+		n = key.NameF1
+	case C.NSF2FunctionKey:
+		n = key.NameF2
+	case C.NSF3FunctionKey:
+		n = key.NameF3
+	case C.NSF4FunctionKey:
+		n = key.NameF4
+	case C.NSF5FunctionKey:
+		n = key.NameF5
+	case C.NSF6FunctionKey:
+		n = key.NameF6
+	case C.NSF7FunctionKey:
+		n = key.NameF7
+	case C.NSF8FunctionKey:
+		n = key.NameF8
+	case C.NSF9FunctionKey:
+		n = key.NameF9
+	case C.NSF10FunctionKey:
+		n = key.NameF10
+	case C.NSF11FunctionKey:
+		n = key.NameF11
+	case C.NSF12FunctionKey:
+		n = key.NameF12
+	case 0x20:
+		n = key.NameSpace
+	default:
+		k = unicode.ToUpper(k)
+		if !unicode.IsPrint(k) {
+			return "", false
+		}
+		n = key.Name(k)
+	}
+	return n, true
+}
+
+func convertMods(mods C.NSUInteger) key.Modifiers {
+	var kmods key.Modifiers
+	if mods&C.NSAlternateKeyMask != 0 {
+		kmods |= key.ModAlt
+	}
+	if mods&C.NSControlKeyMask != 0 {
+		kmods |= key.ModCtrl
+	}
+	if mods&C.NSCommandKeyMask != 0 {
+		kmods |= key.ModCommand
+	}
+	if mods&C.NSShiftKeyMask != 0 {
+		kmods |= key.ModShift
+	}
+	return kmods
+}
+
+func (AppKitViewEvent) implementsViewEvent() {}
+func (AppKitViewEvent) ImplementsEvent()     {}
+func (a AppKitViewEvent) Valid() bool {
+	return a != (AppKitViewEvent{})
+}

+ 459 - 0
vendor/gioui.org/app/os_macos.m

@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build darwin,!ios
+
+#import <AppKit/AppKit.h>
+
+#include "_cgo_export.h"
+
+__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
+
+@interface GioAppDelegate : NSObject<NSApplicationDelegate>
+@end
+
+@interface GioWindowDelegate : NSObject<NSWindowDelegate>
+@end
+
+@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
+@property uintptr_t handle;
+@property BOOL presentWithTrans;
+@end
+
+@implementation GioWindowDelegate
+- (void)windowWillMiniaturize:(NSNotification *)notification {
+	NSWindow *window = (NSWindow *)[notification object];
+  GioView *view = (GioView *)window.contentView;
+	gio_onDraw(view.handle);
+}
+- (void)windowDidDeminiaturize:(NSNotification *)notification {
+	NSWindow *window = (NSWindow *)[notification object];
+  GioView *view = (GioView *)window.contentView;
+	gio_onDraw(view.handle);
+}
+- (void)windowWillEnterFullScreen:(NSNotification *)notification {
+	NSWindow *window = (NSWindow *)[notification object];
+  GioView *view = (GioView *)window.contentView;
+	gio_onDraw(view.handle);
+}
+- (void)windowWillExitFullScreen:(NSNotification *)notification {
+	NSWindow *window = (NSWindow *)[notification object];
+  GioView *view = (GioView *)window.contentView;
+	gio_onDraw(view.handle);
+}
+- (void)windowDidChangeScreen:(NSNotification *)notification {
+	NSWindow *window = (NSWindow *)[notification object];
+	CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
+  GioView *view = (GioView *)window.contentView;
+	gio_onChangeScreen(view.handle, dispID);
+}
+- (void)windowDidBecomeKey:(NSNotification *)notification {
+	NSWindow *window = (NSWindow *)[notification object];
+	GioView *view = (GioView *)window.contentView;
+	if ([window firstResponder] == view) {
+		gio_onFocus(view.handle, 1);
+	}
+}
+- (void)windowDidResignKey:(NSNotification *)notification {
+	NSWindow *window = (NSWindow *)[notification object];
+	GioView *view = (GioView *)window.contentView;
+	if ([window firstResponder] == view) {
+		gio_onFocus(view.handle, 0);
+	}
+}
+@end
+
+static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
+	NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
+	if (!event.hasPreciseScrollingDeltas) {
+		// dx and dy are in rows and columns.
+		dx *= 10;
+		dy *= 10;
+	}
+	// Origin is in the lower left corner. Convert to upper left.
+	CGFloat height = view.bounds.size.height;
+	gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
+}
+
+@implementation GioView
+- (void)setFrameSize:(NSSize)newSize {
+	[super setFrameSize:newSize];
+	[self setNeedsDisplay:YES];
+}
+// drawRect is called when OpenGL is used, displayLayer otherwise.
+// Don't know why.
+- (void)drawRect:(NSRect)r {
+	gio_onDraw(self.handle);
+}
+- (void)displayLayer:(CALayer *)layer {
+	layer.contentsScale = self.window.backingScaleFactor;
+	gio_onDraw(self.handle);
+}
+- (CALayer *)makeBackingLayer {
+	CALayer *layer = gio_layerFactory(self.presentWithTrans);
+	layer.delegate = self;
+	return layer;
+}
+- (void)viewDidMoveToWindow {
+	gio_onAttached(self.handle, self.window != nil ? 1 : 0);
+}
+- (void)mouseDown:(NSEvent *)event {
+	handleMouse(self, event, MOUSE_DOWN, 0, 0);
+}
+- (void)mouseUp:(NSEvent *)event {
+	handleMouse(self, event, MOUSE_UP, 0, 0);
+}
+- (void)rightMouseDown:(NSEvent *)event {
+	handleMouse(self, event, MOUSE_DOWN, 0, 0);
+}
+- (void)rightMouseUp:(NSEvent *)event {
+	handleMouse(self, event, MOUSE_UP, 0, 0);
+}
+- (void)otherMouseDown:(NSEvent *)event {
+	handleMouse(self, event, MOUSE_DOWN, 0, 0);
+}
+- (void)otherMouseUp:(NSEvent *)event {
+	handleMouse(self, event, MOUSE_UP, 0, 0);
+}
+- (void)mouseMoved:(NSEvent *)event {
+	handleMouse(self, event, MOUSE_MOVE, 0, 0);
+}
+- (void)mouseDragged:(NSEvent *)event {
+	handleMouse(self, event, MOUSE_MOVE, 0, 0);
+}
+- (void)rightMouseDragged:(NSEvent *)event {
+	handleMouse(self, event, MOUSE_MOVE, 0, 0);
+}
+- (void)otherMouseDragged:(NSEvent *)event {
+	handleMouse(self, event, MOUSE_MOVE, 0, 0);
+}
+- (void)scrollWheel:(NSEvent *)event {
+	CGFloat dx = -event.scrollingDeltaX;
+	CGFloat dy = -event.scrollingDeltaY;
+	handleMouse(self, event, MOUSE_SCROLL, dx, dy);
+}
+- (void)keyDown:(NSEvent *)event {
+	NSString *keys = [event charactersIgnoringModifiers];
+	gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
+}
+- (void)flagsChanged:(NSEvent *)event {
+	[self interpretKeyEvents:[NSArray arrayWithObject:event]];
+	gio_onFlagsChanged(self.handle, [event modifierFlags]);
+}
+- (void)keyUp:(NSEvent *)event {
+	NSString *keys = [event charactersIgnoringModifiers];
+	gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
+}
+- (void)insertText:(id)string {
+	gio_onText(self.handle, (__bridge CFTypeRef)string);
+}
+- (void)doCommandBySelector:(SEL)action {
+	if (!gio_onCommandBySelector(self.handle)) {
+		[super doCommandBySelector:action];
+	}
+}
+- (BOOL)hasMarkedText {
+	int res = gio_hasMarkedText(self.handle);
+	return res ? YES : NO;
+}
+- (NSRange)markedRange {
+	return gio_markedRange(self.handle);
+}
+- (NSRange)selectedRange {
+	return gio_selectedRange(self.handle);
+}
+- (void)unmarkText {
+	gio_unmarkText(self.handle);
+}
+- (void)setMarkedText:(id)string
+        selectedRange:(NSRange)selRange
+     replacementRange:(NSRange)replaceRange {
+	NSString *str;
+	// string is either an NSAttributedString or an NSString.
+	if ([string isKindOfClass:[NSAttributedString class]]) {
+		str = [string string];
+	} else {
+		str = string;
+	}
+	gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
+}
+- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
+	return nil;
+}
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
+                                                actualRange:(NSRangePointer)actualRange {
+	NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
+	return [[NSAttributedString alloc] initWithString:str attributes:nil];
+}
+- (void)insertText:(id)string
+  replacementRange:(NSRange)replaceRange {
+	NSString *str;
+	// string is either an NSAttributedString or an NSString.
+	if ([string isKindOfClass:[NSAttributedString class]]) {
+		str = [string string];
+	} else {
+		str = string;
+	}
+	gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
+}
+- (NSUInteger)characterIndexForPoint:(NSPoint)p {
+	return gio_characterIndexForPoint(self.handle, p);
+}
+- (NSRect)firstRectForCharacterRange:(NSRange)rng
+                         actualRange:(NSRangePointer)actual {
+    NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
+    r = [self convertRect:r toView:nil];
+    return [[self window] convertRectToScreen:r];
+}
+- (void)applicationWillUnhide:(NSNotification *)notification {
+	gio_onDraw(self.handle);
+}
+- (void)applicationDidHide:(NSNotification *)notification {
+	gio_onDraw(self.handle);
+}
+- (void)dealloc {
+	gio_onDestroy(self.handle);
+}
+- (BOOL) becomeFirstResponder {
+	gio_onFocus(self.handle, 1);
+	return [super becomeFirstResponder];
+ }
+- (BOOL) resignFirstResponder {
+	gio_onFocus(self.handle, 0);
+	return [super resignFirstResponder];
+}
+@end
+
+// Delegates are weakly referenced from their peers. Nothing
+// else holds a strong reference to our window delegate, so
+// keep a single global reference instead.
+static GioWindowDelegate *globalWindowDel;
+
+static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *handle) {
+	gio_onFrameCallback(dl);
+	return kCVReturnSuccess;
+}
+
+CFTypeRef gio_createDisplayLink(void) {
+	CVDisplayLinkRef dl;
+	CVDisplayLinkCreateWithActiveCGDisplays(&dl);
+	CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil);
+	return dl;
+}
+
+int gio_startDisplayLink(CFTypeRef dl) {
+	return CVDisplayLinkStart((CVDisplayLinkRef)dl);
+}
+
+int gio_stopDisplayLink(CFTypeRef dl) {
+	return CVDisplayLinkStop((CVDisplayLinkRef)dl);
+}
+
+void gio_releaseDisplayLink(CFTypeRef dl) {
+	CVDisplayLinkRelease((CVDisplayLinkRef)dl);
+}
+
+void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
+	CVDisplayLinkSetCurrentCGDisplay((CVDisplayLinkRef)dl, (CGDirectDisplayID)did);
+}
+
+void gio_hideCursor() {
+	@autoreleasepool {
+		[NSCursor hide];
+	}
+}
+
+void gio_showCursor() {
+	@autoreleasepool {
+		[NSCursor unhide];
+	}
+}
+
+// some cursors are not public, this tries to use a private cursor
+// and uses fallback when the use of private cursor fails.
+static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
+	if ([NSCursor respondsToSelector:cursorName]) {
+		id object = [NSCursor performSelector:cursorName];
+		if ([object isKindOfClass:[NSCursor class]]) {
+			[(NSCursor*)object set];
+			return;
+		}
+	}
+	[fallback set];
+}
+
+void gio_setCursor(NSUInteger curID) {
+	@autoreleasepool {
+		switch (curID) {
+			case 0: // pointer.CursorDefault
+				[NSCursor.arrowCursor set];
+				break;
+			// case 1: // pointer.CursorNone
+			case 2: // pointer.CursorText
+				[NSCursor.IBeamCursor set];
+				break;
+			case 3: // pointer.CursorVerticalText
+				[NSCursor.IBeamCursorForVerticalLayout set];
+				break;
+			case 4: // pointer.CursorPointer
+				[NSCursor.pointingHandCursor set];
+				break;
+			case 5: // pointer.CursorCrosshair
+				[NSCursor.crosshairCursor set];
+				break;
+			case 6: // pointer.CursorAllScroll
+				// For some reason, using _moveCursor fails on Monterey.
+				// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
+				[NSCursor.arrowCursor set];
+				break;
+			case 7: // pointer.CursorColResize
+				[NSCursor.resizeLeftRightCursor set];
+				break;
+			case 8: // pointer.CursorRowResize
+				[NSCursor.resizeUpDownCursor set];
+				break;
+			case 9: // pointer.CursorGrab
+				[NSCursor.openHandCursor set];
+				break;
+			case 10: // pointer.CursorGrabbing
+				[NSCursor.closedHandCursor set];
+				break;
+			case 11: // pointer.CursorNotAllowed
+				[NSCursor.operationNotAllowedCursor set];
+				break;
+			case 12: // pointer.CursorWait
+				trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
+				break;
+			case 13: // pointer.CursorProgress
+				trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
+				break;
+			case 14: // pointer.CursorNorthWestResize
+				trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
+				break;
+			case 15: // pointer.CursorNorthEastResize
+				trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
+				break;
+			case 16: // pointer.CursorSouthWestResize
+				trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
+				break;
+			case 17: // pointer.CursorSouthEastResize
+				trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
+				break;
+			case 18: // pointer.CursorNorthSouthResize
+				[NSCursor.resizeUpDownCursor set];
+				break;
+			case 19: // pointer.CursorEastWestResize
+				[NSCursor.resizeLeftRightCursor set];
+				break;
+			case 20: // pointer.CursorWestResize
+				[NSCursor.resizeLeftCursor set];
+				break;
+			case 21: // pointer.CursorEastResize
+				[NSCursor.resizeRightCursor set];
+				break;
+			case 22: // pointer.CursorNorthResize
+				[NSCursor.resizeUpCursor set];
+				break;
+			case 23: // pointer.CursorSouthResize
+				[NSCursor.resizeDownCursor set];
+				break;
+			case 24: // pointer.CursorNorthEastSouthWestResize
+				trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
+				break;
+			case 25: // pointer.CursorNorthWestSouthEastResize
+				trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
+				break;
+			default:
+				[NSCursor.arrowCursor set];
+				break;
+		}
+	}
+}
+
+CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
+	@autoreleasepool {
+		NSRect rect = NSMakeRect(0, 0, width, height);
+		NSUInteger styleMask = NSTitledWindowMask |
+			NSResizableWindowMask |
+			NSMiniaturizableWindowMask |
+			NSClosableWindowMask;
+
+		NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
+													   styleMask:styleMask
+														 backing:NSBackingStoreBuffered
+														   defer:NO];
+		if (minWidth > 0 || minHeight > 0) {
+			window.contentMinSize = NSMakeSize(minWidth, minHeight);
+		}
+		if (maxWidth > 0 || maxHeight > 0) {
+			window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
+		}
+		[window setAcceptsMouseMovedEvents:YES];
+		NSView *view = (__bridge NSView *)viewRef;
+		[window setContentView:view];
+		window.delegate = globalWindowDel;
+		return (__bridge_retained CFTypeRef)window;
+	}
+}
+
+CFTypeRef gio_createView(int presentWithTrans) {
+	@autoreleasepool {
+		NSRect frame = NSMakeRect(0, 0, 0, 0);
+		GioView* view = [[GioView alloc] initWithFrame:frame];
+		view.presentWithTrans = presentWithTrans ? YES : NO;
+		view.wantsLayer = YES;
+		view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
+
+		[[NSNotificationCenter defaultCenter] addObserver:view
+												 selector:@selector(applicationWillUnhide:)
+													 name:NSApplicationWillUnhideNotification
+												   object:nil];
+		[[NSNotificationCenter defaultCenter] addObserver:view
+												 selector:@selector(applicationDidHide:)
+													 name:NSApplicationDidHideNotification
+												   object:nil];
+		return CFBridgingRetain(view);
+	}
+}
+
+void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
+	@autoreleasepool {
+		GioView *v = (__bridge GioView *)viewRef;
+		v.handle = handle;
+	}
+}
+
+@implementation GioAppDelegate
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+	[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+	[NSApp activateIgnoringOtherApps:YES];
+	gio_onFinishLaunching();
+}
+@end
+
+void gio_main() {
+	@autoreleasepool {
+		[NSApplication sharedApplication];
+		GioAppDelegate *del = [[GioAppDelegate alloc] init];
+		[NSApp setDelegate:del];
+
+		NSMenuItem *mainMenu = [NSMenuItem new];
+
+		NSMenu *menu = [NSMenu new];
+		NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
+															  action:@selector(hide:)
+													   keyEquivalent:@"h"];
+		[menu addItem:hideMenuItem];
+		NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
+															  action:@selector(terminate:)
+													   keyEquivalent:@"q"];
+		[menu addItem:quitMenuItem];
+		[mainMenu setSubmenu:menu];
+		NSMenu *menuBar = [NSMenu new];
+		[menuBar addItem:mainMenu];
+		[NSApp setMainMenu:menuBar];
+
+		globalWindowDel = [[GioWindowDelegate alloc] init];
+
+		[NSApp run];
+	}
+}

+ 99 - 0
vendor/gioui.org/app/os_unix.go

@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build (linux && !android) || freebsd || openbsd
+// +build linux,!android freebsd openbsd
+
+package app
+
+import (
+	"errors"
+	"unsafe"
+
+	"gioui.org/io/pointer"
+)
+
+type X11ViewEvent struct {
+	// Display is a pointer to the X11 Display created by XOpenDisplay.
+	Display unsafe.Pointer
+	// Window is the X11 window ID as returned by XCreateWindow.
+	Window uintptr
+}
+
+func (X11ViewEvent) implementsViewEvent() {}
+func (X11ViewEvent) ImplementsEvent()     {}
+func (x X11ViewEvent) Valid() bool {
+	return x != (X11ViewEvent{})
+}
+
+type WaylandViewEvent struct {
+	// Display is the *wl_display returned by wl_display_connect.
+	Display unsafe.Pointer
+	// Surface is the *wl_surface returned by wl_compositor_create_surface.
+	Surface unsafe.Pointer
+}
+
+func (WaylandViewEvent) implementsViewEvent() {}
+func (WaylandViewEvent) ImplementsEvent()     {}
+func (w WaylandViewEvent) Valid() bool {
+	return w != (WaylandViewEvent{})
+}
+
+func osMain() {
+	select {}
+}
+
+type windowDriver func(*callbacks, []Option) error
+
+// Instead of creating files with build tags for each combination of wayland +/- x11
+// let each driver initialize these variables with their own version of createWindow.
+var wlDriver, x11Driver windowDriver
+
+func newWindow(window *callbacks, options []Option) {
+	var errFirst error
+	for _, d := range []windowDriver{wlDriver, x11Driver} {
+		if d == nil {
+			continue
+		}
+		err := d(window, options)
+		if err == nil {
+			return
+		}
+		if errFirst == nil {
+			errFirst = err
+		}
+	}
+	if errFirst == nil {
+		errFirst = errors.New("app: no window driver available")
+	}
+	window.ProcessEvent(DestroyEvent{Err: errFirst})
+}
+
+// xCursor contains mapping from pointer.Cursor to XCursor.
+var xCursor = [...]string{
+	pointer.CursorDefault:                  "left_ptr",
+	pointer.CursorNone:                     "",
+	pointer.CursorText:                     "xterm",
+	pointer.CursorVerticalText:             "vertical-text",
+	pointer.CursorPointer:                  "hand2",
+	pointer.CursorCrosshair:                "crosshair",
+	pointer.CursorAllScroll:                "fleur",
+	pointer.CursorColResize:                "sb_h_double_arrow",
+	pointer.CursorRowResize:                "sb_v_double_arrow",
+	pointer.CursorGrab:                     "hand1",
+	pointer.CursorGrabbing:                 "move",
+	pointer.CursorNotAllowed:               "crossed_circle",
+	pointer.CursorWait:                     "watch",
+	pointer.CursorProgress:                 "left_ptr_watch",
+	pointer.CursorNorthWestResize:          "top_left_corner",
+	pointer.CursorNorthEastResize:          "top_right_corner",
+	pointer.CursorSouthWestResize:          "bottom_left_corner",
+	pointer.CursorSouthEastResize:          "bottom_right_corner",
+	pointer.CursorNorthSouthResize:         "sb_v_double_arrow",
+	pointer.CursorEastWestResize:           "sb_h_double_arrow",
+	pointer.CursorWestResize:               "left_side",
+	pointer.CursorEastResize:               "right_side",
+	pointer.CursorNorthResize:              "top_side",
+	pointer.CursorSouthResize:              "bottom_side",
+	pointer.CursorNorthEastSouthWestResize: "fd_double_arrow",
+	pointer.CursorNorthWestSouthEastResize: "bd_double_arrow",
+}

+ 124 - 0
vendor/gioui.org/app/os_wayland.c

@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build ((linux && !android) || freebsd) && !nowayland
+// +build linux,!android freebsd
+// +build !nowayland
+
+#include <wayland-client.h>
+#include "wayland_xdg_shell.h"
+#include "wayland_xdg_decoration.h"
+#include "wayland_text_input.h"
+#include "_cgo_export.h"
+
+const struct wl_registry_listener gio_registry_listener = {
+	// Cast away const parameter.
+	.global = (void (*)(void *, struct wl_registry *, uint32_t,  const char *, uint32_t))gio_onRegistryGlobal,
+	.global_remove = gio_onRegistryGlobalRemove
+};
+
+const struct wl_surface_listener gio_surface_listener = {
+	.enter = gio_onSurfaceEnter,
+	.leave = gio_onSurfaceLeave,
+};
+
+const struct xdg_surface_listener gio_xdg_surface_listener = {
+	.configure = gio_onXdgSurfaceConfigure,
+};
+
+const struct xdg_toplevel_listener gio_xdg_toplevel_listener = {
+	.configure = gio_onToplevelConfigure,
+	.close = gio_onToplevelClose,
+};
+
+const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener = {
+	.configure = gio_onToplevelDecorationConfigure,
+};
+
+static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
+	xdg_wm_base_pong(wm, serial);
+}
+
+const struct xdg_wm_base_listener gio_xdg_wm_base_listener = {
+	.ping = xdg_wm_base_handle_ping,
+};
+
+const struct wl_callback_listener gio_callback_listener = {
+	.done = gio_onFrameDone,
+};
+
+const struct wl_output_listener gio_output_listener = {
+	// Cast away const parameter.
+	.geometry = (void (*)(void *, struct wl_output *, int32_t,  int32_t,  int32_t,  int32_t,  int32_t,  const char *, const char *, int32_t))gio_onOutputGeometry,
+	.mode = gio_onOutputMode,
+	.done = gio_onOutputDone,
+	.scale = gio_onOutputScale,
+};
+
+const struct wl_seat_listener gio_seat_listener = {
+	.capabilities = gio_onSeatCapabilities,
+	// Cast away const parameter.
+	.name = (void (*)(void *, struct wl_seat *, const char *))gio_onSeatName,
+};
+
+const struct wl_pointer_listener gio_pointer_listener = {
+	.enter = gio_onPointerEnter,
+	.leave = gio_onPointerLeave,
+	.motion = gio_onPointerMotion,
+	.button = gio_onPointerButton,
+	.axis = gio_onPointerAxis,
+	.frame = gio_onPointerFrame,
+	.axis_source = gio_onPointerAxisSource,
+	.axis_stop = gio_onPointerAxisStop,
+	.axis_discrete = gio_onPointerAxisDiscrete,
+};
+
+const struct wl_touch_listener gio_touch_listener = {
+	.down = gio_onTouchDown,
+	.up = gio_onTouchUp,
+	.motion = gio_onTouchMotion,
+	.frame = gio_onTouchFrame,
+	.cancel = gio_onTouchCancel,
+};
+
+const struct wl_keyboard_listener gio_keyboard_listener = {
+	.keymap = gio_onKeyboardKeymap,
+	.enter = gio_onKeyboardEnter,
+	.leave = gio_onKeyboardLeave,
+	.key = gio_onKeyboardKey,
+	.modifiers = gio_onKeyboardModifiers,
+	.repeat_info = gio_onKeyboardRepeatInfo
+};
+
+const struct zwp_text_input_v3_listener gio_zwp_text_input_v3_listener = {
+	.enter = gio_onTextInputEnter,
+	.leave = gio_onTextInputLeave,
+	// Cast away const parameter.
+	.preedit_string = (void (*)(void *, struct zwp_text_input_v3 *, const char *, int32_t,  int32_t))gio_onTextInputPreeditString,
+	.commit_string = (void (*)(void *, struct zwp_text_input_v3 *, const char *))gio_onTextInputCommitString,
+	.delete_surrounding_text = gio_onTextInputDeleteSurroundingText,
+	.done = gio_onTextInputDone
+};
+
+const struct wl_data_device_listener gio_data_device_listener = {
+	.data_offer = gio_onDataDeviceOffer,
+	.enter = gio_onDataDeviceEnter,
+	.leave = gio_onDataDeviceLeave,
+	.motion = gio_onDataDeviceMotion,
+	.drop = gio_onDataDeviceDrop,
+	.selection = gio_onDataDeviceSelection,
+};
+
+const struct wl_data_offer_listener gio_data_offer_listener = {
+	.offer = (void (*)(void *, struct wl_data_offer *, const char *))gio_onDataOfferOffer,
+	.source_actions = gio_onDataOfferSourceActions,
+	.action = gio_onDataOfferAction,
+};
+
+const struct wl_data_source_listener gio_data_source_listener = {
+	.target = (void (*)(void *, struct wl_data_source *, const char *))gio_onDataSourceTarget,
+	.send = (void (*)(void *, struct wl_data_source *, const char *, int32_t))gio_onDataSourceSend,
+	.cancelled = gio_onDataSourceCancelled,
+	.dnd_drop_performed = gio_onDataSourceDNDDropPerformed,
+	.dnd_finished = gio_onDataSourceDNDFinished,
+	.action = gio_onDataSourceAction,
+};

+ 1933 - 0
vendor/gioui.org/app/os_wayland.go

@@ -0,0 +1,1933 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build ((linux && !android) || freebsd) && !nowayland
+// +build linux,!android freebsd
+// +build !nowayland
+
+package app
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"image"
+	"io"
+	"math"
+	"os"
+	"os/exec"
+	"runtime"
+	"strconv"
+	"sync"
+	"time"
+	"unsafe"
+
+	syscall "golang.org/x/sys/unix"
+
+	"gioui.org/app/internal/xkb"
+	"gioui.org/f32"
+	"gioui.org/internal/fling"
+	"gioui.org/io/event"
+	"gioui.org/io/key"
+	"gioui.org/io/pointer"
+	"gioui.org/io/system"
+	"gioui.org/io/transfer"
+	"gioui.org/op"
+	"gioui.org/unit"
+)
+
+// Use wayland-scanner to generate glue code for the xdg-shell and xdg-decoration extensions.
+//go:generate wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.h
+//go:generate wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.c
+
+//go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml wayland_text_input.h
+//go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml wayland_text_input.c
+
+//go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.h
+//go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.c
+
+//go:generate sed -i "1s;^;//go:build ((linux \\&\\& !android) || freebsd) \\&\\& !nowayland\\n// +build linux,!android freebsd\\n// +build !nowayland\\n\\n;" wayland_xdg_shell.c
+//go:generate sed -i "1s;^;//go:build ((linux \\&\\& !android) || freebsd) \\&\\& !nowayland\\n// +build linux,!android freebsd\\n// +build !nowayland\\n\\n;" wayland_xdg_decoration.c
+//go:generate sed -i "1s;^;//go:build ((linux \\&\\& !android) || freebsd) \\&\\& !nowayland\\n// +build linux,!android freebsd\\n// +build !nowayland\\n\\n;" wayland_text_input.c
+
+/*
+#cgo linux pkg-config: wayland-client wayland-cursor
+#cgo freebsd openbsd LDFLAGS: -lwayland-client -lwayland-cursor
+#cgo freebsd CFLAGS: -I/usr/local/include
+#cgo freebsd LDFLAGS: -L/usr/local/lib
+
+#include <stdlib.h>
+#include <wayland-client.h>
+#include <wayland-cursor.h>
+#include "wayland_text_input.h"
+#include "wayland_xdg_shell.h"
+#include "wayland_xdg_decoration.h"
+
+extern const struct wl_registry_listener gio_registry_listener;
+extern const struct wl_surface_listener gio_surface_listener;
+extern const struct xdg_surface_listener gio_xdg_surface_listener;
+extern const struct xdg_toplevel_listener gio_xdg_toplevel_listener;
+extern const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener;
+extern const struct xdg_wm_base_listener gio_xdg_wm_base_listener;
+extern const struct wl_callback_listener gio_callback_listener;
+extern const struct wl_output_listener gio_output_listener;
+extern const struct wl_seat_listener gio_seat_listener;
+extern const struct wl_pointer_listener gio_pointer_listener;
+extern const struct wl_touch_listener gio_touch_listener;
+extern const struct wl_keyboard_listener gio_keyboard_listener;
+extern const struct zwp_text_input_v3_listener gio_zwp_text_input_v3_listener;
+extern const struct wl_data_device_listener gio_data_device_listener;
+extern const struct wl_data_offer_listener gio_data_offer_listener;
+extern const struct wl_data_source_listener gio_data_source_listener;
+*/
+import "C"
+
+type wlDisplay struct {
+	disp              *C.struct_wl_display
+	reg               *C.struct_wl_registry
+	compositor        *C.struct_wl_compositor
+	wm                *C.struct_xdg_wm_base
+	imm               *C.struct_zwp_text_input_manager_v3
+	shm               *C.struct_wl_shm
+	dataDeviceManager *C.struct_wl_data_device_manager
+	decor             *C.struct_zxdg_decoration_manager_v1
+	seat              *wlSeat
+	xkb               *xkb.Context
+	outputMap         map[C.uint32_t]*C.struct_wl_output
+	outputConfig      map[*C.struct_wl_output]*wlOutput
+
+	// Notification pipe fds.
+	notify struct {
+		read, write int
+	}
+
+	repeat        repeatState
+	poller        poller
+	readClipClose chan struct{}
+}
+
+type wlSeat struct {
+	disp     *wlDisplay
+	seat     *C.struct_wl_seat
+	name     C.uint32_t
+	pointer  *C.struct_wl_pointer
+	touch    *C.struct_wl_touch
+	keyboard *C.struct_wl_keyboard
+	im       *C.struct_zwp_text_input_v3
+
+	// The most recent input serial.
+	serial C.uint32_t
+
+	pointerFocus  *window
+	keyboardFocus *window
+	touchFoci     map[C.int32_t]*window
+
+	// Clipboard support.
+	dataDev *C.struct_wl_data_device
+	// offers is a map from active wl_data_offers to
+	// the list of mime types they support.
+	offers map[*C.struct_wl_data_offer][]string
+	// clipboard is the wl_data_offer for the clipboard.
+	clipboard *C.struct_wl_data_offer
+	// mimeType is the chosen mime type of clipboard.
+	mimeType string
+	// source represents the clipboard content of the most recent
+	// clipboard write, if any.
+	source *C.struct_wl_data_source
+	// content is the data belonging to source.
+	content []byte
+}
+
+type repeatState struct {
+	rate  int
+	delay time.Duration
+
+	key   uint32
+	win   *window
+	stopC chan struct{}
+
+	start time.Duration
+	last  time.Duration
+	mu    sync.Mutex
+	now   time.Duration
+}
+
+type window struct {
+	w          *callbacks
+	disp       *wlDisplay
+	seat       *wlSeat
+	surf       *C.struct_wl_surface
+	wmSurf     *C.struct_xdg_surface
+	topLvl     *C.struct_xdg_toplevel
+	decor      *C.struct_zxdg_toplevel_decoration_v1
+	ppdp, ppsp float32
+	scroll     struct {
+		time  time.Duration
+		steps image.Point
+		dist  f32.Point
+	}
+	pointerBtns pointer.Buttons
+	lastPos     f32.Point
+	lastTouch   f32.Point
+
+	cursor struct {
+		theme  *C.struct_wl_cursor_theme
+		cursor *C.struct_wl_cursor
+		// system is the active cursor for system gestures
+		// such as border resizes and window moves. It
+		// is nil if the pointer is not in a system gesture
+		// area.
+		system  *C.struct_wl_cursor
+		surf    *C.struct_wl_surface
+		cursors struct {
+			pointer         *C.struct_wl_cursor
+			resizeNorth     *C.struct_wl_cursor
+			resizeSouth     *C.struct_wl_cursor
+			resizeWest      *C.struct_wl_cursor
+			resizeEast      *C.struct_wl_cursor
+			resizeNorthWest *C.struct_wl_cursor
+			resizeNorthEast *C.struct_wl_cursor
+			resizeSouthWest *C.struct_wl_cursor
+			resizeSouthEast *C.struct_wl_cursor
+		}
+	}
+
+	fling struct {
+		yExtrapolation fling.Extrapolation
+		xExtrapolation fling.Extrapolation
+		anim           fling.Animation
+		start          bool
+		dir            f32.Point
+	}
+
+	configured        bool
+	lastFrameCallback *C.struct_wl_callback
+
+	animating bool
+	// The most recent configure serial waiting to be ack'ed.
+	serial C.uint32_t
+	scale  int
+	// size is the unscaled window size (unlike config.Size which is scaled).
+	size         image.Point
+	config       Config
+	wsize        image.Point // window config size before going fullscreen or maximized
+	inCompositor bool        // window is moving or being resized
+
+	clipReads chan transfer.DataEvent
+
+	wakeups chan struct{}
+
+	closing bool
+}
+
+type poller struct {
+	pollfds [2]syscall.PollFd
+	// buf is scratch space for draining the notification pipe.
+	buf [100]byte
+}
+
+type wlOutput struct {
+	width      int
+	height     int
+	physWidth  int
+	physHeight int
+	transform  C.int32_t
+	scale      int
+	windows    []*window
+}
+
+// callbackMap maps Wayland native handles to corresponding Go
+// references. It is necessary because the Wayland client API
+// forces the use of callbacks and storing pointers to Go values
+// in C is forbidden.
+var callbackMap sync.Map
+
+// clipboardMimeTypes is a list of supported clipboard mime types, in
+// order of preference.
+var clipboardMimeTypes = []string{"text/plain;charset=utf8", "UTF8_STRING", "text/plain", "TEXT", "STRING"}
+
+var (
+	newWaylandEGLContext    func(w *window) (context, error)
+	newWaylandVulkanContext func(w *window) (context, error)
+)
+
+func init() {
+	wlDriver = newWLWindow
+}
+
+func newWLWindow(callbacks *callbacks, options []Option) error {
+	d, err := newWLDisplay()
+	if err != nil {
+		return err
+	}
+	w, err := d.createNativeWindow(options)
+	if err != nil {
+		d.destroy()
+		return err
+	}
+	w.w = callbacks
+	w.w.SetDriver(w)
+
+	// Finish and commit setup from createNativeWindow.
+	w.Configure(options)
+	w.draw(true)
+	C.wl_surface_commit(w.surf)
+
+	w.ProcessEvent(WaylandViewEvent{
+		Display: unsafe.Pointer(w.display()),
+		Surface: unsafe.Pointer(w.surf),
+	})
+	return nil
+}
+
+func (d *wlDisplay) writeClipboard(content []byte) error {
+	s := d.seat
+	if s == nil {
+		return nil
+	}
+	// Clear old offer.
+	if s.source != nil {
+		C.wl_data_source_destroy(s.source)
+		s.source = nil
+		s.content = nil
+	}
+	if d.dataDeviceManager == nil || s.dataDev == nil {
+		return nil
+	}
+	s.content = content
+	s.source = C.wl_data_device_manager_create_data_source(d.dataDeviceManager)
+	C.wl_data_source_add_listener(s.source, &C.gio_data_source_listener, unsafe.Pointer(s.seat))
+	for _, mime := range clipboardMimeTypes {
+		C.wl_data_source_offer(s.source, C.CString(mime))
+	}
+	C.wl_data_device_set_selection(s.dataDev, s.source, s.serial)
+	return nil
+}
+
+func (d *wlDisplay) readClipboard() (io.ReadCloser, error) {
+	s := d.seat
+	if s == nil {
+		return nil, nil
+	}
+	if s.clipboard == nil {
+		return nil, nil
+	}
+	r, w, err := os.Pipe()
+	if err != nil {
+		return nil, err
+	}
+	// wl_data_offer_receive performs and implicit dup(2) of the write end
+	// of the pipe. Close our version.
+	defer w.Close()
+	cmimeType := C.CString(s.mimeType)
+	defer C.free(unsafe.Pointer(cmimeType))
+	C.wl_data_offer_receive(s.clipboard, cmimeType, C.int(w.Fd()))
+	return r, nil
+}
+
+func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
+	if d.compositor == nil {
+		return nil, errors.New("wayland: no compositor available")
+	}
+	if d.wm == nil {
+		return nil, errors.New("wayland: no xdg_wm_base available")
+	}
+	if d.shm == nil {
+		return nil, errors.New("wayland: no wl_shm available")
+	}
+	if len(d.outputMap) == 0 {
+		return nil, errors.New("wayland: no outputs available")
+	}
+	var scale int
+	for _, conf := range d.outputConfig {
+		if s := conf.scale; s > scale {
+			scale = s
+		}
+	}
+	ppdp := detectUIScale()
+
+	w := &window{
+		disp:      d,
+		scale:     scale,
+		ppdp:      ppdp,
+		ppsp:      ppdp,
+		wakeups:   make(chan struct{}, 1),
+		clipReads: make(chan transfer.DataEvent, 1),
+	}
+	w.surf = C.wl_compositor_create_surface(d.compositor)
+	if w.surf == nil {
+		w.destroy()
+		return nil, errors.New("wayland: wl_compositor_create_surface failed")
+	}
+	C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
+	callbackStore(unsafe.Pointer(w.surf), w)
+	w.wmSurf = C.xdg_wm_base_get_xdg_surface(d.wm, w.surf)
+	if w.wmSurf == nil {
+		w.destroy()
+		return nil, errors.New("wayland: xdg_wm_base_get_xdg_surface failed")
+	}
+	w.topLvl = C.xdg_surface_get_toplevel(w.wmSurf)
+	if w.topLvl == nil {
+		w.destroy()
+		return nil, errors.New("wayland: xdg_surface_get_toplevel failed")
+	}
+
+	id := C.CString(ID)
+	defer C.free(unsafe.Pointer(id))
+	C.xdg_toplevel_set_app_id(w.topLvl, id)
+
+	cursorTheme := C.CString(os.Getenv("XCURSOR_THEME"))
+	defer C.free(unsafe.Pointer(cursorTheme))
+	cursorSize := 32
+	if envSize, ok := os.LookupEnv("XCURSOR_SIZE"); ok && envSize != "" {
+		size, err := strconv.Atoi(envSize)
+		if err == nil {
+			cursorSize = size
+		}
+	}
+
+	w.cursor.theme = C.wl_cursor_theme_load(cursorTheme, C.int(cursorSize*w.scale), d.shm)
+	if w.cursor.theme == nil {
+		w.destroy()
+		return nil, errors.New("wayland: wl_cursor_theme_load failed")
+	}
+	w.loadCursors()
+	w.cursor.cursor = w.cursor.cursors.pointer
+	if w.cursor.cursor == nil {
+		w.destroy()
+		return nil, errors.New("wayland: wl_cursor_theme_get_cursor failed")
+	}
+	w.cursor.surf = C.wl_compositor_create_surface(d.compositor)
+	if w.cursor.surf == nil {
+		w.destroy()
+		return nil, errors.New("wayland: wl_compositor_create_surface failed")
+	}
+	C.wl_surface_set_buffer_scale(w.cursor.surf, C.int32_t(w.scale))
+	C.xdg_wm_base_add_listener(d.wm, &C.gio_xdg_wm_base_listener, unsafe.Pointer(w.surf))
+	C.wl_surface_add_listener(w.surf, &C.gio_surface_listener, unsafe.Pointer(w.surf))
+	C.xdg_surface_add_listener(w.wmSurf, &C.gio_xdg_surface_listener, unsafe.Pointer(w.surf))
+	C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf))
+
+	if d.decor != nil {
+		w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(d.decor, w.topLvl)
+		C.zxdg_toplevel_decoration_v1_add_listener(w.decor, &C.gio_zxdg_toplevel_decoration_v1_listener, unsafe.Pointer(w.surf))
+	}
+	w.updateOpaqueRegion()
+	return w, nil
+}
+
+func (w *window) loadCursors() {
+	w.cursor.cursors.pointer = w.loadCursor(pointer.CursorDefault)
+	w.cursor.cursors.resizeNorth = w.loadCursor(pointer.CursorNorthResize)
+	w.cursor.cursors.resizeSouth = w.loadCursor(pointer.CursorSouthResize)
+	w.cursor.cursors.resizeWest = w.loadCursor(pointer.CursorWestResize)
+	w.cursor.cursors.resizeEast = w.loadCursor(pointer.CursorEastResize)
+	w.cursor.cursors.resizeSouthWest = w.loadCursor(pointer.CursorSouthWestResize)
+	w.cursor.cursors.resizeSouthEast = w.loadCursor(pointer.CursorSouthEastResize)
+	w.cursor.cursors.resizeNorthWest = w.loadCursor(pointer.CursorNorthWestResize)
+	w.cursor.cursors.resizeNorthEast = w.loadCursor(pointer.CursorNorthEastResize)
+}
+
+func (w *window) loadCursor(name pointer.Cursor) *C.struct_wl_cursor {
+	if name == pointer.CursorNone {
+		return nil
+	}
+	xcursor := xCursor[name]
+	cname := C.CString(xcursor)
+	defer C.free(unsafe.Pointer(cname))
+	c := C.wl_cursor_theme_get_cursor(w.cursor.theme, cname)
+	if c == nil {
+		// Fall back to default cursor.
+		c = w.cursor.cursors.pointer
+	}
+	return c
+}
+
+func callbackDelete(k unsafe.Pointer) {
+	callbackMap.Delete(k)
+}
+
+func callbackStore(k unsafe.Pointer, v interface{}) {
+	callbackMap.Store(k, v)
+}
+
+func callbackLoad(k unsafe.Pointer) interface{} {
+	v, exists := callbackMap.Load(k)
+	if !exists {
+		panic("missing callback entry")
+	}
+	return v
+}
+
+//export gio_onSeatCapabilities
+func gio_onSeatCapabilities(data unsafe.Pointer, seat *C.struct_wl_seat, caps C.uint32_t) {
+	s := callbackLoad(data).(*wlSeat)
+	s.updateCaps(caps)
+}
+
+// flushOffers remove all wl_data_offers that isn't the clipboard
+// content.
+func (s *wlSeat) flushOffers() {
+	for o := range s.offers {
+		if o == s.clipboard {
+			continue
+		}
+		// We're only interested in clipboard offers.
+		delete(s.offers, o)
+		callbackDelete(unsafe.Pointer(o))
+		C.wl_data_offer_destroy(o)
+	}
+}
+
+func (s *wlSeat) destroy() {
+	if s.source != nil {
+		C.wl_data_source_destroy(s.source)
+		s.source = nil
+	}
+	if s.im != nil {
+		C.zwp_text_input_v3_destroy(s.im)
+		s.im = nil
+	}
+	if s.pointer != nil {
+		C.wl_pointer_release(s.pointer)
+	}
+	if s.touch != nil {
+		C.wl_touch_release(s.touch)
+	}
+	if s.keyboard != nil {
+		C.wl_keyboard_release(s.keyboard)
+	}
+	s.clipboard = nil
+	s.flushOffers()
+	if s.dataDev != nil {
+		C.wl_data_device_release(s.dataDev)
+	}
+	if s.seat != nil {
+		callbackDelete(unsafe.Pointer(s.seat))
+		C.wl_seat_release(s.seat)
+	}
+}
+
+func (s *wlSeat) updateCaps(caps C.uint32_t) {
+	if s.im == nil && s.disp.imm != nil {
+		s.im = C.zwp_text_input_manager_v3_get_text_input(s.disp.imm, s.seat)
+		C.zwp_text_input_v3_add_listener(s.im, &C.gio_zwp_text_input_v3_listener, unsafe.Pointer(s.seat))
+	}
+	switch {
+	case s.pointer == nil && caps&C.WL_SEAT_CAPABILITY_POINTER != 0:
+		s.pointer = C.wl_seat_get_pointer(s.seat)
+		C.wl_pointer_add_listener(s.pointer, &C.gio_pointer_listener, unsafe.Pointer(s.seat))
+	case s.pointer != nil && caps&C.WL_SEAT_CAPABILITY_POINTER == 0:
+		C.wl_pointer_release(s.pointer)
+		s.pointer = nil
+	}
+	switch {
+	case s.touch == nil && caps&C.WL_SEAT_CAPABILITY_TOUCH != 0:
+		s.touch = C.wl_seat_get_touch(s.seat)
+		C.wl_touch_add_listener(s.touch, &C.gio_touch_listener, unsafe.Pointer(s.seat))
+	case s.touch != nil && caps&C.WL_SEAT_CAPABILITY_TOUCH == 0:
+		C.wl_touch_release(s.touch)
+		s.touch = nil
+	}
+	switch {
+	case s.keyboard == nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD != 0:
+		s.keyboard = C.wl_seat_get_keyboard(s.seat)
+		C.wl_keyboard_add_listener(s.keyboard, &C.gio_keyboard_listener, unsafe.Pointer(s.seat))
+	case s.keyboard != nil && caps&C.WL_SEAT_CAPABILITY_KEYBOARD == 0:
+		C.wl_keyboard_release(s.keyboard)
+		s.keyboard = nil
+	}
+}
+
+//export gio_onSeatName
+func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) {
+}
+
+//export gio_onXdgSurfaceConfigure
+func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
+	w := callbackLoad(data).(*window)
+	w.serial = serial
+	C.xdg_surface_ack_configure(wmSurf, serial)
+	w.configured = true
+	w.draw(true)
+}
+
+//export gio_onToplevelClose
+func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
+	w := callbackLoad(data).(*window)
+	w.closing = true
+}
+
+//export gio_onToplevelConfigure
+func gio_onToplevelConfigure(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel, width, height C.int32_t, states *C.struct_wl_array) {
+	w := callbackLoad(data).(*window)
+	if width != 0 && height != 0 {
+		w.size = image.Pt(int(width), int(height))
+		w.updateOpaqueRegion()
+	}
+}
+
+//export gio_onToplevelDecorationConfigure
+func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_toplevel_decoration_v1, mode C.uint32_t) {
+	w := callbackLoad(data).(*window)
+	decorated := w.config.Decorated
+	switch mode {
+	case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
+		w.config.Decorated = false
+	case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
+		w.config.Decorated = true
+	}
+	if decorated != w.config.Decorated {
+		w.setWindowConstraints()
+		if w.config.Decorated {
+			w.size.Y -= int(w.config.decoHeight)
+		} else {
+			w.size.Y += int(w.config.decoHeight)
+		}
+		w.ProcessEvent(ConfigEvent{Config: w.config})
+		w.draw(true)
+	}
+}
+
+//export gio_onOutputMode
+func gio_onOutputMode(data unsafe.Pointer, output *C.struct_wl_output, flags C.uint32_t, width, height, refresh C.int32_t) {
+	if flags&C.WL_OUTPUT_MODE_CURRENT == 0 {
+		return
+	}
+	d := callbackLoad(data).(*wlDisplay)
+	c := d.outputConfig[output]
+	c.width = int(width)
+	c.height = int(height)
+}
+
+//export gio_onOutputGeometry
+func gio_onOutputGeometry(data unsafe.Pointer, output *C.struct_wl_output, x, y, physWidth, physHeight, subpixel C.int32_t, make, model *C.char, transform C.int32_t) {
+	d := callbackLoad(data).(*wlDisplay)
+	c := d.outputConfig[output]
+	c.transform = transform
+	c.physWidth = int(physWidth)
+	c.physHeight = int(physHeight)
+}
+
+//export gio_onOutputScale
+func gio_onOutputScale(data unsafe.Pointer, output *C.struct_wl_output, scale C.int32_t) {
+	d := callbackLoad(data).(*wlDisplay)
+	c := d.outputConfig[output]
+	c.scale = int(scale)
+}
+
+//export gio_onOutputDone
+func gio_onOutputDone(data unsafe.Pointer, output *C.struct_wl_output) {
+	d := callbackLoad(data).(*wlDisplay)
+	conf := d.outputConfig[output]
+	for _, w := range conf.windows {
+		w.updateOutputs()
+	}
+}
+
+//export gio_onSurfaceEnter
+func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *C.struct_wl_output) {
+	w := callbackLoad(data).(*window)
+	conf := w.disp.outputConfig[output]
+	var found bool
+	for _, w2 := range conf.windows {
+		if w2 == w {
+			found = true
+			break
+		}
+	}
+	if !found {
+		conf.windows = append(conf.windows, w)
+	}
+	w.updateOutputs()
+	if w.config.Mode == Minimized {
+		// Minimized window got brought back up: it is no longer so.
+		w.config.Mode = Windowed
+		w.ProcessEvent(ConfigEvent{Config: w.config})
+	}
+}
+
+//export gio_onSurfaceLeave
+func gio_onSurfaceLeave(data unsafe.Pointer, surf *C.struct_wl_surface, output *C.struct_wl_output) {
+	w := callbackLoad(data).(*window)
+	conf := w.disp.outputConfig[output]
+	for i, w2 := range conf.windows {
+		if w2 == w {
+			conf.windows = append(conf.windows[:i], conf.windows[i+1:]...)
+			break
+		}
+	}
+	w.updateOutputs()
+}
+
+//export gio_onRegistryGlobal
+func gio_onRegistryGlobal(data unsafe.Pointer, reg *C.struct_wl_registry, name C.uint32_t, cintf *C.char, version C.uint32_t) {
+	d := callbackLoad(data).(*wlDisplay)
+	switch C.GoString(cintf) {
+	case "wl_compositor":
+		d.compositor = (*C.struct_wl_compositor)(C.wl_registry_bind(reg, name, &C.wl_compositor_interface, 3))
+	case "wl_output":
+		output := (*C.struct_wl_output)(C.wl_registry_bind(reg, name, &C.wl_output_interface, 2))
+		C.wl_output_add_listener(output, &C.gio_output_listener, unsafe.Pointer(d.disp))
+		d.outputMap[name] = output
+		d.outputConfig[output] = new(wlOutput)
+	case "wl_seat":
+		if d.seat != nil {
+			break
+		}
+		s := (*C.struct_wl_seat)(C.wl_registry_bind(reg, name, &C.wl_seat_interface, 5))
+		if s == nil {
+			// No support for v5 protocol.
+			break
+		}
+		d.seat = &wlSeat{
+			disp:      d,
+			name:      name,
+			seat:      s,
+			offers:    make(map[*C.struct_wl_data_offer][]string),
+			touchFoci: make(map[C.int32_t]*window),
+		}
+		callbackStore(unsafe.Pointer(s), d.seat)
+		C.wl_seat_add_listener(s, &C.gio_seat_listener, unsafe.Pointer(s))
+		d.bindDataDevice()
+	case "wl_shm":
+		d.shm = (*C.struct_wl_shm)(C.wl_registry_bind(reg, name, &C.wl_shm_interface, 1))
+	case "xdg_wm_base":
+		d.wm = (*C.struct_xdg_wm_base)(C.wl_registry_bind(reg, name, &C.xdg_wm_base_interface, 1))
+	case "zxdg_decoration_manager_v1":
+		d.decor = (*C.struct_zxdg_decoration_manager_v1)(C.wl_registry_bind(reg, name, &C.zxdg_decoration_manager_v1_interface, 1))
+		// TODO: Implement and test text-input support.
+		/*case "zwp_text_input_manager_v3":
+		d.imm = (*C.struct_zwp_text_input_manager_v3)(C.wl_registry_bind(reg, name, &C.zwp_text_input_manager_v3_interface, 1))*/
+	case "wl_data_device_manager":
+		d.dataDeviceManager = (*C.struct_wl_data_device_manager)(C.wl_registry_bind(reg, name, &C.wl_data_device_manager_interface, 3))
+		d.bindDataDevice()
+	}
+}
+
+//export gio_onDataOfferOffer
+func gio_onDataOfferOffer(data unsafe.Pointer, offer *C.struct_wl_data_offer, mime *C.char) {
+	s := callbackLoad(data).(*wlSeat)
+	s.offers[offer] = append(s.offers[offer], C.GoString(mime))
+}
+
+//export gio_onDataOfferSourceActions
+func gio_onDataOfferSourceActions(data unsafe.Pointer, offer *C.struct_wl_data_offer, acts C.uint32_t) {
+}
+
+//export gio_onDataOfferAction
+func gio_onDataOfferAction(data unsafe.Pointer, offer *C.struct_wl_data_offer, act C.uint32_t) {
+}
+
+//export gio_onDataDeviceOffer
+func gio_onDataDeviceOffer(data unsafe.Pointer, dataDev *C.struct_wl_data_device, id *C.struct_wl_data_offer) {
+	s := callbackLoad(data).(*wlSeat)
+	callbackStore(unsafe.Pointer(id), s)
+	C.wl_data_offer_add_listener(id, &C.gio_data_offer_listener, unsafe.Pointer(id))
+	s.offers[id] = nil
+}
+
+//export gio_onDataDeviceEnter
+func gio_onDataDeviceEnter(data unsafe.Pointer, dataDev *C.struct_wl_data_device, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t, id *C.struct_wl_data_offer) {
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+	s.flushOffers()
+}
+
+//export gio_onDataDeviceLeave
+func gio_onDataDeviceLeave(data unsafe.Pointer, dataDev *C.struct_wl_data_device) {
+}
+
+//export gio_onDataDeviceMotion
+func gio_onDataDeviceMotion(data unsafe.Pointer, dataDev *C.struct_wl_data_device, t C.uint32_t, x, y C.wl_fixed_t) {
+}
+
+//export gio_onDataDeviceDrop
+func gio_onDataDeviceDrop(data unsafe.Pointer, dataDev *C.struct_wl_data_device) {
+}
+
+//export gio_onDataDeviceSelection
+func gio_onDataDeviceSelection(data unsafe.Pointer, dataDev *C.struct_wl_data_device, id *C.struct_wl_data_offer) {
+	s := callbackLoad(data).(*wlSeat)
+	defer s.flushOffers()
+	s.clipboard = nil
+loop:
+	for _, want := range clipboardMimeTypes {
+		for _, got := range s.offers[id] {
+			if want != got {
+				continue
+			}
+			s.clipboard = id
+			s.mimeType = got
+			break loop
+		}
+	}
+}
+
+//export gio_onRegistryGlobalRemove
+func gio_onRegistryGlobalRemove(data unsafe.Pointer, reg *C.struct_wl_registry, name C.uint32_t) {
+	d := callbackLoad(data).(*wlDisplay)
+	if s := d.seat; s != nil && name == s.name {
+		s.destroy()
+		d.seat = nil
+	}
+	if output, exists := d.outputMap[name]; exists {
+		C.wl_output_destroy(output)
+		delete(d.outputMap, name)
+		delete(d.outputConfig, output)
+	}
+}
+
+//export gio_onTouchDown
+func gio_onTouchDown(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.uint32_t, surf *C.struct_wl_surface, id C.int32_t, x, y C.wl_fixed_t) {
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+	w := callbackLoad(unsafe.Pointer(surf)).(*window)
+	s.touchFoci[id] = w
+	w.lastTouch = f32.Point{
+		X: fromFixed(x) * float32(w.scale),
+		Y: fromFixed(y) * float32(w.scale),
+	}
+	w.ProcessEvent(pointer.Event{
+		Kind:      pointer.Press,
+		Source:    pointer.Touch,
+		Position:  w.lastTouch,
+		PointerID: pointer.ID(id),
+		Time:      time.Duration(t) * time.Millisecond,
+		Modifiers: w.disp.xkb.Modifiers(),
+	})
+}
+
+//export gio_onTouchUp
+func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.uint32_t, id C.int32_t) {
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+	w := s.touchFoci[id]
+	delete(s.touchFoci, id)
+	w.ProcessEvent(pointer.Event{
+		Kind:      pointer.Release,
+		Source:    pointer.Touch,
+		Position:  w.lastTouch,
+		PointerID: pointer.ID(id),
+		Time:      time.Duration(t) * time.Millisecond,
+		Modifiers: w.disp.xkb.Modifiers(),
+	})
+}
+
+//export gio_onTouchMotion
+func gio_onTouchMotion(data unsafe.Pointer, touch *C.struct_wl_touch, t C.uint32_t, id C.int32_t, x, y C.wl_fixed_t) {
+	s := callbackLoad(data).(*wlSeat)
+	w := s.touchFoci[id]
+	w.lastTouch = f32.Point{
+		X: fromFixed(x) * float32(w.scale),
+		Y: fromFixed(y) * float32(w.scale),
+	}
+	w.ProcessEvent(pointer.Event{
+		Kind:      pointer.Move,
+		Position:  w.lastTouch,
+		Source:    pointer.Touch,
+		PointerID: pointer.ID(id),
+		Time:      time.Duration(t) * time.Millisecond,
+		Modifiers: w.disp.xkb.Modifiers(),
+	})
+}
+
+//export gio_onTouchFrame
+func gio_onTouchFrame(data unsafe.Pointer, touch *C.struct_wl_touch) {
+}
+
+//export gio_onTouchCancel
+func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
+	s := callbackLoad(data).(*wlSeat)
+	for id, w := range s.touchFoci {
+		delete(s.touchFoci, id)
+		w.ProcessEvent(pointer.Event{
+			Kind:   pointer.Cancel,
+			Source: pointer.Touch,
+		})
+	}
+}
+
+//export gio_onPointerEnter
+func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t) {
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+	w := callbackLoad(unsafe.Pointer(surf)).(*window)
+	w.seat = s
+	s.pointerFocus = w
+	w.setCursor(pointer, serial)
+	w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
+}
+
+//export gio_onPointerLeave
+func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) {
+	w := callbackLoad(unsafe.Pointer(surf)).(*window)
+	w.seat = nil
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+	if w.inCompositor {
+		w.inCompositor = false
+		w.ProcessEvent(pointer.Event{Kind: pointer.Cancel})
+	}
+}
+
+//export gio_onPointerMotion
+func gio_onPointerMotion(data unsafe.Pointer, p *C.struct_wl_pointer, t C.uint32_t, x, y C.wl_fixed_t) {
+	s := callbackLoad(data).(*wlSeat)
+	w := s.pointerFocus
+	w.resetFling()
+	w.onPointerMotion(x, y, t)
+}
+
+//export gio_onPointerButton
+func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t, wbtn, state C.uint32_t) {
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+	w := s.pointerFocus
+	// From linux-event-codes.h.
+	const (
+		BTN_LEFT   = 0x110
+		BTN_RIGHT  = 0x111
+		BTN_MIDDLE = 0x112
+	)
+	var btn pointer.Buttons
+	switch wbtn {
+	case BTN_LEFT:
+		btn = pointer.ButtonPrimary
+	case BTN_RIGHT:
+		btn = pointer.ButtonSecondary
+	case BTN_MIDDLE:
+		btn = pointer.ButtonTertiary
+	default:
+		return
+	}
+	if state == 1 && btn == pointer.ButtonPrimary {
+		if _, edge := w.systemGesture(); edge != 0 {
+			w.resize(serial, edge)
+			return
+		}
+		act, ok := w.w.ActionAt(w.lastPos)
+		if ok && w.config.Mode == Windowed {
+			switch act {
+			case system.ActionMove:
+				w.move(serial)
+				return
+			}
+		}
+	}
+	var kind pointer.Kind
+	switch state {
+	case 0:
+		w.pointerBtns &^= btn
+		kind = pointer.Release
+		// Move or resize gestures no longer applies.
+		w.inCompositor = false
+	case 1:
+		w.pointerBtns |= btn
+		kind = pointer.Press
+	}
+	w.flushScroll()
+	w.resetFling()
+	w.ProcessEvent(pointer.Event{
+		Kind:      kind,
+		Source:    pointer.Mouse,
+		Buttons:   w.pointerBtns,
+		Position:  w.lastPos,
+		Time:      time.Duration(t) * time.Millisecond,
+		Modifiers: w.disp.xkb.Modifiers(),
+	})
+}
+
+//export gio_onPointerAxis
+func gio_onPointerAxis(data unsafe.Pointer, p *C.struct_wl_pointer, t, axis C.uint32_t, value C.wl_fixed_t) {
+	s := callbackLoad(data).(*wlSeat)
+	w := s.pointerFocus
+	v := fromFixed(value)
+	w.resetFling()
+	if w.scroll.dist == (f32.Point{}) {
+		w.scroll.time = time.Duration(t) * time.Millisecond
+	}
+	switch axis {
+	case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL:
+		w.scroll.dist.X += v
+	case C.WL_POINTER_AXIS_VERTICAL_SCROLL:
+		// horizontal scroll if shift + mousewheel(up/down) pressed.
+		if w.disp.xkb.Modifiers() == key.ModShift {
+			w.scroll.dist.X += v
+		} else {
+			w.scroll.dist.Y += v
+		}
+	}
+}
+
+//export gio_onPointerFrame
+func gio_onPointerFrame(data unsafe.Pointer, p *C.struct_wl_pointer) {
+	s := callbackLoad(data).(*wlSeat)
+	w := s.pointerFocus
+	w.flushScroll()
+	w.flushFling()
+}
+
+func (w *window) flushFling() {
+	if !w.fling.start {
+		return
+	}
+	w.fling.start = false
+	estx, esty := w.fling.xExtrapolation.Estimate(), w.fling.yExtrapolation.Estimate()
+	w.fling.xExtrapolation = fling.Extrapolation{}
+	w.fling.yExtrapolation = fling.Extrapolation{}
+	vel := float32(math.Sqrt(float64(estx.Velocity*estx.Velocity + esty.Velocity*esty.Velocity)))
+	_, c := w.getConfig()
+	if !w.fling.anim.Start(c, time.Now(), vel) {
+		return
+	}
+	invDist := 1 / vel
+	w.fling.dir.X = estx.Velocity * invDist
+	w.fling.dir.Y = esty.Velocity * invDist
+}
+
+//export gio_onPointerAxisSource
+func gio_onPointerAxisSource(data unsafe.Pointer, pointer *C.struct_wl_pointer, source C.uint32_t) {
+}
+
+//export gio_onPointerAxisStop
+func gio_onPointerAxisStop(data unsafe.Pointer, p *C.struct_wl_pointer, t, axis C.uint32_t) {
+	s := callbackLoad(data).(*wlSeat)
+	w := s.pointerFocus
+	w.fling.start = true
+}
+
+//export gio_onPointerAxisDiscrete
+func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis C.uint32_t, discrete C.int32_t) {
+	s := callbackLoad(data).(*wlSeat)
+	w := s.pointerFocus
+	w.resetFling()
+	switch axis {
+	case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL:
+		w.scroll.steps.X += int(discrete)
+	case C.WL_POINTER_AXIS_VERTICAL_SCROLL:
+		// horizontal scroll if shift + mousewheel(up/down) pressed.
+		if w.disp.xkb.Modifiers() == key.ModShift {
+			w.scroll.steps.X += int(discrete)
+		} else {
+			w.scroll.steps.Y += int(discrete)
+		}
+	}
+}
+
+func (w *window) ReadClipboard() {
+	if w.disp.readClipClose != nil {
+		return
+	}
+	w.disp.readClipClose = make(chan struct{})
+	r, err := w.disp.readClipboard()
+	if r == nil || err != nil {
+		return
+	}
+	// Don't let slow clipboard transfers block event loop.
+	go func() {
+		defer r.Close()
+		data, _ := io.ReadAll(r)
+		e := transfer.DataEvent{
+			Type: "application/text",
+			Open: func() io.ReadCloser {
+				return io.NopCloser(bytes.NewReader(data))
+			},
+		}
+		select {
+		case w.clipReads <- e:
+			w.disp.wakeup()
+		case <-w.disp.readClipClose:
+		}
+	}()
+}
+
+func (w *window) WriteClipboard(mime string, s []byte) {
+	w.disp.writeClipboard(s)
+}
+
+func (w *window) Configure(options []Option) {
+	_, cfg := w.getConfig()
+	prev := w.config
+	cnf := w.config
+	cnf.apply(cfg, options)
+	w.config.decoHeight = cnf.decoHeight
+
+	switch cnf.Mode {
+	case Fullscreen:
+		switch prev.Mode {
+		case Minimized, Fullscreen:
+		default:
+			w.config.Mode = Fullscreen
+			w.wsize = w.config.Size
+			C.xdg_toplevel_set_fullscreen(w.topLvl, nil)
+		}
+	case Minimized:
+		switch prev.Mode {
+		case Minimized, Fullscreen:
+		default:
+			w.config.Mode = Minimized
+			C.xdg_toplevel_set_minimized(w.topLvl)
+		}
+	case Maximized:
+		switch prev.Mode {
+		case Minimized, Fullscreen:
+		default:
+			w.config.Mode = Maximized
+			w.wsize = w.config.Size
+			C.xdg_toplevel_set_maximized(w.topLvl)
+			w.setTitle(prev, cnf)
+		}
+	case Windowed:
+		switch prev.Mode {
+		case Fullscreen:
+			w.config.Mode = Windowed
+			w.size = w.wsize.Div(w.scale)
+			C.xdg_toplevel_unset_fullscreen(w.topLvl)
+		case Minimized:
+			w.config.Mode = Windowed
+		case Maximized:
+			w.config.Mode = Windowed
+			w.size = w.wsize.Div(w.scale)
+			C.xdg_toplevel_unset_maximized(w.topLvl)
+		}
+		w.setTitle(prev, cnf)
+		if prev.Size != cnf.Size {
+			w.config.Size = cnf.Size
+			w.config.Size.Y += int(w.decoHeight()) * w.scale
+			w.size = w.config.Size.Div(w.scale)
+		}
+		w.config.MinSize = cnf.MinSize
+		w.config.MaxSize = cnf.MaxSize
+		w.setWindowConstraints()
+	}
+	w.ProcessEvent(ConfigEvent{Config: w.config})
+}
+
+func (w *window) setWindowConstraints() {
+	decoHeight := w.decoHeight()
+	if scaled := w.config.MinSize.Div(w.scale); scaled != (image.Point{}) {
+		C.xdg_toplevel_set_min_size(w.topLvl, C.int32_t(scaled.X), C.int32_t(scaled.Y+decoHeight))
+	}
+	if scaled := w.config.MaxSize.Div(w.scale); scaled != (image.Point{}) {
+		C.xdg_toplevel_set_max_size(w.topLvl, C.int32_t(scaled.X), C.int32_t(scaled.Y+decoHeight))
+	}
+}
+
+// decoHeight returns the adjustment for client-side decorations, if applicable.
+// The unit is in surface-local coordinates.
+func (w *window) decoHeight() int {
+	if !w.config.Decorated {
+		return int(w.config.decoHeight)
+	}
+	return 0
+}
+
+func (w *window) setTitle(prev, cnf Config) {
+	if prev.Title != cnf.Title {
+		w.config.Title = cnf.Title
+		title := C.CString(cnf.Title)
+		C.xdg_toplevel_set_title(w.topLvl, title)
+		C.free(unsafe.Pointer(title))
+	}
+}
+
+func (w *window) Perform(actions system.Action) {
+	// NB. there is no way for a minimized window to be unminimized.
+	// https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:set_minimized
+	walkActions(actions, func(action system.Action) {
+		switch action {
+		case system.ActionClose:
+			w.closing = true
+		}
+	})
+}
+
+func (w *window) move(serial C.uint32_t) {
+	s := w.seat
+	if !w.inCompositor && s != nil {
+		w.inCompositor = true
+		C.xdg_toplevel_move(w.topLvl, s.seat, serial)
+	}
+}
+
+func (w *window) resize(serial, edge C.uint32_t) {
+	s := w.seat
+	if w.inCompositor || s == nil {
+		return
+	}
+	w.inCompositor = true
+	C.xdg_toplevel_resize(w.topLvl, s.seat, serial, edge)
+}
+
+func (w *window) SetCursor(cursor pointer.Cursor) {
+	w.cursor.cursor = w.loadCursor(cursor)
+	w.updateCursor()
+}
+
+func (w *window) updateCursor() {
+	ptr := w.disp.seat.pointer
+	if ptr == nil {
+		return
+	}
+	w.setCursor(ptr, w.serial)
+}
+
+func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
+	c := w.cursor.system
+	if c == nil {
+		c = w.cursor.cursor
+	}
+	if c == nil {
+		C.wl_pointer_set_cursor(pointer, w.serial, nil, 0, 0)
+		return
+	}
+	// Get images[0].
+	img := *c.images
+	buf := C.wl_cursor_image_get_buffer(img)
+	if buf == nil {
+		return
+	}
+	C.wl_pointer_set_cursor(pointer, serial, w.cursor.surf, C.int32_t(img.hotspot_x/C.uint(w.scale)), C.int32_t(img.hotspot_y/C.uint(w.scale)))
+	C.wl_surface_attach(w.cursor.surf, buf, 0, 0)
+	C.wl_surface_damage(w.cursor.surf, 0, 0, C.int32_t(img.width), C.int32_t(img.height))
+	C.wl_surface_commit(w.cursor.surf)
+}
+
+func (w *window) resetFling() {
+	w.fling.start = false
+	w.fling.anim = fling.Animation{}
+}
+
+//export gio_onKeyboardKeymap
+func gio_onKeyboardKeymap(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, format C.uint32_t, fd C.int32_t, size C.uint32_t) {
+	defer syscall.Close(int(fd))
+	s := callbackLoad(data).(*wlSeat)
+	s.disp.repeat.Stop(0)
+	s.disp.xkb.DestroyKeymapState()
+	if format != C.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 {
+		return
+	}
+	if err := s.disp.xkb.LoadKeymap(int(format), int(fd), int(size)); err != nil {
+		// TODO: Do better.
+		panic(err)
+	}
+}
+
+//export gio_onKeyboardEnter
+func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial C.uint32_t, surf *C.struct_wl_surface, keys *C.struct_wl_array) {
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+	w := callbackLoad(unsafe.Pointer(surf)).(*window)
+	s.keyboardFocus = w
+	s.disp.repeat.Stop(0)
+	w.config.Focused = true
+	w.ProcessEvent(ConfigEvent{Config: w.config})
+}
+
+//export gio_onKeyboardLeave
+func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial C.uint32_t, surf *C.struct_wl_surface) {
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+	s.disp.repeat.Stop(0)
+	w := s.keyboardFocus
+	w.config.Focused = false
+	w.ProcessEvent(ConfigEvent{Config: w.config})
+}
+
+//export gio_onKeyboardKey
+func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, timestamp, keyCode, state C.uint32_t) {
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+	w := s.keyboardFocus
+	t := time.Duration(timestamp) * time.Millisecond
+	s.disp.repeat.Stop(t)
+	w.resetFling()
+	kc := mapXKBKeycode(uint32(keyCode))
+	ks := mapXKBKeyState(uint32(state))
+	for _, e := range w.disp.xkb.DispatchKey(kc, ks) {
+		if ee, ok := e.(key.EditEvent); ok {
+			// There's no support for IME yet.
+			w.w.EditorInsert(ee.Text)
+		} else {
+			w.ProcessEvent(e)
+		}
+	}
+	if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
+		return
+	}
+	if w.disp.xkb.IsRepeatKey(kc) {
+		w.disp.repeat.Start(w, kc, t)
+	}
+}
+
+func mapXKBKeycode(keyCode uint32) uint32 {
+	// According to the xkb_v1 spec: "to determine the xkb keycode, clients must add 8 to the key event keycode."
+	return keyCode + 8
+}
+
+func mapXKBKeyState(state uint32) key.State {
+	switch state {
+	case C.WL_KEYBOARD_KEY_STATE_RELEASED:
+		return key.Release
+	default:
+		return key.Press
+	}
+}
+
+func (r *repeatState) Start(w *window, keyCode uint32, t time.Duration) {
+	if r.rate <= 0 {
+		return
+	}
+	stopC := make(chan struct{})
+	r.start = t
+	r.last = 0
+	r.now = 0
+	r.stopC = stopC
+	r.key = keyCode
+	r.win = w
+	rate, delay := r.rate, r.delay
+	go func() {
+		timer := time.NewTimer(delay)
+		for {
+			select {
+			case <-timer.C:
+			case <-stopC:
+				close(stopC)
+				return
+			}
+			r.Advance(delay)
+			w.disp.wakeup()
+			delay = time.Second / time.Duration(rate)
+			timer.Reset(delay)
+		}
+	}()
+}
+
+func (r *repeatState) Stop(t time.Duration) {
+	if r.stopC == nil {
+		return
+	}
+	r.stopC <- struct{}{}
+	<-r.stopC
+	r.stopC = nil
+	t -= r.start
+	if r.now > t {
+		r.now = t
+	}
+}
+
+func (r *repeatState) Advance(dt time.Duration) {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	r.now += dt
+}
+
+func (r *repeatState) Repeat(d *wlDisplay) {
+	if r.rate <= 0 {
+		return
+	}
+	r.mu.Lock()
+	now := r.now
+	r.mu.Unlock()
+	for {
+		var delay time.Duration
+		if r.last < r.delay {
+			delay = r.delay
+		} else {
+			delay = time.Second / time.Duration(r.rate)
+		}
+		if r.last+delay > now {
+			break
+		}
+		for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
+			if ee, ok := e.(key.EditEvent); ok {
+				// There's no support for IME yet.
+				r.win.w.EditorInsert(ee.Text)
+			} else {
+				r.win.ProcessEvent(e)
+			}
+		}
+		r.last += delay
+	}
+}
+
+//export gio_onFrameDone
+func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.uint32_t) {
+	C.wl_callback_destroy(callback)
+	w := callbackLoad(data).(*window)
+	if w.lastFrameCallback == callback {
+		w.lastFrameCallback = nil
+		w.draw(false)
+	}
+}
+
+func (w *window) close(err error) {
+	w.ProcessEvent(WaylandViewEvent{})
+	w.ProcessEvent(DestroyEvent{Err: err})
+	w.destroy()
+	w.disp.destroy()
+	w.disp = nil
+}
+
+func (w *window) dispatch() {
+	if w.disp == nil {
+		<-w.wakeups
+		w.w.Invalidate()
+		return
+	}
+	if err := w.disp.dispatch(); err != nil || w.closing {
+		w.close(err)
+		return
+	}
+	select {
+	case e := <-w.clipReads:
+		w.disp.readClipClose = nil
+		w.ProcessEvent(e)
+	case <-w.wakeups:
+		w.w.Invalidate()
+	default:
+	}
+}
+
+func (w *window) ProcessEvent(e event.Event) {
+	w.w.ProcessEvent(e)
+}
+
+func (w *window) Event() event.Event {
+	for {
+		evt, ok := w.w.nextEvent()
+		if !ok {
+			w.dispatch()
+			continue
+		}
+		return evt
+	}
+}
+
+func (w *window) Invalidate() {
+	select {
+	case w.wakeups <- struct{}{}:
+	default:
+		return
+	}
+	w.disp.wakeup()
+}
+
+func (w *window) Run(f func()) {
+	f()
+}
+
+func (w *window) Frame(frame *op.Ops) {
+	w.w.ProcessFrame(frame, nil)
+}
+
+// bindDataDevice initializes the dataDev field if and only if both
+// the seat and dataDeviceManager fields are initialized.
+func (d *wlDisplay) bindDataDevice() {
+	if d.seat != nil && d.dataDeviceManager != nil {
+		d.seat.dataDev = C.wl_data_device_manager_get_data_device(d.dataDeviceManager, d.seat.seat)
+		if d.seat.dataDev == nil {
+			return
+		}
+		callbackStore(unsafe.Pointer(d.seat.dataDev), d.seat)
+		C.wl_data_device_add_listener(d.seat.dataDev, &C.gio_data_device_listener, unsafe.Pointer(d.seat.dataDev))
+	}
+}
+
+func (d *wlDisplay) dispatch() error {
+	// wl_display_prepare_read records the current thread for
+	// use in wl_display_read_events or wl_display_cancel_events.
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	dispfd := C.wl_display_get_fd(d.disp)
+	// Poll for events and notifications.
+	pollfds := append(d.poller.pollfds[:0],
+		syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
+		syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
+	)
+	for C.wl_display_prepare_read(d.disp) != 0 {
+		C.wl_display_dispatch_pending(d.disp)
+	}
+	dispFd := &pollfds[0]
+	if ret, err := C.wl_display_flush(d.disp); ret < 0 {
+		if err != syscall.EAGAIN {
+			return fmt.Errorf("wayland: wl_display_flush failed: %v", err)
+		}
+		// EAGAIN means the output buffer was full. Poll for
+		// POLLOUT to know when we can write again.
+		dispFd.Events |= syscall.POLLOUT
+	}
+	if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
+		C.wl_display_cancel_read(d.disp)
+		return fmt.Errorf("wayland: poll failed: %v", err)
+	}
+	if dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0 {
+		C.wl_display_cancel_read(d.disp)
+		return errors.New("wayland: display file descriptor gone")
+	}
+	// Handle events.
+	if dispFd.Revents&syscall.POLLIN != 0 {
+		if ret, err := C.wl_display_read_events(d.disp); ret < 0 {
+			return fmt.Errorf("wayland: wl_display_read_events failed: %v", err)
+		}
+		C.wl_display_dispatch_pending(d.disp)
+	} else {
+		C.wl_display_cancel_read(d.disp)
+	}
+	// Clear notifications.
+	for {
+		_, err := syscall.Read(d.notify.read, d.poller.buf[:])
+		if err == syscall.EAGAIN {
+			break
+		}
+		if err != nil {
+			return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
+		}
+	}
+	d.repeat.Repeat(d)
+	return nil
+}
+
+func (w *window) SetAnimating(anim bool) {
+	w.animating = anim
+	if anim {
+		w.draw(false)
+	}
+}
+
+// Wakeup wakes up the event loop through the notification pipe.
+func (d *wlDisplay) wakeup() {
+	oneByte := make([]byte, 1)
+	if _, err := syscall.Write(d.notify.write, oneByte); err != nil && err != syscall.EAGAIN {
+		panic(fmt.Errorf("failed to write to pipe: %v", err))
+	}
+}
+
+func (w *window) destroy() {
+	if w.lastFrameCallback != nil {
+		C.wl_callback_destroy(w.lastFrameCallback)
+		w.lastFrameCallback = nil
+	}
+	if w.cursor.surf != nil {
+		C.wl_surface_destroy(w.cursor.surf)
+	}
+	if w.cursor.theme != nil {
+		C.wl_cursor_theme_destroy(w.cursor.theme)
+	}
+	if w.topLvl != nil {
+		C.xdg_toplevel_destroy(w.topLvl)
+	}
+	if w.surf != nil {
+		C.wl_surface_destroy(w.surf)
+	}
+	if w.wmSurf != nil {
+		C.xdg_surface_destroy(w.wmSurf)
+	}
+	if w.decor != nil {
+		C.zxdg_toplevel_decoration_v1_destroy(w.decor)
+	}
+	callbackDelete(unsafe.Pointer(w.surf))
+}
+
+//export gio_onKeyboardModifiers
+func gio_onKeyboardModifiers(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, depressed, latched, locked, group C.uint32_t) {
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+	d := s.disp
+	d.repeat.Stop(0)
+	if d.xkb == nil {
+		return
+	}
+	d.xkb.UpdateMask(uint32(depressed), uint32(latched), uint32(locked), uint32(group), uint32(group), uint32(group))
+}
+
+//export gio_onKeyboardRepeatInfo
+func gio_onKeyboardRepeatInfo(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, rate, delay C.int32_t) {
+	s := callbackLoad(data).(*wlSeat)
+	d := s.disp
+	d.repeat.Stop(0)
+	d.repeat.rate = int(rate)
+	d.repeat.delay = time.Duration(delay) * time.Millisecond
+}
+
+//export gio_onTextInputEnter
+func gio_onTextInputEnter(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, surf *C.struct_wl_surface) {
+}
+
+//export gio_onTextInputLeave
+func gio_onTextInputLeave(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, surf *C.struct_wl_surface) {
+}
+
+//export gio_onTextInputPreeditString
+func gio_onTextInputPreeditString(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, ctxt *C.char, begin, end C.int32_t) {
+}
+
+//export gio_onTextInputCommitString
+func gio_onTextInputCommitString(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, ctxt *C.char) {
+}
+
+//export gio_onTextInputDeleteSurroundingText
+func gio_onTextInputDeleteSurroundingText(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, before, after C.uint32_t) {
+}
+
+//export gio_onTextInputDone
+func gio_onTextInputDone(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, serial C.uint32_t) {
+	s := callbackLoad(data).(*wlSeat)
+	s.serial = serial
+}
+
+//export gio_onDataSourceTarget
+func gio_onDataSourceTarget(data unsafe.Pointer, source *C.struct_wl_data_source, mime *C.char) {
+}
+
+//export gio_onDataSourceSend
+func gio_onDataSourceSend(data unsafe.Pointer, source *C.struct_wl_data_source, mime *C.char, fd C.int32_t) {
+	s := callbackLoad(data).(*wlSeat)
+	content := s.content
+	go func() {
+		defer syscall.Close(int(fd))
+		syscall.Write(int(fd), content)
+	}()
+}
+
+//export gio_onDataSourceCancelled
+func gio_onDataSourceCancelled(data unsafe.Pointer, source *C.struct_wl_data_source) {
+	s := callbackLoad(data).(*wlSeat)
+	if s.source == source {
+		s.content = nil
+		s.source = nil
+	}
+	C.wl_data_source_destroy(source)
+}
+
+//export gio_onDataSourceDNDDropPerformed
+func gio_onDataSourceDNDDropPerformed(data unsafe.Pointer, source *C.struct_wl_data_source) {
+}
+
+//export gio_onDataSourceDNDFinished
+func gio_onDataSourceDNDFinished(data unsafe.Pointer, source *C.struct_wl_data_source) {
+}
+
+//export gio_onDataSourceAction
+func gio_onDataSourceAction(data unsafe.Pointer, source *C.struct_wl_data_source, act C.uint32_t) {
+}
+
+func (w *window) flushScroll() {
+	var fling f32.Point
+	if w.fling.anim.Active() {
+		dist := float32(w.fling.anim.Tick(time.Now()))
+		fling = w.fling.dir.Mul(dist)
+	}
+	// The Wayland reported scroll distance for
+	// discrete scroll axes is only 10 pixels, where
+	// 100 seems more appropriate.
+	const discreteScale = 10
+	if w.scroll.steps.X != 0 {
+		w.scroll.dist.X *= discreteScale
+	}
+	if w.scroll.steps.Y != 0 {
+		w.scroll.dist.Y *= discreteScale
+	}
+	total := w.scroll.dist.Add(fling)
+	if total == (f32.Point{}) {
+		return
+	}
+	if w.scroll.steps == (image.Point{}) {
+		w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
+		w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
+	}
+	// Zero scroll distance prior to calling ProcessEvent, otherwise we may recursively
+	// re-process the scroll distance.
+	w.scroll.dist = f32.Point{}
+	w.scroll.steps = image.Point{}
+	w.ProcessEvent(pointer.Event{
+		Kind:      pointer.Scroll,
+		Source:    pointer.Mouse,
+		Buttons:   w.pointerBtns,
+		Position:  w.lastPos,
+		Scroll:    total,
+		Time:      w.scroll.time,
+		Modifiers: w.disp.xkb.Modifiers(),
+	})
+}
+
+func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
+	w.flushScroll()
+	w.lastPos = f32.Point{
+		X: fromFixed(x) * float32(w.scale),
+		Y: fromFixed(y) * float32(w.scale),
+	}
+	w.ProcessEvent(pointer.Event{
+		Kind:      pointer.Move,
+		Position:  w.lastPos,
+		Buttons:   w.pointerBtns,
+		Source:    pointer.Mouse,
+		Time:      time.Duration(t) * time.Millisecond,
+		Modifiers: w.disp.xkb.Modifiers(),
+	})
+	c, _ := w.systemGesture()
+	if c != w.cursor.system {
+		w.cursor.system = c
+		w.updateCursor()
+	}
+}
+
+// updateCursor updates the system gesture cursor according to the pointer
+// position.
+func (w *window) systemGesture() (*C.struct_wl_cursor, C.uint32_t) {
+	if w.config.Mode != Windowed || w.config.Decorated {
+		return nil, 0
+	}
+	_, cfg := w.getConfig()
+	border := cfg.Dp(3)
+	x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size
+	north := y <= border
+	south := y >= size.Y-border
+	west := x <= border
+	east := x >= size.X-border
+
+	switch {
+	default:
+		fallthrough
+	case !north && !south && !west && !east:
+		return nil, 0
+	case north && west:
+		return w.cursor.cursors.resizeNorthWest, C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT
+	case north && east:
+		return w.cursor.cursors.resizeNorthEast, C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT
+	case south && west:
+		return w.cursor.cursors.resizeSouthWest, C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT
+	case south && east:
+		return w.cursor.cursors.resizeSouthEast, C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT
+	case north:
+		return w.cursor.cursors.resizeNorth, C.XDG_TOPLEVEL_RESIZE_EDGE_TOP
+	case south:
+		return w.cursor.cursors.resizeSouth, C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM
+	case west:
+		return w.cursor.cursors.resizeWest, C.XDG_TOPLEVEL_RESIZE_EDGE_LEFT
+	case east:
+		return w.cursor.cursors.resizeEast, C.XDG_TOPLEVEL_RESIZE_EDGE_RIGHT
+	}
+}
+
+func (w *window) updateOpaqueRegion() {
+	reg := C.wl_compositor_create_region(w.disp.compositor)
+	C.wl_region_add(reg, 0, 0, C.int32_t(w.size.X), C.int32_t(w.size.Y))
+	C.wl_surface_set_opaque_region(w.surf, reg)
+	C.wl_region_destroy(reg)
+}
+
+func (w *window) updateOutputs() {
+	scale := 1
+	var found bool
+	for _, conf := range w.disp.outputConfig {
+		for _, w2 := range conf.windows {
+			if w2 == w {
+				found = true
+				if conf.scale > scale {
+					scale = conf.scale
+				}
+			}
+		}
+	}
+	if found && scale != w.scale {
+		w.scale = scale
+		C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
+		w.draw(true)
+	}
+	if found {
+		w.draw(true)
+	}
+}
+
+func (w *window) getConfig() (image.Point, unit.Metric) {
+	size := w.size.Mul(w.scale)
+	return size, unit.Metric{
+		PxPerDp: w.ppdp * float32(w.scale),
+		PxPerSp: w.ppsp * float32(w.scale),
+	}
+}
+
+func (w *window) draw(sync bool) {
+	if !w.configured {
+		return
+	}
+	w.flushScroll()
+	size, cfg := w.getConfig()
+	if cfg == (unit.Metric{}) {
+		return
+	}
+	if size != w.config.Size {
+		w.config.Size = size
+		w.ProcessEvent(ConfigEvent{Config: w.config})
+	}
+	anim := w.animating || w.fling.anim.Active()
+	// Draw animation only when not waiting for frame callback.
+	redrawAnim := anim && w.lastFrameCallback == nil
+	if !redrawAnim && !sync {
+		return
+	}
+	if anim {
+		w.lastFrameCallback = C.wl_surface_frame(w.surf)
+		// Use the surface as listener data for gio_onFrameDone.
+		C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
+	}
+	w.ProcessEvent(frameEvent{
+		FrameEvent: FrameEvent{
+			Now:    time.Now(),
+			Size:   w.config.Size,
+			Metric: cfg,
+		},
+		Sync: sync,
+	})
+}
+
+func (w *window) display() *C.struct_wl_display {
+	return w.disp.disp
+}
+
+func (w *window) surface() (*C.struct_wl_surface, int, int) {
+	sz, _ := w.getConfig()
+	return w.surf, sz.X, sz.Y
+}
+
+func (w *window) ShowTextInput(show bool) {}
+
+func (w *window) SetInputHint(_ key.InputHint) {}
+
+func (w *window) EditorStateChanged(old, new editorState) {}
+
+func (w *window) NewContext() (context, error) {
+	var firstErr error
+	if f := newWaylandEGLContext; f != nil {
+		c, err := f(w)
+		if err == nil {
+			return c, nil
+		}
+		firstErr = err
+	}
+	if f := newWaylandVulkanContext; f != nil {
+		c, err := f(w)
+		if err == nil {
+			return c, nil
+		}
+		firstErr = err
+	}
+	if firstErr != nil {
+		return nil, firstErr
+	}
+	return nil, errors.New("wayland: no available GPU backends")
+}
+
+// detectUIScale reports the system UI scale, or 1.0 if it fails.
+func detectUIScale() float32 {
+	// TODO: What about other window environments?
+	out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor").Output()
+	if err != nil {
+		return 1.0
+	}
+	scale, err := strconv.ParseFloat(string(bytes.TrimSpace(out)), 32)
+	if err != nil {
+		return 1.0
+	}
+	return float32(scale)
+}
+
+func newWLDisplay() (*wlDisplay, error) {
+	d := &wlDisplay{
+		outputMap:    make(map[C.uint32_t]*C.struct_wl_output),
+		outputConfig: make(map[*C.struct_wl_output]*wlOutput),
+	}
+	pipe := make([]int, 2)
+	if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
+		return nil, fmt.Errorf("wayland: failed to create pipe: %v", err)
+	}
+	d.notify.read = pipe[0]
+	d.notify.write = pipe[1]
+	xkb, err := xkb.New()
+	if err != nil {
+		d.destroy()
+		return nil, fmt.Errorf("wayland: %v", err)
+	}
+	d.xkb = xkb
+	d.disp, err = C.wl_display_connect(nil)
+	if d.disp == nil {
+		d.destroy()
+		return nil, fmt.Errorf("wayland: wl_display_connect failed: %v", err)
+	}
+	callbackMap.Store(unsafe.Pointer(d.disp), d)
+	d.reg = C.wl_display_get_registry(d.disp)
+	if d.reg == nil {
+		d.destroy()
+		return nil, errors.New("wayland: wl_display_get_registry failed")
+	}
+	C.wl_registry_add_listener(d.reg, &C.gio_registry_listener, unsafe.Pointer(d.disp))
+	// Wait for the server to register all its globals to the
+	// registry listener (gio_onRegistryGlobal).
+	C.wl_display_roundtrip(d.disp)
+	// Configuration listeners are added to outputs by gio_onRegistryGlobal.
+	// We need another roundtrip to get the initial output configurations
+	// through the gio_onOutput* callbacks.
+	C.wl_display_roundtrip(d.disp)
+	return d, nil
+}
+
+func (d *wlDisplay) destroy() {
+	if d.readClipClose != nil {
+		close(d.readClipClose)
+		d.readClipClose = nil
+	}
+	if d.notify.write != 0 {
+		syscall.Close(d.notify.write)
+		d.notify.write = 0
+	}
+	if d.notify.read != 0 {
+		syscall.Close(d.notify.read)
+		d.notify.read = 0
+	}
+	d.repeat.Stop(0)
+	if d.xkb != nil {
+		d.xkb.Destroy()
+		d.xkb = nil
+	}
+	if d.seat != nil {
+		d.seat.destroy()
+		d.seat = nil
+	}
+	if d.imm != nil {
+		C.zwp_text_input_manager_v3_destroy(d.imm)
+	}
+	if d.decor != nil {
+		C.zxdg_decoration_manager_v1_destroy(d.decor)
+	}
+	if d.shm != nil {
+		C.wl_shm_destroy(d.shm)
+	}
+	if d.compositor != nil {
+		C.wl_compositor_destroy(d.compositor)
+	}
+	if d.wm != nil {
+		C.xdg_wm_base_destroy(d.wm)
+	}
+	for _, output := range d.outputMap {
+		C.wl_output_destroy(output)
+	}
+	if d.reg != nil {
+		C.wl_registry_destroy(d.reg)
+	}
+	if d.disp != nil {
+		C.wl_display_disconnect(d.disp)
+		callbackDelete(unsafe.Pointer(d.disp))
+		d.disp = nil
+	}
+}
+
+// fromFixed converts a Wayland wl_fixed_t 23.8 number to float32.
+func fromFixed(v C.wl_fixed_t) float32 {
+	// Convert to float64 to avoid overflow.
+	// From wayland-util.h.
+	b := ((1023 + 44) << 52) + (1 << 51) + uint64(v)
+	f := math.Float64frombits(b) - (3 << 43)
+	return float32(f)
+}

+ 995 - 0
vendor/gioui.org/app/os_windows.go

@@ -0,0 +1,995 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+import (
+	"errors"
+	"fmt"
+	"image"
+	"io"
+	"runtime"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+	"unicode"
+	"unicode/utf8"
+	"unsafe"
+
+	syscall "golang.org/x/sys/windows"
+
+	"gioui.org/app/internal/windows"
+	"gioui.org/op"
+	"gioui.org/unit"
+	gowindows "golang.org/x/sys/windows"
+
+	"gioui.org/f32"
+	"gioui.org/io/event"
+	"gioui.org/io/key"
+	"gioui.org/io/pointer"
+	"gioui.org/io/system"
+	"gioui.org/io/transfer"
+)
+
+type Win32ViewEvent struct {
+	HWND uintptr
+}
+
+type window struct {
+	hwnd        syscall.Handle
+	hdc         syscall.Handle
+	w           *callbacks
+	pointerBtns pointer.Buttons
+
+	// cursorIn tracks whether the cursor was inside the window according
+	// to the most recent WM_SETCURSOR.
+	cursorIn bool
+	cursor   syscall.Handle
+
+	animating bool
+
+	borderSize image.Point
+	config     Config
+	// frameDims stores the last seen window frame width and height.
+	frameDims image.Point
+	loop      *eventLoop
+}
+
+const _WM_WAKEUP = windows.WM_USER + iota
+
+type gpuAPI struct {
+	priority    int
+	initializer func(w *window) (context, error)
+}
+
+// drivers is the list of potential Context implementations.
+var drivers []gpuAPI
+
+// winMap maps win32 HWNDs to *windows.
+var winMap sync.Map
+
+// iconID is the ID of the icon in the resource file.
+const iconID = 1
+
+var resources struct {
+	once sync.Once
+	// handle is the module handle from GetModuleHandle.
+	handle syscall.Handle
+	// class is the Gio window class from RegisterClassEx.
+	class uint16
+	// cursor is the arrow cursor resource.
+	cursor syscall.Handle
+}
+
+func osMain() {
+	select {}
+}
+
+func newWindow(win *callbacks, options []Option) {
+	done := make(chan struct{})
+	go func() {
+		// GetMessage and PeekMessage can filter on a window HWND, but
+		// then thread-specific messages such as WM_QUIT are ignored.
+		// Instead lock the thread so window messages arrive through
+		// unfiltered GetMessage calls.
+		runtime.LockOSThread()
+
+		w := &window{
+			w: win,
+		}
+		w.loop = newEventLoop(w.w, w.wakeup)
+		w.w.SetDriver(w)
+		err := w.init()
+		done <- struct{}{}
+		if err != nil {
+			w.ProcessEvent(DestroyEvent{Err: err})
+			return
+		}
+		winMap.Store(w.hwnd, w)
+		defer winMap.Delete(w.hwnd)
+		w.Configure(options)
+		w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
+		windows.SetForegroundWindow(w.hwnd)
+		windows.SetFocus(w.hwnd)
+		// Since the window class for the cursor is null,
+		// set it here to show the cursor.
+		w.SetCursor(pointer.CursorDefault)
+		w.runLoop()
+	}()
+	<-done
+}
+
+// initResources initializes the resources global.
+func initResources() error {
+	windows.SetProcessDPIAware()
+	hInst, err := windows.GetModuleHandle()
+	if err != nil {
+		return err
+	}
+	resources.handle = hInst
+	c, err := windows.LoadCursor(windows.IDC_ARROW)
+	if err != nil {
+		return err
+	}
+	resources.cursor = c
+	icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
+	wcls := windows.WndClassEx{
+		CbSize:        uint32(unsafe.Sizeof(windows.WndClassEx{})),
+		Style:         windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
+		LpfnWndProc:   syscall.NewCallback(windowProc),
+		HInstance:     hInst,
+		HIcon:         icon,
+		LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
+	}
+	cls, err := windows.RegisterClassEx(&wcls)
+	if err != nil {
+		return err
+	}
+	resources.class = cls
+	return nil
+}
+
+const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
+
+func (w *window) init() error {
+	var resErr error
+	resources.once.Do(func() {
+		resErr = initResources()
+	})
+	if resErr != nil {
+		return resErr
+	}
+	const dwStyle = windows.WS_OVERLAPPEDWINDOW
+
+	hwnd, err := windows.CreateWindowEx(
+		dwExStyle,
+		resources.class,
+		"",
+		dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
+		windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
+		windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
+		0,
+		0,
+		resources.handle,
+		0)
+	if err != nil {
+		return err
+	}
+	w.hdc, err = windows.GetDC(hwnd)
+	if err != nil {
+		windows.DestroyWindow(hwnd)
+		return err
+	}
+	w.hwnd = hwnd
+	return nil
+}
+
+// update handles changes done by the user, and updates the configuration.
+// It reads the window style and size/position and updates w.config.
+// If anything has changed it emits a ConfigEvent to notify the application.
+func (w *window) update() {
+	p := windows.GetWindowPlacement(w.hwnd)
+	if !p.IsMinimized() {
+		r := windows.GetWindowRect(w.hwnd)
+		cr := windows.GetClientRect(w.hwnd)
+		w.config.Size = image.Point{
+			X: int(cr.Right - cr.Left),
+			Y: int(cr.Bottom - cr.Top),
+		}
+		w.frameDims = image.Point{
+			X: int(r.Right - r.Left),
+			Y: int(r.Bottom - r.Top),
+		}.Sub(w.config.Size)
+	}
+
+	w.borderSize = image.Pt(
+		windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
+		windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
+	)
+	style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
+	switch {
+	case p.IsMaximized() && style&windows.WS_OVERLAPPEDWINDOW != 0:
+		w.config.Mode = Maximized
+	case p.IsMaximized():
+		w.config.Mode = Fullscreen
+	default:
+		w.config.Mode = Windowed
+	}
+	w.ProcessEvent(ConfigEvent{Config: w.config})
+	w.draw(true)
+}
+
+func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
+	win, exists := winMap.Load(hwnd)
+	if !exists {
+		return windows.DefWindowProc(hwnd, msg, wParam, lParam)
+	}
+
+	w := win.(*window)
+
+	switch msg {
+	case windows.WM_UNICHAR:
+		if wParam == windows.UNICODE_NOCHAR {
+			// Tell the system that we accept WM_UNICHAR messages.
+			return windows.TRUE
+		}
+		fallthrough
+	case windows.WM_CHAR:
+		if r := rune(wParam); unicode.IsPrint(r) {
+			w.w.EditorInsert(string(r))
+		}
+		// The message is processed.
+		return windows.TRUE
+	case windows.WM_DPICHANGED:
+		// Let Windows know we're prepared for runtime DPI changes.
+		return windows.TRUE
+	case windows.WM_ERASEBKGND:
+		// Avoid flickering between GPU content and background color.
+		return windows.TRUE
+	case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP:
+		if n, ok := convertKeyCode(wParam); ok {
+			e := key.Event{
+				Name:      n,
+				Modifiers: getModifiers(),
+				State:     key.Press,
+			}
+			if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP {
+				e.State = key.Release
+			}
+
+			w.ProcessEvent(e)
+
+			if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
+				// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
+				// such as cmd.exe and graphical debuggers also reserve F10.
+				return 0
+			}
+		}
+	case windows.WM_LBUTTONDOWN:
+		w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
+	case windows.WM_LBUTTONUP:
+		w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
+	case windows.WM_RBUTTONDOWN:
+		w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
+	case windows.WM_RBUTTONUP:
+		w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
+	case windows.WM_MBUTTONDOWN:
+		w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
+	case windows.WM_MBUTTONUP:
+		w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
+	case windows.WM_CANCELMODE:
+		w.ProcessEvent(pointer.Event{
+			Kind: pointer.Cancel,
+		})
+	case windows.WM_SETFOCUS:
+		w.config.Focused = true
+		w.ProcessEvent(ConfigEvent{Config: w.config})
+	case windows.WM_KILLFOCUS:
+		w.config.Focused = false
+		w.ProcessEvent(ConfigEvent{Config: w.config})
+	case windows.WM_NCHITTEST:
+		if w.config.Decorated {
+			// Let the system handle it.
+			break
+		}
+		x, y := coordsFromlParam(lParam)
+		np := windows.Point{X: int32(x), Y: int32(y)}
+		windows.ScreenToClient(w.hwnd, &np)
+		return w.hitTest(int(np.X), int(np.Y))
+	case windows.WM_MOUSEMOVE:
+		x, y := coordsFromlParam(lParam)
+		p := f32.Point{X: float32(x), Y: float32(y)}
+		w.ProcessEvent(pointer.Event{
+			Kind:      pointer.Move,
+			Source:    pointer.Mouse,
+			Position:  p,
+			Buttons:   w.pointerBtns,
+			Time:      windows.GetMessageTime(),
+			Modifiers: getModifiers(),
+		})
+	case windows.WM_MOUSEWHEEL:
+		w.scrollEvent(wParam, lParam, false, getModifiers())
+	case windows.WM_MOUSEHWHEEL:
+		w.scrollEvent(wParam, lParam, true, getModifiers())
+	case windows.WM_DESTROY:
+		w.ProcessEvent(Win32ViewEvent{})
+		w.ProcessEvent(DestroyEvent{})
+		w.w = nil
+		if w.hdc != 0 {
+			windows.ReleaseDC(w.hdc)
+			w.hdc = 0
+		}
+		// The system destroys the HWND for us.
+		w.hwnd = 0
+		windows.PostQuitMessage(0)
+		return 0
+	case windows.WM_NCCALCSIZE:
+		if w.config.Decorated {
+			// Let Windows handle decorations.
+			break
+		}
+		// No client areas; we draw decorations ourselves.
+		if wParam != 1 {
+			return 0
+		}
+		// lParam contains an NCCALCSIZE_PARAMS for us to adjust.
+		place := windows.GetWindowPlacement(w.hwnd)
+		if !place.IsMaximized() {
+			// Nothing do adjust.
+			return 0
+		}
+		// Adjust window position to avoid the extra padding in maximized
+		// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
+		// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
+		szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
+		mi := windows.GetMonitorInfo(w.hwnd)
+		szp.Rgrc[0] = mi.WorkArea
+		return 0
+	case windows.WM_PAINT:
+		w.draw(true)
+	case windows.WM_STYLECHANGED:
+		w.update()
+	case windows.WM_WINDOWPOSCHANGED:
+		w.update()
+	case windows.WM_SIZE:
+		w.update()
+	case windows.WM_GETMINMAXINFO:
+		mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
+
+		var frameDims image.Point
+		if w.config.Decorated {
+			frameDims = w.frameDims
+		}
+		if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
+			p = p.Add(frameDims)
+			mm.PtMinTrackSize = windows.Point{
+				X: int32(p.X),
+				Y: int32(p.Y),
+			}
+		}
+		if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
+			p = p.Add(frameDims)
+			mm.PtMaxTrackSize = windows.Point{
+				X: int32(p.X),
+				Y: int32(p.Y),
+			}
+		}
+		return 0
+	case windows.WM_SETCURSOR:
+		w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
+		if w.cursorIn {
+			windows.SetCursor(w.cursor)
+			return windows.TRUE
+		}
+	case _WM_WAKEUP:
+		w.loop.Wakeup()
+		w.loop.FlushEvents()
+	case windows.WM_IME_STARTCOMPOSITION:
+		imc := windows.ImmGetContext(w.hwnd)
+		if imc == 0 {
+			return windows.TRUE
+		}
+		defer windows.ImmReleaseContext(w.hwnd, imc)
+		sel := w.w.EditorState().Selection
+		caret := sel.Transform.Transform(sel.Caret.Pos.Add(f32.Pt(0, sel.Caret.Descent)))
+		icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5))
+		windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y)
+		windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y)
+	case windows.WM_IME_COMPOSITION:
+		imc := windows.ImmGetContext(w.hwnd)
+		if imc == 0 {
+			return windows.TRUE
+		}
+		defer windows.ImmReleaseContext(w.hwnd, imc)
+		state := w.w.EditorState()
+		rng := state.compose
+		if rng.Start == -1 {
+			rng = state.Selection.Range
+		}
+		if rng.Start > rng.End {
+			rng.Start, rng.End = rng.End, rng.Start
+		}
+		var replacement string
+		switch {
+		case lParam&windows.GCS_RESULTSTR != 0:
+			replacement = windows.ImmGetCompositionString(imc, windows.GCS_RESULTSTR)
+		case lParam&windows.GCS_COMPSTR != 0:
+			replacement = windows.ImmGetCompositionString(imc, windows.GCS_COMPSTR)
+		}
+		end := rng.Start + utf8.RuneCountInString(replacement)
+		w.w.EditorReplace(rng, replacement)
+		state = w.w.EditorState()
+		comp := key.Range{
+			Start: rng.Start,
+			End:   end,
+		}
+		if lParam&windows.GCS_DELTASTART != 0 {
+			start := windows.ImmGetCompositionValue(imc, windows.GCS_DELTASTART)
+			comp.Start = state.RunesIndex(state.UTF16Index(comp.Start) + start)
+		}
+		w.w.SetComposingRegion(comp)
+		pos := end
+		if lParam&windows.GCS_CURSORPOS != 0 {
+			rel := windows.ImmGetCompositionValue(imc, windows.GCS_CURSORPOS)
+			pos = state.RunesIndex(state.UTF16Index(rng.Start) + rel)
+		}
+		w.w.SetEditorSelection(key.Range{Start: pos, End: pos})
+		return windows.TRUE
+	case windows.WM_IME_ENDCOMPOSITION:
+		w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
+		return windows.TRUE
+	}
+
+	return windows.DefWindowProc(hwnd, msg, wParam, lParam)
+}
+
+func getModifiers() key.Modifiers {
+	var kmods key.Modifiers
+	if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
+		kmods |= key.ModSuper
+	}
+	if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
+		kmods |= key.ModAlt
+	}
+	if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
+		kmods |= key.ModCtrl
+	}
+	if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
+		kmods |= key.ModShift
+	}
+	return kmods
+}
+
+// hitTest returns the non-client area hit by the point, needed to
+// process WM_NCHITTEST.
+func (w *window) hitTest(x, y int) uintptr {
+	if w.config.Mode != Windowed {
+		// Only windowed mode should allow resizing.
+		return windows.HTCLIENT
+	}
+	// Check for resize handle before system actions; otherwise it can be impossible to
+	// resize a custom-decorations window when the system move area is flush with the
+	// edge of the window.
+	top := y <= w.borderSize.Y
+	bottom := y >= w.config.Size.Y-w.borderSize.Y
+	left := x <= w.borderSize.X
+	right := x >= w.config.Size.X-w.borderSize.X
+	switch {
+	case top && left:
+		return windows.HTTOPLEFT
+	case top && right:
+		return windows.HTTOPRIGHT
+	case bottom && left:
+		return windows.HTBOTTOMLEFT
+	case bottom && right:
+		return windows.HTBOTTOMRIGHT
+	case top:
+		return windows.HTTOP
+	case bottom:
+		return windows.HTBOTTOM
+	case left:
+		return windows.HTLEFT
+	case right:
+		return windows.HTRIGHT
+	}
+	p := f32.Pt(float32(x), float32(y))
+	if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove {
+		return windows.HTCAPTION
+	}
+	return windows.HTCLIENT
+}
+
+func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
+	if !w.config.Focused {
+		windows.SetFocus(w.hwnd)
+	}
+
+	var kind pointer.Kind
+	if press {
+		kind = pointer.Press
+		if w.pointerBtns == 0 {
+			windows.SetCapture(w.hwnd)
+		}
+		w.pointerBtns |= btn
+	} else {
+		kind = pointer.Release
+		w.pointerBtns &^= btn
+		if w.pointerBtns == 0 {
+			windows.ReleaseCapture()
+		}
+	}
+	x, y := coordsFromlParam(lParam)
+	p := f32.Point{X: float32(x), Y: float32(y)}
+	w.ProcessEvent(pointer.Event{
+		Kind:      kind,
+		Source:    pointer.Mouse,
+		Position:  p,
+		Buttons:   w.pointerBtns,
+		Time:      windows.GetMessageTime(),
+		Modifiers: kmods,
+	})
+}
+
+func coordsFromlParam(lParam uintptr) (int, int) {
+	x := int(int16(lParam & 0xffff))
+	y := int(int16((lParam >> 16) & 0xffff))
+	return x, y
+}
+
+func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
+	x, y := coordsFromlParam(lParam)
+	// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
+	// to other mouse events.
+	np := windows.Point{X: int32(x), Y: int32(y)}
+	windows.ScreenToClient(w.hwnd, &np)
+	p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
+	dist := float32(int16(wParam >> 16))
+	var sp f32.Point
+	if horizontal {
+		sp.X = dist
+	} else {
+		// support horizontal scroll (shift + mousewheel)
+		if kmods == key.ModShift {
+			sp.X = -dist
+		} else {
+			sp.Y = -dist
+		}
+	}
+	w.ProcessEvent(pointer.Event{
+		Kind:      pointer.Scroll,
+		Source:    pointer.Mouse,
+		Position:  p,
+		Buttons:   w.pointerBtns,
+		Scroll:    sp,
+		Modifiers: kmods,
+		Time:      windows.GetMessageTime(),
+	})
+}
+
+// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
+func (w *window) runLoop() {
+	msg := new(windows.Msg)
+loop:
+	for {
+		anim := w.animating
+		p := windows.GetWindowPlacement(w.hwnd)
+		if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
+			w.draw(false)
+			continue
+		}
+		switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
+		case -1:
+			panic(errors.New("GetMessage failed"))
+		case 0:
+			// WM_QUIT received.
+			break loop
+		}
+		windows.TranslateMessage(msg)
+		windows.DispatchMessage(msg)
+	}
+}
+
+func (w *window) EditorStateChanged(old, new editorState) {
+	imc := windows.ImmGetContext(w.hwnd)
+	if imc == 0 {
+		return
+	}
+	defer windows.ImmReleaseContext(w.hwnd, imc)
+	if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
+		windows.ImmNotifyIME(imc, windows.NI_COMPOSITIONSTR, windows.CPS_CANCEL, 0)
+	}
+}
+
+func (w *window) SetAnimating(anim bool) {
+	w.animating = anim
+}
+
+func (w *window) ProcessEvent(e event.Event) {
+	w.w.ProcessEvent(e)
+	w.loop.FlushEvents()
+}
+
+func (w *window) Event() event.Event {
+	return w.loop.Event()
+}
+
+func (w *window) Invalidate() {
+	w.loop.Invalidate()
+}
+
+func (w *window) Run(f func()) {
+	w.loop.Run(f)
+}
+
+func (w *window) Frame(frame *op.Ops) {
+	w.loop.Frame(frame)
+}
+
+func (w *window) wakeup() {
+	if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
+		panic(err)
+	}
+}
+
+func (w *window) draw(sync bool) {
+	if w.config.Size.X == 0 || w.config.Size.Y == 0 {
+		return
+	}
+	dpi := windows.GetWindowDPI(w.hwnd)
+	cfg := configForDPI(dpi)
+	w.ProcessEvent(frameEvent{
+		FrameEvent: FrameEvent{
+			Now:    time.Now(),
+			Size:   w.config.Size,
+			Metric: cfg,
+		},
+		Sync: sync,
+	})
+}
+
+func (w *window) NewContext() (context, error) {
+	sort.Slice(drivers, func(i, j int) bool {
+		return drivers[i].priority < drivers[j].priority
+	})
+	var errs []string
+	for _, b := range drivers {
+		ctx, err := b.initializer(w)
+		if err == nil {
+			return ctx, nil
+		}
+		errs = append(errs, err.Error())
+	}
+	if len(errs) > 0 {
+		return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", "))
+	}
+	return nil, errors.New("NewContext: no available GPU drivers")
+}
+
+func (w *window) ReadClipboard() {
+	w.readClipboard()
+}
+
+func (w *window) readClipboard() error {
+	if err := windows.OpenClipboard(w.hwnd); err != nil {
+		return err
+	}
+	defer windows.CloseClipboard()
+	mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT)
+	if err != nil {
+		return err
+	}
+	ptr, err := windows.GlobalLock(mem)
+	if err != nil {
+		return err
+	}
+	defer windows.GlobalUnlock(mem)
+	content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
+	w.ProcessEvent(transfer.DataEvent{
+		Type: "application/text",
+		Open: func() io.ReadCloser {
+			return io.NopCloser(strings.NewReader(content))
+		},
+	})
+	return nil
+}
+
+func (w *window) Configure(options []Option) {
+	dpi := windows.GetSystemDPI()
+	metric := configForDPI(dpi)
+	cnf := w.config
+	cnf.apply(metric, options)
+	w.config.Title = cnf.Title
+	w.config.Decorated = cnf.Decorated
+	w.config.MinSize = cnf.MinSize
+	w.config.MaxSize = cnf.MaxSize
+	windows.SetWindowText(w.hwnd, cnf.Title)
+
+	style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
+	var showMode int32
+	var x, y, width, height int32
+	swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
+	winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
+	style &^= winStyle
+	switch cnf.Mode {
+	case Minimized:
+		style |= winStyle
+		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
+		showMode = windows.SW_SHOWMINIMIZED
+
+	case Maximized:
+		style |= winStyle
+		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
+		showMode = windows.SW_SHOWMAXIMIZED
+
+	case Windowed:
+		style |= winStyle
+		showMode = windows.SW_SHOWNORMAL
+		// Get target for client area size.
+		width = int32(cnf.Size.X)
+		height = int32(cnf.Size.Y)
+		// Get the current window size and position.
+		wr := windows.GetWindowRect(w.hwnd)
+		x = wr.Left
+		y = wr.Top
+		if cnf.Decorated {
+			// Compute client size and position. Note that the client size is
+			// equal to the window size when we are in control of decorations.
+			r := windows.Rect{
+				Right:  width,
+				Bottom: height,
+			}
+			windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
+			width = r.Right - r.Left
+			height = r.Bottom - r.Top
+		} else {
+			// Enable drop shadows when we draw decorations.
+			windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
+		}
+
+	case Fullscreen:
+		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
+		showMode = windows.SW_SHOWMAXIMIZED
+	}
+	windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
+	windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
+	windows.ShowWindow(w.hwnd, showMode)
+}
+
+func (w *window) WriteClipboard(mime string, s []byte) {
+	w.writeClipboard(string(s))
+}
+
+func (w *window) writeClipboard(s string) error {
+	if err := windows.OpenClipboard(w.hwnd); err != nil {
+		return err
+	}
+	defer windows.CloseClipboard()
+	if err := windows.EmptyClipboard(); err != nil {
+		return err
+	}
+	u16, err := gowindows.UTF16FromString(s)
+	if err != nil {
+		return err
+	}
+	n := len(u16) * int(unsafe.Sizeof(u16[0]))
+	mem, err := windows.GlobalAlloc(n)
+	if err != nil {
+		return err
+	}
+	ptr, err := windows.GlobalLock(mem)
+	if err != nil {
+		windows.GlobalFree(mem)
+		return err
+	}
+	u16v := unsafe.Slice((*uint16)(ptr), len(u16))
+	copy(u16v, u16)
+	windows.GlobalUnlock(mem)
+	if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil {
+		windows.GlobalFree(mem)
+		return err
+	}
+	return nil
+}
+
+func (w *window) SetCursor(cursor pointer.Cursor) {
+	c, err := loadCursor(cursor)
+	if err != nil {
+		c = resources.cursor
+	}
+	w.cursor = c
+	if w.cursorIn {
+		windows.SetCursor(w.cursor)
+	}
+}
+
+// windowsCursor contains mapping from pointer.Cursor to an IDC.
+var windowsCursor = [...]uint16{
+	pointer.CursorDefault:                  windows.IDC_ARROW,
+	pointer.CursorNone:                     0,
+	pointer.CursorText:                     windows.IDC_IBEAM,
+	pointer.CursorVerticalText:             windows.IDC_IBEAM,
+	pointer.CursorPointer:                  windows.IDC_HAND,
+	pointer.CursorCrosshair:                windows.IDC_CROSS,
+	pointer.CursorAllScroll:                windows.IDC_SIZEALL,
+	pointer.CursorColResize:                windows.IDC_SIZEWE,
+	pointer.CursorRowResize:                windows.IDC_SIZENS,
+	pointer.CursorGrab:                     windows.IDC_SIZEALL,
+	pointer.CursorGrabbing:                 windows.IDC_SIZEALL,
+	pointer.CursorNotAllowed:               windows.IDC_NO,
+	pointer.CursorWait:                     windows.IDC_WAIT,
+	pointer.CursorProgress:                 windows.IDC_APPSTARTING,
+	pointer.CursorNorthWestResize:          windows.IDC_SIZENWSE,
+	pointer.CursorNorthEastResize:          windows.IDC_SIZENESW,
+	pointer.CursorSouthWestResize:          windows.IDC_SIZENESW,
+	pointer.CursorSouthEastResize:          windows.IDC_SIZENWSE,
+	pointer.CursorNorthSouthResize:         windows.IDC_SIZENS,
+	pointer.CursorEastWestResize:           windows.IDC_SIZEWE,
+	pointer.CursorWestResize:               windows.IDC_SIZEWE,
+	pointer.CursorEastResize:               windows.IDC_SIZEWE,
+	pointer.CursorNorthResize:              windows.IDC_SIZENS,
+	pointer.CursorSouthResize:              windows.IDC_SIZENS,
+	pointer.CursorNorthEastSouthWestResize: windows.IDC_SIZENESW,
+	pointer.CursorNorthWestSouthEastResize: windows.IDC_SIZENWSE,
+}
+
+func loadCursor(cursor pointer.Cursor) (syscall.Handle, error) {
+	switch cursor {
+	case pointer.CursorDefault:
+		return resources.cursor, nil
+	case pointer.CursorNone:
+		return 0, nil
+	default:
+		return windows.LoadCursor(windowsCursor[cursor])
+	}
+}
+
+func (w *window) ShowTextInput(show bool) {}
+
+func (w *window) SetInputHint(_ key.InputHint) {}
+
+func (w *window) HDC() syscall.Handle {
+	return w.hdc
+}
+
+func (w *window) HWND() (syscall.Handle, int, int) {
+	return w.hwnd, w.config.Size.X, w.config.Size.Y
+}
+
+func (w *window) Perform(acts system.Action) {
+	walkActions(acts, func(a system.Action) {
+		switch a {
+		case system.ActionCenter:
+			if w.config.Mode != Windowed {
+				break
+			}
+			r := windows.GetWindowRect(w.hwnd)
+			dx := r.Right - r.Left
+			dy := r.Bottom - r.Top
+			// Calculate center position on current monitor.
+			mi := windows.GetMonitorInfo(w.hwnd).Monitor
+			x := (mi.Right - mi.Left - dx) / 2
+			y := (mi.Bottom - mi.Top - dy) / 2
+			windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED)
+		case system.ActionRaise:
+			w.raise()
+		case system.ActionClose:
+			windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
+		}
+	})
+}
+
+func (w *window) raise() {
+	windows.SetForegroundWindow(w.hwnd)
+	windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0,
+		windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
+}
+
+func convertKeyCode(code uintptr) (key.Name, bool) {
+	if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
+		return key.Name(rune(code)), true
+	}
+	var r key.Name
+
+	switch code {
+	case windows.VK_ESCAPE:
+		r = key.NameEscape
+	case windows.VK_LEFT:
+		r = key.NameLeftArrow
+	case windows.VK_RIGHT:
+		r = key.NameRightArrow
+	case windows.VK_RETURN:
+		r = key.NameReturn
+	case windows.VK_UP:
+		r = key.NameUpArrow
+	case windows.VK_DOWN:
+		r = key.NameDownArrow
+	case windows.VK_HOME:
+		r = key.NameHome
+	case windows.VK_END:
+		r = key.NameEnd
+	case windows.VK_BACK:
+		r = key.NameDeleteBackward
+	case windows.VK_DELETE:
+		r = key.NameDeleteForward
+	case windows.VK_PRIOR:
+		r = key.NamePageUp
+	case windows.VK_NEXT:
+		r = key.NamePageDown
+	case windows.VK_F1:
+		r = key.NameF1
+	case windows.VK_F2:
+		r = key.NameF2
+	case windows.VK_F3:
+		r = key.NameF3
+	case windows.VK_F4:
+		r = key.NameF4
+	case windows.VK_F5:
+		r = key.NameF5
+	case windows.VK_F6:
+		r = key.NameF6
+	case windows.VK_F7:
+		r = key.NameF7
+	case windows.VK_F8:
+		r = key.NameF8
+	case windows.VK_F9:
+		r = key.NameF9
+	case windows.VK_F10:
+		r = key.NameF10
+	case windows.VK_F11:
+		r = key.NameF11
+	case windows.VK_F12:
+		r = key.NameF12
+	case windows.VK_TAB:
+		r = key.NameTab
+	case windows.VK_SPACE:
+		r = key.NameSpace
+	case windows.VK_OEM_1:
+		r = ";"
+	case windows.VK_OEM_PLUS:
+		r = "+"
+	case windows.VK_OEM_COMMA:
+		r = ","
+	case windows.VK_OEM_MINUS:
+		r = "-"
+	case windows.VK_OEM_PERIOD:
+		r = "."
+	case windows.VK_OEM_2:
+		r = "/"
+	case windows.VK_OEM_3:
+		r = "`"
+	case windows.VK_OEM_4:
+		r = "["
+	case windows.VK_OEM_5, windows.VK_OEM_102:
+		r = "\\"
+	case windows.VK_OEM_6:
+		r = "]"
+	case windows.VK_OEM_7:
+		r = "'"
+	case windows.VK_CONTROL:
+		r = key.NameCtrl
+	case windows.VK_SHIFT:
+		r = key.NameShift
+	case windows.VK_MENU:
+		r = key.NameAlt
+	case windows.VK_LWIN, windows.VK_RWIN:
+		r = key.NameSuper
+	default:
+		return "", false
+	}
+	return r, true
+}
+
+func configForDPI(dpi int) unit.Metric {
+	const inchPrDp = 1.0 / 96.0
+	ppdp := float32(dpi) * inchPrDp
+	return unit.Metric{
+		PxPerDp: ppdp,
+		PxPerSp: ppdp,
+	}
+}
+
+func (Win32ViewEvent) implementsViewEvent() {}
+func (Win32ViewEvent) ImplementsEvent()     {}
+func (w Win32ViewEvent) Valid() bool {
+	return w != (Win32ViewEvent{})
+}

+ 929 - 0
vendor/gioui.org/app/os_x11.go

@@ -0,0 +1,929 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build ((linux && !android) || freebsd || openbsd) && !nox11
+// +build linux,!android freebsd openbsd
+// +build !nox11
+
+package app
+
+/*
+#cgo freebsd openbsd CFLAGS: -I/usr/X11R6/include -I/usr/local/include
+#cgo freebsd openbsd LDFLAGS: -L/usr/X11R6/lib -L/usr/local/lib
+#cgo freebsd openbsd LDFLAGS: -lX11 -lxkbcommon -lxkbcommon-x11 -lX11-xcb -lXcursor -lXfixes
+#cgo linux pkg-config: x11 xkbcommon xkbcommon-x11 x11-xcb xcursor xfixes
+
+#include <stdlib.h>
+#include <locale.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/Xresource.h>
+#include <X11/XKBlib.h>
+#include <X11/Xlib-xcb.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/Xcursor/Xcursor.h>
+#include <xkbcommon/xkbcommon-x11.h>
+
+*/
+import "C"
+import (
+	"errors"
+	"fmt"
+	"image"
+	"io"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+	"unsafe"
+
+	"gioui.org/f32"
+	"gioui.org/io/event"
+	"gioui.org/io/key"
+	"gioui.org/io/pointer"
+	"gioui.org/io/system"
+	"gioui.org/io/transfer"
+	"gioui.org/op"
+	"gioui.org/unit"
+
+	syscall "golang.org/x/sys/unix"
+
+	"gioui.org/app/internal/xkb"
+)
+
+const (
+	_NET_WM_STATE_REMOVE = 0
+	_NET_WM_STATE_ADD    = 1
+)
+
+type x11Window struct {
+	w            *callbacks
+	x            *C.Display
+	xkb          *xkb.Context
+	xkbEventBase C.int
+	xw           C.Window
+
+	atoms struct {
+		// "UTF8_STRING".
+		utf8string C.Atom
+		// "text/plain;charset=utf-8".
+		plaintext C.Atom
+		// "TARGETS"
+		targets C.Atom
+		// "CLIPBOARD".
+		clipboard C.Atom
+		// "PRIMARY".
+		primary C.Atom
+		// "CLIPBOARD_CONTENT", the clipboard destination property.
+		clipboardContent C.Atom
+		// "WM_DELETE_WINDOW"
+		evDelWindow C.Atom
+		// "ATOM"
+		atom C.Atom
+		// "GTK_TEXT_BUFFER_CONTENTS"
+		gtk_text_buffer_contents C.Atom
+		// "_NET_WM_NAME"
+		wmName C.Atom
+		// "_NET_WM_STATE"
+		wmState C.Atom
+		// "_NET_WM_STATE_FULLSCREEN"
+		wmStateFullscreen C.Atom
+		// "_NET_ACTIVE_WINDOW"
+		wmActiveWindow C.Atom
+		// _NET_WM_STATE_MAXIMIZED_HORZ
+		wmStateMaximizedHorz C.Atom
+		// _NET_WM_STATE_MAXIMIZED_VERT
+		wmStateMaximizedVert C.Atom
+	}
+	metric unit.Metric
+	notify struct {
+		read, write int
+	}
+
+	animating bool
+
+	pointerBtns pointer.Buttons
+
+	clipboard struct {
+		content []byte
+	}
+	cursor pointer.Cursor
+	config Config
+
+	wakeups chan struct{}
+	handler x11EventHandler
+	buf     [100]byte
+}
+
+var (
+	newX11EGLContext    func(w *x11Window) (context, error)
+	newX11VulkanContext func(w *x11Window) (context, error)
+)
+
+// X11 and Vulkan doesn't work reliably on NVIDIA systems.
+// See https://gioui.org/issue/347.
+const vulkanBuggy = true
+
+func (w *x11Window) NewContext() (context, error) {
+	var firstErr error
+	if f := newX11VulkanContext; f != nil && !vulkanBuggy {
+		c, err := f(w)
+		if err == nil {
+			return c, nil
+		}
+		firstErr = err
+	}
+	if f := newX11EGLContext; f != nil {
+		c, err := f(w)
+		if err == nil {
+			return c, nil
+		}
+		firstErr = err
+	}
+	if firstErr != nil {
+		return nil, firstErr
+	}
+	return nil, errors.New("x11: no available GPU backends")
+}
+
+func (w *x11Window) SetAnimating(anim bool) {
+	w.animating = anim
+}
+
+func (w *x11Window) ReadClipboard() {
+	C.XDeleteProperty(w.x, w.xw, w.atoms.clipboardContent)
+	C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
+}
+
+func (w *x11Window) WriteClipboard(mime string, s []byte) {
+	w.clipboard.content = s
+	C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
+	C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
+}
+
+func (w *x11Window) Configure(options []Option) {
+	var shints C.XSizeHints
+	prev := w.config
+	cnf := w.config
+	cnf.apply(w.metric, options)
+	// Decorations are never disabled.
+	cnf.Decorated = true
+
+	switch cnf.Mode {
+	case Fullscreen:
+		switch prev.Mode {
+		case Fullscreen:
+		case Minimized:
+			w.raise()
+			fallthrough
+		default:
+			w.config.Mode = Fullscreen
+			w.sendWMStateEvent(_NET_WM_STATE_ADD, w.atoms.wmStateFullscreen, 0)
+		}
+	case Minimized:
+		switch prev.Mode {
+		case Minimized, Fullscreen:
+		default:
+			w.config.Mode = Minimized
+			screen := C.XDefaultScreen(w.x)
+			C.XIconifyWindow(w.x, w.xw, screen)
+		}
+	case Maximized:
+		switch prev.Mode {
+		case Fullscreen:
+		case Minimized:
+			w.raise()
+			fallthrough
+		default:
+			w.config.Mode = Maximized
+			w.sendWMStateEvent(_NET_WM_STATE_ADD, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
+			w.setTitle(prev, cnf)
+		}
+	case Windowed:
+		switch prev.Mode {
+		case Fullscreen:
+			w.config.Mode = Windowed
+			w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateFullscreen, 0)
+			C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
+		case Minimized:
+			w.config.Mode = Windowed
+			w.raise()
+		case Maximized:
+			w.config.Mode = Windowed
+			w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
+		}
+		w.setTitle(prev, cnf)
+		if prev.Size != cnf.Size {
+			w.config.Size = cnf.Size
+			C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
+		}
+		if prev.MinSize != cnf.MinSize {
+			w.config.MinSize = cnf.MinSize
+			shints.min_width = C.int(cnf.MinSize.X)
+			shints.min_height = C.int(cnf.MinSize.Y)
+			shints.flags = C.PMinSize
+		}
+		if prev.MaxSize != cnf.MaxSize {
+			w.config.MaxSize = cnf.MaxSize
+			shints.max_width = C.int(cnf.MaxSize.X)
+			shints.max_height = C.int(cnf.MaxSize.Y)
+			shints.flags = shints.flags | C.PMaxSize
+		}
+		if shints.flags != 0 {
+			C.XSetWMNormalHints(w.x, w.xw, &shints)
+		}
+	}
+	if cnf.Decorated != prev.Decorated {
+		w.config.Decorated = cnf.Decorated
+	}
+	w.ProcessEvent(ConfigEvent{Config: w.config})
+}
+
+func (w *x11Window) setTitle(prev, cnf Config) {
+	if prev.Title != cnf.Title {
+		title := cnf.Title
+		ctitle := C.CString(title)
+		defer C.free(unsafe.Pointer(ctitle))
+		C.XStoreName(w.x, w.xw, ctitle)
+		// set _NET_WM_NAME as well for UTF-8 support in window title.
+		C.XSetTextProperty(w.x, w.xw,
+			&C.XTextProperty{
+				value:    (*C.uchar)(unsafe.Pointer(ctitle)),
+				encoding: w.atoms.utf8string,
+				format:   8,
+				nitems:   C.ulong(len(title)),
+			},
+			w.atoms.wmName)
+	}
+}
+
+func (w *x11Window) Perform(acts system.Action) {
+	walkActions(acts, func(a system.Action) {
+		switch a {
+		case system.ActionCenter:
+			w.center()
+		case system.ActionRaise:
+			w.raise()
+		}
+	})
+	if acts&system.ActionClose != 0 {
+		w.close()
+	}
+}
+
+func (w *x11Window) center() {
+	screen := C.XDefaultScreen(w.x)
+	width := C.XDisplayWidth(w.x, screen)
+	height := C.XDisplayHeight(w.x, screen)
+
+	var attrs C.XWindowAttributes
+	C.XGetWindowAttributes(w.x, w.xw, &attrs)
+	width -= attrs.border_width
+	height -= attrs.border_width
+
+	sz := w.config.Size
+	x := (int(width) - sz.X) / 2
+	y := (int(height) - sz.Y) / 2
+
+	C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y))
+}
+
+func (w *x11Window) raise() {
+	var xev C.XEvent
+	ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
+	*ev = C.XClientMessageEvent{
+		_type:        C.ClientMessage,
+		display:      w.x,
+		window:       w.xw,
+		message_type: w.atoms.wmActiveWindow,
+		format:       32,
+	}
+	C.XSendEvent(
+		w.x,
+		C.XDefaultRootWindow(w.x), // MUST be the root window
+		C.False,
+		C.SubstructureNotifyMask|C.SubstructureRedirectMask,
+		&xev,
+	)
+	C.XMapRaised(w.display(), w.xw)
+}
+
+func (w *x11Window) SetCursor(cursor pointer.Cursor) {
+	if cursor == pointer.CursorNone {
+		w.cursor = cursor
+		C.XFixesHideCursor(w.x, w.xw)
+		return
+	}
+
+	xcursor := xCursor[cursor]
+	cname := C.CString(xcursor)
+	defer C.free(unsafe.Pointer(cname))
+	c := C.XcursorLibraryLoadCursor(w.x, cname)
+	if c == 0 {
+		cursor = pointer.CursorDefault
+	}
+	w.cursor = cursor
+	// If c if null (i.e. cursor was not found),
+	// XDefineCursor will use the default cursor.
+	C.XDefineCursor(w.x, w.xw, c)
+}
+
+func (w *x11Window) ShowTextInput(show bool) {}
+
+func (w *x11Window) SetInputHint(_ key.InputHint) {}
+
+func (w *x11Window) EditorStateChanged(old, new editorState) {}
+
+// close the window.
+func (w *x11Window) close() {
+	var xev C.XEvent
+	ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
+	*ev = C.XClientMessageEvent{
+		_type:        C.ClientMessage,
+		display:      w.x,
+		window:       w.xw,
+		message_type: w.atom("WM_PROTOCOLS", true),
+		format:       32,
+	}
+	arr := (*[5]C.long)(unsafe.Pointer(&ev.data))
+	arr[0] = C.long(w.atoms.evDelWindow)
+	arr[1] = C.CurrentTime
+	C.XSendEvent(w.x, w.xw, C.False, C.NoEventMask, &xev)
+}
+
+// action is one of _NET_WM_STATE_REMOVE, _NET_WM_STATE_ADD.
+func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
+	var xev C.XEvent
+	ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
+	*ev = C.XClientMessageEvent{
+		_type:        C.ClientMessage,
+		display:      w.x,
+		window:       w.xw,
+		message_type: w.atoms.wmState,
+		format:       32,
+	}
+	data := (*[5]C.long)(unsafe.Pointer(&ev.data))
+	data[0] = C.long(action)
+	data[1] = C.long(atom1)
+	data[2] = C.long(atom2)
+	data[3] = 1 // application
+
+	C.XSendEvent(
+		w.x,
+		C.XDefaultRootWindow(w.x), // MUST be the root window
+		C.False,
+		C.SubstructureNotifyMask|C.SubstructureRedirectMask,
+		&xev,
+	)
+}
+
+var x11OneByte = make([]byte, 1)
+
+func (w *x11Window) ProcessEvent(e event.Event) {
+	w.w.ProcessEvent(e)
+}
+
+func (w *x11Window) shutdown(err error) {
+	w.ProcessEvent(X11ViewEvent{})
+	w.ProcessEvent(DestroyEvent{Err: err})
+	w.destroy()
+}
+
+func (w *x11Window) Event() event.Event {
+	for {
+		evt, ok := w.w.nextEvent()
+		if !ok {
+			w.dispatch()
+			continue
+		}
+		return evt
+	}
+}
+
+func (w *x11Window) Run(f func()) {
+	f()
+}
+
+func (w *x11Window) Frame(frame *op.Ops) {
+	w.w.ProcessFrame(frame, nil)
+}
+
+func (w *x11Window) Invalidate() {
+	select {
+	case w.wakeups <- struct{}{}:
+	default:
+	}
+	if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
+		panic(fmt.Errorf("failed to write to pipe: %v", err))
+	}
+}
+
+func (w *x11Window) display() *C.Display {
+	return w.x
+}
+
+func (w *x11Window) window() (C.Window, int, int) {
+	return w.xw, w.config.Size.X, w.config.Size.Y
+}
+
+func (w *x11Window) dispatch() {
+	if w.x == nil {
+		// Only Invalidate can wake us up.
+		<-w.wakeups
+		w.w.Invalidate()
+		return
+	}
+
+	select {
+	case <-w.wakeups:
+		w.w.Invalidate()
+	default:
+	}
+
+	xfd := C.XConnectionNumber(w.x)
+
+	// Poll for events and notifications.
+	pollfds := []syscall.PollFd{
+		{Fd: int32(xfd), Events: syscall.POLLIN | syscall.POLLERR},
+		{Fd: int32(w.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
+	}
+	xEvents := &pollfds[0].Revents
+	// Plenty of room for a backlog of notifications.
+
+	var syn, anim bool
+	// Check for pending draw events before checking animation or blocking.
+	// This fixes an issue on Xephyr where on startup XPending() > 0 but
+	// poll will still block. This also prevents no-op calls to poll.
+	syn = w.handler.handleEvents()
+	if w.x == nil {
+		// handleEvents received a close request and destroyed the window.
+		return
+	}
+	if !syn {
+		anim = w.animating
+		if !anim {
+			// Clear poll events.
+			*xEvents = 0
+			// Wait for X event or gio notification.
+			if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
+				panic(fmt.Errorf("x11 loop: poll failed: %w", err))
+			}
+			switch {
+			case *xEvents&syscall.POLLIN != 0:
+				syn = w.handler.handleEvents()
+				if w.x == nil {
+					return
+				}
+			case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
+			}
+		}
+	}
+	// Clear notifications.
+	for {
+		_, err := syscall.Read(w.notify.read, w.buf[:])
+		if err == syscall.EAGAIN {
+			break
+		}
+		if err != nil {
+			panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
+		}
+	}
+	if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
+		w.ProcessEvent(frameEvent{
+			FrameEvent: FrameEvent{
+				Now:    time.Now(),
+				Size:   w.config.Size,
+				Metric: w.metric,
+			},
+			Sync: syn,
+		})
+	}
+}
+
+func (w *x11Window) destroy() {
+	if w.notify.write != 0 {
+		syscall.Close(w.notify.write)
+		w.notify.write = 0
+	}
+	if w.notify.read != 0 {
+		syscall.Close(w.notify.read)
+		w.notify.read = 0
+	}
+	if w.xkb != nil {
+		w.xkb.Destroy()
+		w.xkb = nil
+	}
+	C.XDestroyWindow(w.x, w.xw)
+	C.XCloseDisplay(w.x)
+	w.x = nil
+}
+
+// atom is a wrapper around XInternAtom. Callers should cache the result
+// in order to limit round-trips to the X server.
+func (w *x11Window) atom(name string, onlyIfExists bool) C.Atom {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+	flag := C.Bool(C.False)
+	if onlyIfExists {
+		flag = C.True
+	}
+	return C.XInternAtom(w.x, cname, flag)
+}
+
+// x11EventHandler wraps static variables for the main event loop.
+// Its sole purpose is to prevent heap allocation and reduce clutter
+// in x11window.loop.
+type x11EventHandler struct {
+	w    *x11Window
+	text []byte
+	xev  *C.XEvent
+}
+
+// handleEvents returns true if the window needs to be redrawn.
+func (h *x11EventHandler) handleEvents() bool {
+	w := h.w
+	xev := h.xev
+	redraw := false
+	for C.XPending(w.x) != 0 {
+		C.XNextEvent(w.x, xev)
+		if C.XFilterEvent(xev, C.None) == C.True {
+			continue
+		}
+		switch _type := (*C.XAnyEvent)(unsafe.Pointer(xev))._type; _type {
+		case h.w.xkbEventBase:
+			xkbEvent := (*C.XkbAnyEvent)(unsafe.Pointer(xev))
+			switch xkbEvent.xkb_type {
+			case C.XkbNewKeyboardNotify, C.XkbMapNotify:
+				if err := h.w.updateXkbKeymap(); err != nil {
+					panic(err)
+				}
+			case C.XkbStateNotify:
+				state := (*C.XkbStateNotifyEvent)(unsafe.Pointer(xev))
+				h.w.xkb.UpdateMask(uint32(state.base_mods), uint32(state.latched_mods), uint32(state.locked_mods),
+					uint32(state.base_group), uint32(state.latched_group), uint32(state.locked_group))
+			}
+		case C.KeyPress, C.KeyRelease:
+			ks := key.Press
+			if _type == C.KeyRelease {
+				ks = key.Release
+			}
+			kevt := (*C.XKeyPressedEvent)(unsafe.Pointer(xev))
+			for _, e := range h.w.xkb.DispatchKey(uint32(kevt.keycode), ks) {
+				if ee, ok := e.(key.EditEvent); ok {
+					// There's no support for IME yet.
+					w.w.EditorInsert(ee.Text)
+				} else {
+					w.ProcessEvent(e)
+				}
+			}
+		case C.ButtonPress, C.ButtonRelease:
+			bevt := (*C.XButtonEvent)(unsafe.Pointer(xev))
+			ev := pointer.Event{
+				Kind:   pointer.Press,
+				Source: pointer.Mouse,
+				Position: f32.Point{
+					X: float32(bevt.x),
+					Y: float32(bevt.y),
+				},
+				Time:      time.Duration(bevt.time) * time.Millisecond,
+				Modifiers: w.xkb.Modifiers(),
+			}
+			if bevt._type == C.ButtonRelease {
+				ev.Kind = pointer.Release
+			}
+			var btn pointer.Buttons
+			const scrollScale = 10
+			switch bevt.button {
+			case C.Button1:
+				btn = pointer.ButtonPrimary
+			case C.Button2:
+				btn = pointer.ButtonTertiary
+			case C.Button3:
+				btn = pointer.ButtonSecondary
+			case C.Button4:
+				ev.Kind = pointer.Scroll
+				// scroll up or left (if shift is pressed).
+				if ev.Modifiers == key.ModShift {
+					ev.Scroll.X = -scrollScale
+				} else {
+					ev.Scroll.Y = -scrollScale
+				}
+			case C.Button5:
+				// scroll down or right (if shift is pressed).
+				ev.Kind = pointer.Scroll
+				if ev.Modifiers == key.ModShift {
+					ev.Scroll.X = +scrollScale
+				} else {
+					ev.Scroll.Y = +scrollScale
+				}
+			case 6:
+				// http://xahlee.info/linux/linux_x11_mouse_button_number.html
+				// scroll left.
+				ev.Kind = pointer.Scroll
+				ev.Scroll.X = -scrollScale * 2
+			case 7:
+				// scroll right
+				ev.Kind = pointer.Scroll
+				ev.Scroll.X = +scrollScale * 2
+			default:
+				continue
+			}
+			switch _type {
+			case C.ButtonPress:
+				w.pointerBtns |= btn
+			case C.ButtonRelease:
+				w.pointerBtns &^= btn
+			}
+			ev.Buttons = w.pointerBtns
+			w.ProcessEvent(ev)
+		case C.MotionNotify:
+			mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
+			w.ProcessEvent(pointer.Event{
+				Kind:    pointer.Move,
+				Source:  pointer.Mouse,
+				Buttons: w.pointerBtns,
+				Position: f32.Point{
+					X: float32(mevt.x),
+					Y: float32(mevt.y),
+				},
+				Time:      time.Duration(mevt.time) * time.Millisecond,
+				Modifiers: w.xkb.Modifiers(),
+			})
+		case C.Expose: // update
+			// redraw only on the last expose event
+			redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
+		case C.FocusIn:
+			w.config.Focused = true
+			w.ProcessEvent(ConfigEvent{Config: w.config})
+		case C.FocusOut:
+			w.config.Focused = false
+			w.ProcessEvent(ConfigEvent{Config: w.config})
+		case C.ConfigureNotify: // window configuration change
+			cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
+			if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
+				w.config.Size = sz
+				w.ProcessEvent(ConfigEvent{Config: w.config})
+			}
+			// redraw will be done by a later expose event
+		case C.SelectionNotify:
+			cevt := (*C.XSelectionEvent)(unsafe.Pointer(xev))
+			prop := w.atoms.clipboardContent
+			if cevt.property != prop {
+				break
+			}
+			if cevt.selection != w.atoms.clipboard {
+				break
+			}
+			var text C.XTextProperty
+			if st := C.XGetTextProperty(w.x, w.xw, &text, prop); st == 0 {
+				// Failed; ignore.
+				break
+			}
+			if text.format != 8 || text.encoding != w.atoms.utf8string {
+				// Ignore non-utf-8 encoded strings.
+				break
+			}
+			str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
+			w.ProcessEvent(transfer.DataEvent{
+				Type: "application/text",
+				Open: func() io.ReadCloser {
+					return io.NopCloser(strings.NewReader(str))
+				},
+			})
+		case C.SelectionRequest:
+			cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
+			if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
+				// Unsupported clipboard or obsolete requestor.
+				break
+			}
+			notify := func() {
+				var xev C.XEvent
+				ev := (*C.XSelectionEvent)(unsafe.Pointer(&xev))
+				*ev = C.XSelectionEvent{
+					_type:     C.SelectionNotify,
+					display:   cevt.display,
+					requestor: cevt.requestor,
+					selection: cevt.selection,
+					target:    cevt.target,
+					property:  cevt.property,
+					time:      cevt.time,
+				}
+				C.XSendEvent(w.x, cevt.requestor, 0, 0, &xev)
+			}
+			switch cevt.target {
+			case w.atoms.targets:
+				// The requestor wants the supported clipboard
+				// formats. First write the targets...
+				formats := [...]C.long{
+					C.long(w.atoms.targets),
+					C.long(w.atoms.utf8string),
+					C.long(w.atoms.plaintext),
+					// GTK clients need this.
+					C.long(w.atoms.gtk_text_buffer_contents),
+				}
+				C.XChangeProperty(w.x, cevt.requestor, cevt.property, w.atoms.atom,
+					32 /* bitwidth of formats */, C.PropModeReplace,
+					(*C.uchar)(unsafe.Pointer(&formats)), C.int(len(formats)),
+				)
+				// ...then notify the requestor.
+				notify()
+			case w.atoms.plaintext, w.atoms.utf8string, w.atoms.gtk_text_buffer_contents:
+				content := w.clipboard.content
+				var ptr *C.uchar
+				if len(content) > 0 {
+					ptr = (*C.uchar)(unsafe.Pointer(&content[0]))
+				}
+				C.XChangeProperty(w.x, cevt.requestor, cevt.property, cevt.target,
+					8 /* bitwidth */, C.PropModeReplace,
+					ptr, C.int(len(content)),
+				)
+				notify()
+			}
+		case C.ClientMessage: // extensions
+			cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
+			switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
+			case C.long(w.atoms.evDelWindow):
+				w.shutdown(nil)
+				return false
+			}
+		}
+	}
+	return redraw
+}
+
+var (
+	x11Threads sync.Once
+)
+
+func init() {
+	x11Driver = newX11Window
+}
+
+func newX11Window(gioWin *callbacks, options []Option) error {
+	var err error
+
+	pipe := make([]int, 2)
+	if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
+		return fmt.Errorf("NewX11Window: failed to create pipe: %w", err)
+	}
+
+	x11Threads.Do(func() {
+		if C.XInitThreads() == 0 {
+			err = errors.New("x11: threads init failed")
+		}
+		C.XrmInitialize()
+	})
+	if err != nil {
+		return err
+	}
+	dpy := C.XOpenDisplay(nil)
+	if dpy == nil {
+		return errors.New("x11: cannot connect to the X server")
+	}
+	var major, minor C.int = C.XkbMajorVersion, C.XkbMinorVersion
+	var xkbEventBase C.int
+	if C.XkbQueryExtension(dpy, nil, &xkbEventBase, nil, &major, &minor) != C.True {
+		C.XCloseDisplay(dpy)
+		return errors.New("x11: XkbQueryExtension failed")
+	}
+	const bits = C.uint(C.XkbNewKeyboardNotifyMask | C.XkbMapNotifyMask | C.XkbStateNotifyMask)
+	if C.XkbSelectEvents(dpy, C.XkbUseCoreKbd, bits, bits) != C.True {
+		C.XCloseDisplay(dpy)
+		return errors.New("x11: XkbSelectEvents failed")
+	}
+	xkb, err := xkb.New()
+	if err != nil {
+		C.XCloseDisplay(dpy)
+		return fmt.Errorf("x11: %v", err)
+	}
+
+	ppsp := x11DetectUIScale(dpy)
+	cfg := unit.Metric{PxPerDp: ppsp, PxPerSp: ppsp}
+	// Only use cnf for getting the window size.
+	var cnf Config
+	cnf.apply(cfg, options)
+
+	swa := C.XSetWindowAttributes{
+		event_mask: C.ExposureMask | C.FocusChangeMask | // update
+			C.KeyPressMask | C.KeyReleaseMask | // keyboard
+			C.ButtonPressMask | C.ButtonReleaseMask | // mouse clicks
+			C.PointerMotionMask | // mouse movement
+			C.StructureNotifyMask, // resize
+		background_pixmap: C.None,
+		override_redirect: C.False,
+	}
+	win := C.XCreateWindow(dpy, C.XDefaultRootWindow(dpy),
+		0, 0, C.uint(cnf.Size.X), C.uint(cnf.Size.Y),
+		0, C.CopyFromParent, C.InputOutput, nil,
+		C.CWEventMask|C.CWBackPixmap|C.CWOverrideRedirect, &swa)
+
+	w := &x11Window{
+		w: gioWin, x: dpy, xw: win,
+		metric:       cfg,
+		xkb:          xkb,
+		xkbEventBase: xkbEventBase,
+		wakeups:      make(chan struct{}, 1),
+		config:       Config{Size: cnf.Size},
+	}
+	w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
+	w.notify.read = pipe[0]
+	w.notify.write = pipe[1]
+	w.w.SetDriver(w)
+
+	if err := w.updateXkbKeymap(); err != nil {
+		w.destroy()
+		return err
+	}
+
+	var hints C.XWMHints
+	hints.input = C.True
+	hints.flags = C.InputHint
+	C.XSetWMHints(dpy, win, &hints)
+
+	name := C.CString(ID)
+	defer C.free(unsafe.Pointer(name))
+	wmhints := C.XClassHint{name, name}
+	C.XSetClassHint(dpy, win, &wmhints)
+
+	w.atoms.utf8string = w.atom("UTF8_STRING", false)
+	w.atoms.plaintext = w.atom("text/plain;charset=utf-8", false)
+	w.atoms.gtk_text_buffer_contents = w.atom("GTK_TEXT_BUFFER_CONTENTS", false)
+	w.atoms.evDelWindow = w.atom("WM_DELETE_WINDOW", false)
+	w.atoms.clipboard = w.atom("CLIPBOARD", false)
+	w.atoms.primary = w.atom("PRIMARY", false)
+	w.atoms.clipboardContent = w.atom("CLIPBOARD_CONTENT", false)
+	w.atoms.atom = w.atom("ATOM", false)
+	w.atoms.targets = w.atom("TARGETS", false)
+	w.atoms.wmName = w.atom("_NET_WM_NAME", false)
+	w.atoms.wmState = w.atom("_NET_WM_STATE", false)
+	w.atoms.wmStateFullscreen = w.atom("_NET_WM_STATE_FULLSCREEN", false)
+	w.atoms.wmActiveWindow = w.atom("_NET_ACTIVE_WINDOW", false)
+	w.atoms.wmStateMaximizedHorz = w.atom("_NET_WM_STATE_MAXIMIZED_HORZ", false)
+	w.atoms.wmStateMaximizedVert = w.atom("_NET_WM_STATE_MAXIMIZED_VERT", false)
+
+	// extensions
+	C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
+
+	// make the window visible on the screen
+	C.XMapWindow(dpy, win)
+	w.Configure(options)
+	w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
+	return nil
+}
+
+// detectUIScale reports the system UI scale, or 1.0 if it fails.
+func x11DetectUIScale(dpy *C.Display) float32 {
+	// default fixed DPI value used in most desktop UI toolkits
+	const defaultDesktopDPI = 96
+	var scale float32 = 1.0
+
+	// Get actual DPI from X resource Xft.dpi (set by GTK and Qt).
+	// This value is entirely based on user preferences and conflates both
+	// screen (UI) scaling and font scale.
+	rms := C.XResourceManagerString(dpy)
+	if rms != nil {
+		db := C.XrmGetStringDatabase(rms)
+		if db != nil {
+			var (
+				t *C.char
+				v C.XrmValue
+			)
+			if C.XrmGetResource(db, (*C.char)(unsafe.Pointer(&[]byte("Xft.dpi\x00")[0])),
+				(*C.char)(unsafe.Pointer(&[]byte("Xft.Dpi\x00")[0])), &t, &v) != C.False {
+				if t != nil && C.GoString(t) == "String" {
+					f, err := strconv.ParseFloat(C.GoString(v.addr), 32)
+					if err == nil {
+						scale = float32(f) / defaultDesktopDPI
+					}
+				}
+			}
+			C.XrmDestroyDatabase(db)
+		}
+	}
+
+	return scale
+}
+
+func (w *x11Window) updateXkbKeymap() error {
+	w.xkb.DestroyKeymapState()
+	ctx := (*C.struct_xkb_context)(unsafe.Pointer(w.xkb.Ctx))
+	xcb := C.XGetXCBConnection(w.x)
+	if xcb == nil {
+		return errors.New("x11: XGetXCBConnection failed")
+	}
+	xkbDevID := C.xkb_x11_get_core_keyboard_device_id(xcb)
+	if xkbDevID == -1 {
+		return errors.New("x11: xkb_x11_get_core_keyboard_device_id failed")
+	}
+	keymap := C.xkb_x11_keymap_new_from_device(ctx, xcb, xkbDevID, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
+	if keymap == nil {
+		return errors.New("x11: xkb_x11_keymap_new_from_device failed")
+	}
+	state := C.xkb_x11_state_new_from_device(keymap, xcb, xkbDevID)
+	if state == nil {
+		C.xkb_keymap_unref(keymap)
+		return errors.New("x11: xkb_x11_keymap_new_from_device failed")
+	}
+	w.xkb.SetKeymap(unsafe.Pointer(keymap), unsafe.Pointer(state))
+	return nil
+}

+ 30 - 0
vendor/gioui.org/app/runmain.go

@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build android || (darwin && ios)
+// +build android darwin,ios
+
+package app
+
+// Android only supports non-Java programs as c-shared libraries.
+// Unfortunately, Go does not run a program's main function in
+// library mode. To make Gio programs simpler and uniform, we'll
+// link to the main function here and call it from Java.
+
+import (
+	"sync"
+	_ "unsafe" // for go:linkname
+)
+
+//go:linkname mainMain main.main
+func mainMain()
+
+var runMainOnce sync.Once
+
+func runMain() {
+	runMainOnce.Do(func() {
+		// Indirect call, since the linker does not know the address of main when
+		// laying down this package.
+		fn := mainMain
+		fn()
+	})
+}

+ 13 - 0
vendor/gioui.org/app/system.go

@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+// DestroyEvent is the last event sent through
+// a window event channel.
+type DestroyEvent struct {
+	// Err is nil for normal window closures. If a
+	// window is prematurely closed, Err is the cause.
+	Err error
+}
+
+func (DestroyEvent) ImplementsEvent() {}

+ 218 - 0
vendor/gioui.org/app/vulkan.go

@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build (linux || freebsd) && !novulkan
+// +build linux freebsd
+// +build !novulkan
+
+package app
+
+import (
+	"errors"
+	"unsafe"
+
+	"gioui.org/gpu"
+	"gioui.org/internal/vk"
+)
+
+type vkContext struct {
+	physDev    vk.PhysicalDevice
+	inst       vk.Instance
+	dev        vk.Device
+	queueFam   int
+	queue      vk.Queue
+	acquireSem vk.Semaphore
+	presentSem vk.Semaphore
+	fence      vk.Fence
+
+	swchain    vk.Swapchain
+	imgs       []vk.Image
+	views      []vk.ImageView
+	fbos       []vk.Framebuffer
+	format     vk.Format
+	presentIdx int
+}
+
+func newVulkanContext(inst vk.Instance, surf vk.Surface) (*vkContext, error) {
+	physDev, qFam, err := vk.ChoosePhysicalDevice(inst, surf)
+	if err != nil {
+		return nil, err
+	}
+	dev, err := vk.CreateDeviceAndQueue(physDev, qFam, "VK_KHR_swapchain")
+	if err != nil {
+		return nil, err
+	}
+	acquireSem, err := vk.CreateSemaphore(dev)
+	if err != nil {
+		vk.DestroyDevice(dev)
+		return nil, err
+	}
+	presentSem, err := vk.CreateSemaphore(dev)
+	if err != nil {
+		vk.DestroySemaphore(dev, acquireSem)
+		vk.DestroyDevice(dev)
+		return nil, err
+	}
+	fence, err := vk.CreateFence(dev, vk.FENCE_CREATE_SIGNALED_BIT)
+	if err != nil {
+		vk.DestroySemaphore(dev, presentSem)
+		vk.DestroySemaphore(dev, acquireSem)
+		vk.DestroyDevice(dev)
+		return nil, err
+	}
+	c := &vkContext{
+		physDev:    physDev,
+		inst:       inst,
+		dev:        dev,
+		queueFam:   qFam,
+		queue:      vk.GetDeviceQueue(dev, qFam, 0),
+		acquireSem: acquireSem,
+		presentSem: presentSem,
+		fence:      fence,
+	}
+	return c, nil
+}
+
+func (c *vkContext) RenderTarget() (gpu.RenderTarget, error) {
+	vk.WaitForFences(c.dev, c.fence)
+	vk.ResetFences(c.dev, c.fence)
+
+	imgIdx, err := vk.AcquireNextImage(c.dev, c.swchain, c.acquireSem, 0)
+	if err := mapSurfaceErr(err); err != nil {
+		return nil, err
+	}
+	c.presentIdx = imgIdx
+	return gpu.VulkanRenderTarget{
+		WaitSem:     uint64(c.acquireSem),
+		SignalSem:   uint64(c.presentSem),
+		Fence:       uint64(c.fence),
+		Framebuffer: uint64(c.fbos[imgIdx]),
+		Image:       uint64(c.imgs[imgIdx]),
+	}, nil
+}
+
+func (c *vkContext) api() gpu.API {
+	return gpu.Vulkan{
+		PhysDevice:  unsafe.Pointer(c.physDev),
+		Device:      unsafe.Pointer(c.dev),
+		Format:      int(c.format),
+		QueueFamily: c.queueFam,
+		QueueIndex:  0,
+	}
+}
+
+func mapErr(err error) error {
+	var vkErr vk.Error
+	if errors.As(err, &vkErr) && vkErr == vk.ERROR_DEVICE_LOST {
+		return gpu.ErrDeviceLost
+	}
+	return err
+}
+
+func mapSurfaceErr(err error) error {
+	var vkErr vk.Error
+	if !errors.As(err, &vkErr) {
+		return err
+	}
+	switch {
+	case vkErr == vk.SUBOPTIMAL_KHR:
+		// Android reports VK_SUBOPTIMAL_KHR when presenting to a rotated
+		// swapchain (preTransform != currentTransform). However, we don't
+		// support transforming the output ourselves, so we'll live with it.
+		return nil
+	case vkErr == vk.ERROR_OUT_OF_DATE_KHR:
+		return errOutOfDate
+	case vkErr == vk.ERROR_SURFACE_LOST_KHR:
+		// Treating a lost surface as a lost device isn't accurate, but
+		// probably not worth optimizing.
+		return gpu.ErrDeviceLost
+	}
+	return mapErr(err)
+}
+
+func (c *vkContext) release() {
+	vk.DeviceWaitIdle(c.dev)
+
+	c.destroySwapchain()
+	vk.DestroyFence(c.dev, c.fence)
+	vk.DestroySemaphore(c.dev, c.acquireSem)
+	vk.DestroySemaphore(c.dev, c.presentSem)
+	vk.DestroyDevice(c.dev)
+	*c = vkContext{}
+}
+
+func (c *vkContext) present() error {
+	return mapSurfaceErr(vk.PresentQueue(c.queue, c.swchain, c.presentSem, c.presentIdx))
+}
+
+func (c *vkContext) destroyImageViews() {
+	for _, f := range c.fbos {
+		vk.DestroyFramebuffer(c.dev, f)
+	}
+	c.fbos = nil
+	for _, view := range c.views {
+		vk.DestroyImageView(c.dev, view)
+	}
+	c.views = nil
+}
+
+func (c *vkContext) destroySwapchain() {
+	vk.DeviceWaitIdle(c.dev)
+
+	c.destroyImageViews()
+	if c.swchain != 0 {
+		vk.DestroySwapchain(c.dev, c.swchain)
+		c.swchain = 0
+	}
+}
+
+func (c *vkContext) refresh(surf vk.Surface, width, height int) error {
+	vk.DeviceWaitIdle(c.dev)
+
+	c.destroyImageViews()
+	// Check whether size is valid. That's needed on X11, where ConfigureNotify
+	// is not always synchronized with the window extent.
+	caps, err := vk.GetPhysicalDeviceSurfaceCapabilities(c.physDev, surf)
+	if err != nil {
+		return err
+	}
+	minExt, maxExt := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps)
+	if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
+		return errOutOfDate
+	}
+	swchain, imgs, format, err := vk.CreateSwapchain(c.physDev, c.dev, surf, width, height, c.swchain)
+	if c.swchain != 0 {
+		vk.DestroySwapchain(c.dev, c.swchain)
+		c.swchain = 0
+	}
+	if err := mapSurfaceErr(err); err != nil {
+		return err
+	}
+	c.swchain = swchain
+	c.imgs = imgs
+	c.format = format
+	pass, err := vk.CreateRenderPass(
+		c.dev,
+		format,
+		vk.ATTACHMENT_LOAD_OP_CLEAR,
+		vk.IMAGE_LAYOUT_UNDEFINED,
+		vk.IMAGE_LAYOUT_PRESENT_SRC_KHR,
+		nil,
+	)
+	if err := mapErr(err); err != nil {
+		return err
+	}
+	defer vk.DestroyRenderPass(c.dev, pass)
+	for _, img := range imgs {
+		view, err := vk.CreateImageView(c.dev, img, format)
+		if err := mapErr(err); err != nil {
+			return err
+		}
+		c.views = append(c.views, view)
+		fbo, err := vk.CreateFramebuffer(c.dev, pass, view, width, height)
+		if err := mapErr(err); err != nil {
+			return err
+		}
+		c.fbos = append(c.fbos, fbo)
+	}
+	return nil
+}

+ 90 - 0
vendor/gioui.org/app/vulkan_android.go

@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build !novulkan
+// +build !novulkan
+
+package app
+
+import (
+	"unsafe"
+
+	"gioui.org/gpu"
+	"gioui.org/internal/vk"
+)
+
+type wlVkContext struct {
+	win  *window
+	inst vk.Instance
+	surf vk.Surface
+	ctx  *vkContext
+}
+
+func init() {
+	newAndroidVulkanContext = func(w *window) (context, error) {
+		inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_android_surface")
+		if err != nil {
+			return nil, err
+		}
+		window, _, _ := w.nativeWindow()
+		surf, err := vk.CreateAndroidSurface(inst, unsafe.Pointer(window))
+		if err != nil {
+			vk.DestroyInstance(inst)
+			return nil, err
+		}
+		ctx, err := newVulkanContext(inst, surf)
+		if err != nil {
+			vk.DestroySurface(inst, surf)
+			vk.DestroyInstance(inst)
+			return nil, err
+		}
+		c := &wlVkContext{
+			win:  w,
+			inst: inst,
+			surf: surf,
+			ctx:  ctx,
+		}
+		return c, nil
+	}
+}
+
+func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) {
+	return c.ctx.RenderTarget()
+}
+
+func (c *wlVkContext) API() gpu.API {
+	return c.ctx.api()
+}
+
+func (c *wlVkContext) Release() {
+	c.ctx.release()
+	if c.surf != 0 {
+		vk.DestroySurface(c.inst, c.surf)
+	}
+	vk.DestroyInstance(c.inst)
+	*c = wlVkContext{}
+}
+
+func (c *wlVkContext) Present() error {
+	return c.ctx.present()
+}
+
+func (c *wlVkContext) Lock() error {
+	return nil
+}
+
+func (c *wlVkContext) Unlock() {}
+
+func (c *wlVkContext) Refresh() error {
+	win, w, h := c.win.nativeWindow()
+	if c.surf != 0 {
+		c.ctx.destroySwapchain()
+		vk.DestroySurface(c.inst, c.surf)
+		c.surf = 0
+	}
+	surf, err := vk.CreateAndroidSurface(c.inst, unsafe.Pointer(win))
+	if err != nil {
+		return err
+	}
+	c.surf = surf
+	return c.ctx.refresh(c.surf, w, h)
+}

+ 81 - 0
vendor/gioui.org/app/vulkan_wayland.go

@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build ((linux && !android) || freebsd) && !nowayland && !novulkan
+// +build linux,!android freebsd
+// +build !nowayland
+// +build !novulkan
+
+package app
+
+import (
+	"unsafe"
+
+	"gioui.org/gpu"
+	"gioui.org/internal/vk"
+)
+
+type wlVkContext struct {
+	win  *window
+	inst vk.Instance
+	surf vk.Surface
+	ctx  *vkContext
+}
+
+func init() {
+	newWaylandVulkanContext = func(w *window) (context, error) {
+		inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_wayland_surface")
+		if err != nil {
+			return nil, err
+		}
+		disp := w.display()
+		wlSurf, _, _ := w.surface()
+		surf, err := vk.CreateWaylandSurface(inst, unsafe.Pointer(disp), unsafe.Pointer(wlSurf))
+		if err != nil {
+			vk.DestroyInstance(inst)
+			return nil, err
+		}
+		ctx, err := newVulkanContext(inst, surf)
+		if err != nil {
+			vk.DestroySurface(inst, surf)
+			vk.DestroyInstance(inst)
+			return nil, err
+		}
+		c := &wlVkContext{
+			win:  w,
+			inst: inst,
+			surf: surf,
+			ctx:  ctx,
+		}
+		return c, nil
+	}
+}
+
+func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) {
+	return c.ctx.RenderTarget()
+}
+
+func (c *wlVkContext) API() gpu.API {
+	return c.ctx.api()
+}
+
+func (c *wlVkContext) Release() {
+	c.ctx.release()
+	vk.DestroySurface(c.inst, c.surf)
+	vk.DestroyInstance(c.inst)
+	*c = wlVkContext{}
+}
+
+func (c *wlVkContext) Present() error {
+	return c.ctx.present()
+}
+
+func (c *wlVkContext) Lock() error {
+	return nil
+}
+
+func (c *wlVkContext) Unlock() {}
+
+func (c *wlVkContext) Refresh() error {
+	_, w, h := c.win.surface()
+	return c.ctx.refresh(c.surf, w, h)
+}

+ 81 - 0
vendor/gioui.org/app/vulkan_x11.go

@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build ((linux && !android) || freebsd) && !nox11 && !novulkan
+// +build linux,!android freebsd
+// +build !nox11
+// +build !novulkan
+
+package app
+
+import (
+	"unsafe"
+
+	"gioui.org/gpu"
+	"gioui.org/internal/vk"
+)
+
+type x11VkContext struct {
+	win  *x11Window
+	inst vk.Instance
+	surf vk.Surface
+	ctx  *vkContext
+}
+
+func init() {
+	newX11VulkanContext = func(w *x11Window) (context, error) {
+		inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_xlib_surface")
+		if err != nil {
+			return nil, err
+		}
+		disp := w.display()
+		window, _, _ := w.window()
+		surf, err := vk.CreateXlibSurface(inst, unsafe.Pointer(disp), uintptr(window))
+		if err != nil {
+			vk.DestroyInstance(inst)
+			return nil, err
+		}
+		ctx, err := newVulkanContext(inst, surf)
+		if err != nil {
+			vk.DestroySurface(inst, surf)
+			vk.DestroyInstance(inst)
+			return nil, err
+		}
+		c := &x11VkContext{
+			win:  w,
+			inst: inst,
+			surf: surf,
+			ctx:  ctx,
+		}
+		return c, nil
+	}
+}
+
+func (c *x11VkContext) RenderTarget() (gpu.RenderTarget, error) {
+	return c.ctx.RenderTarget()
+}
+
+func (c *x11VkContext) API() gpu.API {
+	return c.ctx.api()
+}
+
+func (c *x11VkContext) Release() {
+	c.ctx.release()
+	vk.DestroySurface(c.inst, c.surf)
+	vk.DestroyInstance(c.inst)
+	*c = x11VkContext{}
+}
+
+func (c *x11VkContext) Present() error {
+	return c.ctx.present()
+}
+
+func (c *x11VkContext) Lock() error {
+	return nil
+}
+
+func (c *x11VkContext) Unlock() {}
+
+func (c *x11VkContext) Refresh() error {
+	_, w, h := c.win.window()
+	return c.ctx.refresh(c.surf, w, h)
+}

+ 100 - 0
vendor/gioui.org/app/wayland_text_input.c

@@ -0,0 +1,100 @@
+//go:build ((linux && !android) || freebsd) && !nowayland
+// +build linux,!android freebsd
+// +build !nowayland
+
+/* Generated by wayland-scanner 1.19.0 */
+
+/*
+ * Copyright © 2012, 2013 Intel Corporation
+ * Copyright © 2015, 2016 Jan Arne Petersen
+ * Copyright © 2017, 2018 Red Hat, Inc.
+ * Copyright © 2018       Purism SPC
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0  /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+#define WL_PRIVATE __attribute__ ((visibility("hidden")))
+#else
+#define WL_PRIVATE
+#endif
+
+extern const struct wl_interface wl_seat_interface;
+extern const struct wl_interface wl_surface_interface;
+extern const struct wl_interface zwp_text_input_v3_interface;
+
+static const struct wl_interface *text_input_unstable_v3_types[] = {
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	&wl_surface_interface,
+	&wl_surface_interface,
+	&zwp_text_input_v3_interface,
+	&wl_seat_interface,
+};
+
+static const struct wl_message zwp_text_input_v3_requests[] = {
+	{ "destroy", "", text_input_unstable_v3_types + 0 },
+	{ "enable", "", text_input_unstable_v3_types + 0 },
+	{ "disable", "", text_input_unstable_v3_types + 0 },
+	{ "set_surrounding_text", "sii", text_input_unstable_v3_types + 0 },
+	{ "set_text_change_cause", "u", text_input_unstable_v3_types + 0 },
+	{ "set_content_type", "uu", text_input_unstable_v3_types + 0 },
+	{ "set_cursor_rectangle", "iiii", text_input_unstable_v3_types + 0 },
+	{ "commit", "", text_input_unstable_v3_types + 0 },
+};
+
+static const struct wl_message zwp_text_input_v3_events[] = {
+	{ "enter", "o", text_input_unstable_v3_types + 4 },
+	{ "leave", "o", text_input_unstable_v3_types + 5 },
+	{ "preedit_string", "?sii", text_input_unstable_v3_types + 0 },
+	{ "commit_string", "?s", text_input_unstable_v3_types + 0 },
+	{ "delete_surrounding_text", "uu", text_input_unstable_v3_types + 0 },
+	{ "done", "u", text_input_unstable_v3_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwp_text_input_v3_interface = {
+	"zwp_text_input_v3", 1,
+	8, zwp_text_input_v3_requests,
+	6, zwp_text_input_v3_events,
+};
+
+static const struct wl_message zwp_text_input_manager_v3_requests[] = {
+	{ "destroy", "", text_input_unstable_v3_types + 0 },
+	{ "get_text_input", "no", text_input_unstable_v3_types + 6 },
+};
+
+WL_PRIVATE const struct wl_interface zwp_text_input_manager_v3_interface = {
+	"zwp_text_input_manager_v3", 1,
+	2, zwp_text_input_manager_v3_requests,
+	0, NULL,
+};
+

+ 836 - 0
vendor/gioui.org/app/wayland_text_input.h

@@ -0,0 +1,836 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+#ifndef TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
+#define TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_text_input_unstable_v3 The text_input_unstable_v3 protocol
+ * Protocol for composing text
+ *
+ * @section page_desc_text_input_unstable_v3 Description
+ *
+ * This protocol allows compositors to act as input methods and to send text
+ * to applications. A text input object is used to manage state of what are
+ * typically text entry fields in the application.
+ *
+ * This document adheres to the RFC 2119 when using words like "must",
+ * "should", "may", etc.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ *
+ * @section page_ifaces_text_input_unstable_v3 Interfaces
+ * - @subpage page_iface_zwp_text_input_v3 - text input
+ * - @subpage page_iface_zwp_text_input_manager_v3 - text input manager
+ * @section page_copyright_text_input_unstable_v3 Copyright
+ * <pre>
+ *
+ * Copyright © 2012, 2013 Intel Corporation
+ * Copyright © 2015, 2016 Jan Arne Petersen
+ * Copyright © 2017, 2018 Red Hat, Inc.
+ * Copyright © 2018       Purism SPC
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ * </pre>
+ */
+struct wl_seat;
+struct wl_surface;
+struct zwp_text_input_manager_v3;
+struct zwp_text_input_v3;
+
+#ifndef ZWP_TEXT_INPUT_V3_INTERFACE
+#define ZWP_TEXT_INPUT_V3_INTERFACE
+/**
+ * @page page_iface_zwp_text_input_v3 zwp_text_input_v3
+ * @section page_iface_zwp_text_input_v3_desc Description
+ *
+ * The zwp_text_input_v3 interface represents text input and input methods
+ * associated with a seat. It provides enter/leave events to follow the
+ * text input focus for a seat.
+ *
+ * Requests are used to enable/disable the text-input object and set
+ * state information like surrounding and selected text or the content type.
+ * The information about the entered text is sent to the text-input object
+ * via the preedit_string and commit_string events.
+ *
+ * Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
+ * must not point to middle bytes inside a code point: they must either
+ * point to the first byte of a code point or to the end of the buffer.
+ * Lengths must be measured between two valid indices.
+ *
+ * Focus moving throughout surfaces will result in the emission of
+ * zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
+ * surface must commit zwp_text_input_v3.enable and
+ * zwp_text_input_v3.disable requests as the keyboard focus moves across
+ * editable and non-editable elements of the UI. Those two requests are not
+ * expected to be paired with each other, the compositor must be able to
+ * handle consecutive series of the same request.
+ *
+ * State is sent by the state requests (set_surrounding_text,
+ * set_content_type and set_cursor_rectangle) and a commit request. After an
+ * enter event or disable request all state information is invalidated and
+ * needs to be resent by the client.
+ * @section page_iface_zwp_text_input_v3_api API
+ * See @ref iface_zwp_text_input_v3.
+ */
+/**
+ * @defgroup iface_zwp_text_input_v3 The zwp_text_input_v3 interface
+ *
+ * The zwp_text_input_v3 interface represents text input and input methods
+ * associated with a seat. It provides enter/leave events to follow the
+ * text input focus for a seat.
+ *
+ * Requests are used to enable/disable the text-input object and set
+ * state information like surrounding and selected text or the content type.
+ * The information about the entered text is sent to the text-input object
+ * via the preedit_string and commit_string events.
+ *
+ * Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
+ * must not point to middle bytes inside a code point: they must either
+ * point to the first byte of a code point or to the end of the buffer.
+ * Lengths must be measured between two valid indices.
+ *
+ * Focus moving throughout surfaces will result in the emission of
+ * zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
+ * surface must commit zwp_text_input_v3.enable and
+ * zwp_text_input_v3.disable requests as the keyboard focus moves across
+ * editable and non-editable elements of the UI. Those two requests are not
+ * expected to be paired with each other, the compositor must be able to
+ * handle consecutive series of the same request.
+ *
+ * State is sent by the state requests (set_surrounding_text,
+ * set_content_type and set_cursor_rectangle) and a commit request. After an
+ * enter event or disable request all state information is invalidated and
+ * needs to be resent by the client.
+ */
+extern const struct wl_interface zwp_text_input_v3_interface;
+#endif
+#ifndef ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE
+#define ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE
+/**
+ * @page page_iface_zwp_text_input_manager_v3 zwp_text_input_manager_v3
+ * @section page_iface_zwp_text_input_manager_v3_desc Description
+ *
+ * A factory for text-input objects. This object is a global singleton.
+ * @section page_iface_zwp_text_input_manager_v3_api API
+ * See @ref iface_zwp_text_input_manager_v3.
+ */
+/**
+ * @defgroup iface_zwp_text_input_manager_v3 The zwp_text_input_manager_v3 interface
+ *
+ * A factory for text-input objects. This object is a global singleton.
+ */
+extern const struct wl_interface zwp_text_input_manager_v3_interface;
+#endif
+
+#ifndef ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
+#define ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
+/**
+ * @ingroup iface_zwp_text_input_v3
+ * text change reason
+ *
+ * Reason for the change of surrounding text or cursor posision.
+ */
+enum zwp_text_input_v3_change_cause {
+	/**
+	 * input method caused the change
+	 */
+	ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD = 0,
+	/**
+	 * something else than the input method caused the change
+	 */
+	ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER = 1,
+};
+#endif /* ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM */
+
+#ifndef ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
+#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
+/**
+ * @ingroup iface_zwp_text_input_v3
+ * content hint
+ *
+ * Content hint is a bitmask to allow to modify the behavior of the text
+ * input.
+ */
+enum zwp_text_input_v3_content_hint {
+	/**
+	 * no special behavior
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE = 0x0,
+	/**
+	 * suggest word completions
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION = 0x1,
+	/**
+	 * suggest word corrections
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK = 0x2,
+	/**
+	 * switch to uppercase letters at the start of a sentence
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION = 0x4,
+	/**
+	 * prefer lowercase letters
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE = 0x8,
+	/**
+	 * prefer uppercase letters
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE = 0x10,
+	/**
+	 * prefer casing for titles and headings (can be language dependent)
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE = 0x20,
+	/**
+	 * characters should be hidden
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT = 0x40,
+	/**
+	 * typed text should not be stored
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA = 0x80,
+	/**
+	 * just Latin characters should be entered
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN = 0x100,
+	/**
+	 * the text input is multiline
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE = 0x200,
+};
+#endif /* ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM */
+
+#ifndef ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
+#define ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
+/**
+ * @ingroup iface_zwp_text_input_v3
+ * content purpose
+ *
+ * The content purpose allows to specify the primary purpose of a text
+ * input.
+ *
+ * This allows an input method to show special purpose input panels with
+ * extra characters or to disallow some characters.
+ */
+enum zwp_text_input_v3_content_purpose {
+	/**
+	 * default input, allowing all characters
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL = 0,
+	/**
+	 * allow only alphabetic characters
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA = 1,
+	/**
+	 * allow only digits
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS = 2,
+	/**
+	 * input a number (including decimal separator and sign)
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER = 3,
+	/**
+	 * input a phone number
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE = 4,
+	/**
+	 * input an URL
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL = 5,
+	/**
+	 * input an email address
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL = 6,
+	/**
+	 * input a name of a person
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME = 7,
+	/**
+	 * input a password (combine with sensitive_data hint)
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD = 8,
+	/**
+	 * input is a numeric password (combine with sensitive_data hint)
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN = 9,
+	/**
+	 * input a date
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE = 10,
+	/**
+	 * input a time
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME = 11,
+	/**
+	 * input a date and time
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME = 12,
+	/**
+	 * input for a terminal
+	 */
+	ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL = 13,
+};
+#endif /* ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM */
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ * @struct zwp_text_input_v3_listener
+ */
+struct zwp_text_input_v3_listener {
+	/**
+	 * enter event
+	 *
+	 * Notification that this seat's text-input focus is on a certain
+	 * surface.
+	 *
+	 * If client has created multiple text input objects, compositor
+	 * must send this event to all of them.
+	 *
+	 * When the seat has the keyboard capability the text-input focus
+	 * follows the keyboard focus. This event sets the current surface
+	 * for the text-input object.
+	 */
+	void (*enter)(void *data,
+		      struct zwp_text_input_v3 *zwp_text_input_v3,
+		      struct wl_surface *surface);
+	/**
+	 * leave event
+	 *
+	 * Notification that this seat's text-input focus is no longer on
+	 * a certain surface. The client should reset any preedit string
+	 * previously set.
+	 *
+	 * The leave notification clears the current surface. It is sent
+	 * before the enter notification for the new focus. After leave
+	 * event, compositor must ignore requests from any text input
+	 * instances until next enter event.
+	 *
+	 * When the seat has the keyboard capability the text-input focus
+	 * follows the keyboard focus.
+	 */
+	void (*leave)(void *data,
+		      struct zwp_text_input_v3 *zwp_text_input_v3,
+		      struct wl_surface *surface);
+	/**
+	 * pre-edit
+	 *
+	 * Notify when a new composing text (pre-edit) should be set at
+	 * the current cursor position. Any previously set composing text
+	 * must be removed. Any previously existing selected text must be
+	 * removed.
+	 *
+	 * The argument text contains the pre-edit string buffer.
+	 *
+	 * The parameters cursor_begin and cursor_end are counted in bytes
+	 * relative to the beginning of the submitted text buffer. Cursor
+	 * should be hidden when both are equal to -1.
+	 *
+	 * They could be represented by the client as a line if both values
+	 * are the same, or as a text highlight otherwise.
+	 *
+	 * Values set with this event are double-buffered. They must be
+	 * applied and reset to initial on the next zwp_text_input_v3.done
+	 * event.
+	 *
+	 * The initial value of text is an empty string, and cursor_begin,
+	 * cursor_end and cursor_hidden are all 0.
+	 */
+	void (*preedit_string)(void *data,
+			       struct zwp_text_input_v3 *zwp_text_input_v3,
+			       const char *text,
+			       int32_t cursor_begin,
+			       int32_t cursor_end);
+	/**
+	 * text commit
+	 *
+	 * Notify when text should be inserted into the editor widget.
+	 * The text to commit could be either just a single character after
+	 * a key press or the result of some composing (pre-edit).
+	 *
+	 * Values set with this event are double-buffered. They must be
+	 * applied and reset to initial on the next zwp_text_input_v3.done
+	 * event.
+	 *
+	 * The initial value of text is an empty string.
+	 */
+	void (*commit_string)(void *data,
+			      struct zwp_text_input_v3 *zwp_text_input_v3,
+			      const char *text);
+	/**
+	 * delete surrounding text
+	 *
+	 * Notify when the text around the current cursor position should
+	 * be deleted.
+	 *
+	 * Before_length and after_length are the number of bytes before
+	 * and after the current cursor index (excluding the selection) to
+	 * delete.
+	 *
+	 * If a preedit text is present, in effect before_length is counted
+	 * from the beginning of it, and after_length from its end (see
+	 * done event sequence).
+	 *
+	 * Values set with this event are double-buffered. They must be
+	 * applied and reset to initial on the next zwp_text_input_v3.done
+	 * event.
+	 *
+	 * The initial values of both before_length and after_length are 0.
+	 * @param before_length length of text before current cursor position
+	 * @param after_length length of text after current cursor position
+	 */
+	void (*delete_surrounding_text)(void *data,
+					struct zwp_text_input_v3 *zwp_text_input_v3,
+					uint32_t before_length,
+					uint32_t after_length);
+	/**
+	 * apply changes
+	 *
+	 * Instruct the application to apply changes to state requested
+	 * by the preedit_string, commit_string and delete_surrounding_text
+	 * events. The state relating to these events is double-buffered,
+	 * and each one modifies the pending state. This event replaces the
+	 * current state with the pending state.
+	 *
+	 * The application must proceed by evaluating the changes in the
+	 * following order:
+	 *
+	 * 1. Replace existing preedit string with the cursor. 2. Delete
+	 * requested surrounding text. 3. Insert commit string with the
+	 * cursor at its end. 4. Calculate surrounding text to send. 5.
+	 * Insert new preedit text in cursor position. 6. Place cursor
+	 * inside preedit text.
+	 *
+	 * The serial number reflects the last state of the
+	 * zwp_text_input_v3 object known to the compositor. The value of
+	 * the serial argument must be equal to the number of commit
+	 * requests already issued on that object. When the client receives
+	 * a done event with a serial different than the number of past
+	 * commit requests, it must proceed as normal, except it should not
+	 * change the current state of the zwp_text_input_v3 object.
+	 */
+	void (*done)(void *data,
+		     struct zwp_text_input_v3 *zwp_text_input_v3,
+		     uint32_t serial);
+};
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+static inline int
+zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *zwp_text_input_v3,
+			       const struct zwp_text_input_v3_listener *listener, void *data)
+{
+	return wl_proxy_add_listener((struct wl_proxy *) zwp_text_input_v3,
+				     (void (**)(void)) listener, data);
+}
+
+#define ZWP_TEXT_INPUT_V3_DESTROY 0
+#define ZWP_TEXT_INPUT_V3_ENABLE 1
+#define ZWP_TEXT_INPUT_V3_DISABLE 2
+#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT 3
+#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE 4
+#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE 5
+#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE 6
+#define ZWP_TEXT_INPUT_V3_COMMIT 7
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_ENTER_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_LEAVE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_PREEDIT_STRING_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_COMMIT_STRING_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_DELETE_SURROUNDING_TEXT_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_DONE_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_ENABLE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_DISABLE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_v3
+ */
+#define ZWP_TEXT_INPUT_V3_COMMIT_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_text_input_v3 */
+static inline void
+zwp_text_input_v3_set_user_data(struct zwp_text_input_v3 *zwp_text_input_v3, void *user_data)
+{
+	wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_v3, user_data);
+}
+
+/** @ingroup iface_zwp_text_input_v3 */
+static inline void *
+zwp_text_input_v3_get_user_data(struct zwp_text_input_v3 *zwp_text_input_v3)
+{
+	return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_v3);
+}
+
+static inline uint32_t
+zwp_text_input_v3_get_version(struct zwp_text_input_v3 *zwp_text_input_v3)
+{
+	return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3);
+}
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ *
+ * Destroy the wp_text_input object. Also disables all surfaces enabled
+ * through this wp_text_input object.
+ */
+static inline void
+zwp_text_input_v3_destroy(struct zwp_text_input_v3 *zwp_text_input_v3)
+{
+	wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
+			 ZWP_TEXT_INPUT_V3_DESTROY);
+
+	wl_proxy_destroy((struct wl_proxy *) zwp_text_input_v3);
+}
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ *
+ * Requests text input on the surface previously obtained from the enter
+ * event.
+ *
+ * This request must be issued every time the active text input changes
+ * to a new one, including within the current surface. Use
+ * zwp_text_input_v3.disable when there is no longer any input focus on
+ * the current surface.
+ *
+ * Clients must not enable more than one text input on the single seat
+ * and should disable the current text input before enabling the new one.
+ * At most one instance of text input may be in enabled state per instance,
+ * Requests to enable the another text input when some text input is active
+ * must be ignored by compositor.
+ *
+ * This request resets all state associated with previous enable, disable,
+ * set_surrounding_text, set_text_change_cause, set_content_type, and
+ * set_cursor_rectangle requests, as well as the state associated with
+ * preedit_string, commit_string, and delete_surrounding_text events.
+ *
+ * The set_surrounding_text, set_content_type and set_cursor_rectangle
+ * requests must follow if the text input supports the necessary
+ * functionality.
+ *
+ * State set with this request is double-buffered. It will get applied on
+ * the next zwp_text_input_v3.commit request, and stay valid until the
+ * next committed enable or disable request.
+ *
+ * The changes must be applied by the compositor after issuing a
+ * zwp_text_input_v3.commit request.
+ */
+static inline void
+zwp_text_input_v3_enable(struct zwp_text_input_v3 *zwp_text_input_v3)
+{
+	wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
+			 ZWP_TEXT_INPUT_V3_ENABLE);
+}
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ *
+ * Explicitly disable text input on the current surface (typically when
+ * there is no focus on any text entry inside the surface).
+ *
+ * State set with this request is double-buffered. It will get applied on
+ * the next zwp_text_input_v3.commit request.
+ */
+static inline void
+zwp_text_input_v3_disable(struct zwp_text_input_v3 *zwp_text_input_v3)
+{
+	wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
+			 ZWP_TEXT_INPUT_V3_DISABLE);
+}
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ *
+ * Sets the surrounding plain text around the input, excluding the preedit
+ * text.
+ *
+ * The client should notify the compositor of any changes in any of the
+ * values carried with this request, including changes caused by handling
+ * incoming text-input events as well as changes caused by other
+ * mechanisms like keyboard typing.
+ *
+ * If the client is unaware of the text around the cursor, it should not
+ * issue this request, to signify lack of support to the compositor.
+ *
+ * Text is UTF-8 encoded, and should include the cursor position, the
+ * complete selection and additional characters before and after them.
+ * There is a maximum length of wayland messages, so text can not be
+ * longer than 4000 bytes.
+ *
+ * Cursor is the byte offset of the cursor within text buffer.
+ *
+ * Anchor is the byte offset of the selection anchor within text buffer.
+ * If there is no selected text, anchor is the same as cursor.
+ *
+ * If any preedit text is present, it is replaced with a cursor for the
+ * purpose of this event.
+ *
+ * Values set with this request are double-buffered. They will get applied
+ * on the next zwp_text_input_v3.commit request, and stay valid until the
+ * next committed enable or disable request.
+ *
+ * The initial state for affected fields is empty, meaning that the text
+ * input does not support sending surrounding text. If the empty values
+ * get applied, subsequent attempts to change them may have no effect.
+ */
+static inline void
+zwp_text_input_v3_set_surrounding_text(struct zwp_text_input_v3 *zwp_text_input_v3, const char *text, int32_t cursor, int32_t anchor)
+{
+	wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
+			 ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT, text, cursor, anchor);
+}
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ *
+ * Tells the compositor why the text surrounding the cursor changed.
+ *
+ * Whenever the client detects an external change in text, cursor, or
+ * anchor posision, it must issue this request to the compositor. This
+ * request is intended to give the input method a chance to update the
+ * preedit text in an appropriate way, e.g. by removing it when the user
+ * starts typing with a keyboard.
+ *
+ * cause describes the source of the change.
+ *
+ * The value set with this request is double-buffered. It must be applied
+ * and reset to initial at the next zwp_text_input_v3.commit request.
+ *
+ * The initial value of cause is input_method.
+ */
+static inline void
+zwp_text_input_v3_set_text_change_cause(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t cause)
+{
+	wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
+			 ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE, cause);
+}
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ *
+ * Sets the content purpose and content hint. While the purpose is the
+ * basic purpose of an input field, the hint flags allow to modify some of
+ * the behavior.
+ *
+ * Values set with this request are double-buffered. They will get applied
+ * on the next zwp_text_input_v3.commit request.
+ * Subsequent attempts to update them may have no effect. The values
+ * remain valid until the next committed enable or disable request.
+ *
+ * The initial value for hint is none, and the initial value for purpose
+ * is normal.
+ */
+static inline void
+zwp_text_input_v3_set_content_type(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t hint, uint32_t purpose)
+{
+	wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
+			 ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE, hint, purpose);
+}
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ *
+ * Marks an area around the cursor as a x, y, width, height rectangle in
+ * surface local coordinates.
+ *
+ * Allows the compositor to put a window with word suggestions near the
+ * cursor, without obstructing the text being input.
+ *
+ * If the client is unaware of the position of edited text, it should not
+ * issue this request, to signify lack of support to the compositor.
+ *
+ * Values set with this request are double-buffered. They will get applied
+ * on the next zwp_text_input_v3.commit request, and stay valid until the
+ * next committed enable or disable request.
+ *
+ * The initial values describing a cursor rectangle are empty. That means
+ * the text input does not support describing the cursor area. If the
+ * empty values get applied, subsequent attempts to change them may have
+ * no effect.
+ */
+static inline void
+zwp_text_input_v3_set_cursor_rectangle(struct zwp_text_input_v3 *zwp_text_input_v3, int32_t x, int32_t y, int32_t width, int32_t height)
+{
+	wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
+			 ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE, x, y, width, height);
+}
+
+/**
+ * @ingroup iface_zwp_text_input_v3
+ *
+ * Atomically applies state changes recently sent to the compositor.
+ *
+ * The commit request establishes and updates the state of the client, and
+ * must be issued after any changes to apply them.
+ *
+ * Text input state (enabled status, content purpose, content hint,
+ * surrounding text and change cause, cursor rectangle) is conceptually
+ * double-buffered within the context of a text input, i.e. between a
+ * committed enable request and the following committed enable or disable
+ * request.
+ *
+ * Protocol requests modify the pending state, as opposed to the current
+ * state in use by the input method. A commit request atomically applies
+ * all pending state, replacing the current state. After commit, the new
+ * pending state is as documented for each related request.
+ *
+ * Requests are applied in the order of arrival.
+ *
+ * Neither current nor pending state are modified unless noted otherwise.
+ *
+ * The compositor must count the number of commit requests coming from
+ * each zwp_text_input_v3 object and use the count as the serial in done
+ * events.
+ */
+static inline void
+zwp_text_input_v3_commit(struct zwp_text_input_v3 *zwp_text_input_v3)
+{
+	wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
+			 ZWP_TEXT_INPUT_V3_COMMIT);
+}
+
+#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY 0
+#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT 1
+
+
+/**
+ * @ingroup iface_zwp_text_input_manager_v3
+ */
+#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_text_input_manager_v3
+ */
+#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_text_input_manager_v3 */
+static inline void
+zwp_text_input_manager_v3_set_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, void *user_data)
+{
+	wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_manager_v3, user_data);
+}
+
+/** @ingroup iface_zwp_text_input_manager_v3 */
+static inline void *
+zwp_text_input_manager_v3_get_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
+{
+	return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_manager_v3);
+}
+
+static inline uint32_t
+zwp_text_input_manager_v3_get_version(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
+{
+	return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3);
+}
+
+/**
+ * @ingroup iface_zwp_text_input_manager_v3
+ *
+ * Destroy the wp_text_input_manager object.
+ */
+static inline void
+zwp_text_input_manager_v3_destroy(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
+{
+	wl_proxy_marshal((struct wl_proxy *) zwp_text_input_manager_v3,
+			 ZWP_TEXT_INPUT_MANAGER_V3_DESTROY);
+
+	wl_proxy_destroy((struct wl_proxy *) zwp_text_input_manager_v3);
+}
+
+/**
+ * @ingroup iface_zwp_text_input_manager_v3
+ *
+ * Creates a new text-input object for a given seat.
+ */
+static inline struct zwp_text_input_v3 *
+zwp_text_input_manager_v3_get_text_input(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, struct wl_seat *seat)
+{
+	struct wl_proxy *id;
+
+	id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_text_input_manager_v3,
+			 ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT, &zwp_text_input_v3_interface, NULL, seat);
+
+	return (struct zwp_text_input_v3 *) id;
+}
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif

+ 79 - 0
vendor/gioui.org/app/wayland_xdg_decoration.c

@@ -0,0 +1,79 @@
+//go:build ((linux && !android) || freebsd) && !nowayland
+// +build linux,!android freebsd
+// +build !nowayland
+
+/* Generated by wayland-scanner 1.19.0 */
+
+/*
+ * Copyright © 2018 Simon Ser
+ *
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0  /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+#define WL_PRIVATE __attribute__ ((visibility("hidden")))
+#else
+#define WL_PRIVATE
+#endif
+
+extern const struct wl_interface xdg_toplevel_interface;
+extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
+
+static const struct wl_interface *xdg_decoration_unstable_v1_types[] = {
+	NULL,
+	&zxdg_toplevel_decoration_v1_interface,
+	&xdg_toplevel_interface,
+};
+
+static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
+	{ "destroy", "", xdg_decoration_unstable_v1_types + 0 },
+	{ "get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1 },
+};
+
+WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = {
+	"zxdg_decoration_manager_v1", 1,
+	2, zxdg_decoration_manager_v1_requests,
+	0, NULL,
+};
+
+static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = {
+	{ "destroy", "", xdg_decoration_unstable_v1_types + 0 },
+	{ "set_mode", "u", xdg_decoration_unstable_v1_types + 0 },
+	{ "unset_mode", "", xdg_decoration_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
+	{ "configure", "u", xdg_decoration_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = {
+	"zxdg_toplevel_decoration_v1", 1,
+	3, zxdg_toplevel_decoration_v1_requests,
+	1, zxdg_toplevel_decoration_v1_events,
+};
+

+ 382 - 0
vendor/gioui.org/app/wayland_xdg_decoration.h

@@ -0,0 +1,382 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+#ifndef XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_xdg_decoration_unstable_v1 The xdg_decoration_unstable_v1 protocol
+ * @section page_ifaces_xdg_decoration_unstable_v1 Interfaces
+ * - @subpage page_iface_zxdg_decoration_manager_v1 - window decoration manager
+ * - @subpage page_iface_zxdg_toplevel_decoration_v1 - decoration object for a toplevel surface
+ * @section page_copyright_xdg_decoration_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2018 Simon Ser
+ *
+ * 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.
+ * </pre>
+ */
+struct xdg_toplevel;
+struct zxdg_decoration_manager_v1;
+struct zxdg_toplevel_decoration_v1;
+
+#ifndef ZXDG_DECORATION_MANAGER_V1_INTERFACE
+#define ZXDG_DECORATION_MANAGER_V1_INTERFACE
+/**
+ * @page page_iface_zxdg_decoration_manager_v1 zxdg_decoration_manager_v1
+ * @section page_iface_zxdg_decoration_manager_v1_desc Description
+ *
+ * This interface allows a compositor to announce support for server-side
+ * decorations.
+ *
+ * A window decoration is a set of window controls as deemed appropriate by
+ * the party managing them, such as user interface components used to move,
+ * resize and change a window's state.
+ *
+ * A client can use this protocol to request being decorated by a supporting
+ * compositor.
+ *
+ * If compositor and client do not negotiate the use of a server-side
+ * decoration using this protocol, clients continue to self-decorate as they
+ * see fit.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ * @section page_iface_zxdg_decoration_manager_v1_api API
+ * See @ref iface_zxdg_decoration_manager_v1.
+ */
+/**
+ * @defgroup iface_zxdg_decoration_manager_v1 The zxdg_decoration_manager_v1 interface
+ *
+ * This interface allows a compositor to announce support for server-side
+ * decorations.
+ *
+ * A window decoration is a set of window controls as deemed appropriate by
+ * the party managing them, such as user interface components used to move,
+ * resize and change a window's state.
+ *
+ * A client can use this protocol to request being decorated by a supporting
+ * compositor.
+ *
+ * If compositor and client do not negotiate the use of a server-side
+ * decoration using this protocol, clients continue to self-decorate as they
+ * see fit.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ */
+extern const struct wl_interface zxdg_decoration_manager_v1_interface;
+#endif
+#ifndef ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE
+#define ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE
+/**
+ * @page page_iface_zxdg_toplevel_decoration_v1 zxdg_toplevel_decoration_v1
+ * @section page_iface_zxdg_toplevel_decoration_v1_desc Description
+ *
+ * The decoration object allows the compositor to toggle server-side window
+ * decorations for a toplevel surface. The client can request to switch to
+ * another mode.
+ *
+ * The xdg_toplevel_decoration object must be destroyed before its
+ * xdg_toplevel.
+ * @section page_iface_zxdg_toplevel_decoration_v1_api API
+ * See @ref iface_zxdg_toplevel_decoration_v1.
+ */
+/**
+ * @defgroup iface_zxdg_toplevel_decoration_v1 The zxdg_toplevel_decoration_v1 interface
+ *
+ * The decoration object allows the compositor to toggle server-side window
+ * decorations for a toplevel surface. The client can request to switch to
+ * another mode.
+ *
+ * The xdg_toplevel_decoration object must be destroyed before its
+ * xdg_toplevel.
+ */
+extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
+#endif
+
+#define ZXDG_DECORATION_MANAGER_V1_DESTROY 0
+#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION 1
+
+
+/**
+ * @ingroup iface_zxdg_decoration_manager_v1
+ */
+#define ZXDG_DECORATION_MANAGER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_decoration_manager_v1
+ */
+#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION_SINCE_VERSION 1
+
+/** @ingroup iface_zxdg_decoration_manager_v1 */
+static inline void
+zxdg_decoration_manager_v1_set_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, void *user_data)
+{
+	wl_proxy_set_user_data((struct wl_proxy *) zxdg_decoration_manager_v1, user_data);
+}
+
+/** @ingroup iface_zxdg_decoration_manager_v1 */
+static inline void *
+zxdg_decoration_manager_v1_get_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
+{
+	return wl_proxy_get_user_data((struct wl_proxy *) zxdg_decoration_manager_v1);
+}
+
+static inline uint32_t
+zxdg_decoration_manager_v1_get_version(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
+{
+	return wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_decoration_manager_v1
+ *
+ * Destroy the decoration manager. This doesn't destroy objects created
+ * with the manager.
+ */
+static inline void
+zxdg_decoration_manager_v1_destroy(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
+{
+	wl_proxy_marshal((struct wl_proxy *) zxdg_decoration_manager_v1,
+			 ZXDG_DECORATION_MANAGER_V1_DESTROY);
+
+	wl_proxy_destroy((struct wl_proxy *) zxdg_decoration_manager_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_decoration_manager_v1
+ *
+ * Create a new decoration object associated with the given toplevel.
+ *
+ * Creating an xdg_toplevel_decoration from an xdg_toplevel 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_toplevel_decoration.configure event must also be treated as
+ * errors.
+ */
+static inline struct zxdg_toplevel_decoration_v1 *
+zxdg_decoration_manager_v1_get_toplevel_decoration(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, struct xdg_toplevel *toplevel)
+{
+	struct wl_proxy *id;
+
+	id = wl_proxy_marshal_constructor((struct wl_proxy *) zxdg_decoration_manager_v1,
+			 ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, &zxdg_toplevel_decoration_v1_interface, NULL, toplevel);
+
+	return (struct zxdg_toplevel_decoration_v1 *) id;
+}
+
+#ifndef ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
+#define ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
+enum zxdg_toplevel_decoration_v1_error {
+	/**
+	 * xdg_toplevel has a buffer attached before configure
+	 */
+	ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER = 0,
+	/**
+	 * xdg_toplevel already has a decoration object
+	 */
+	ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED = 1,
+	/**
+	 * xdg_toplevel destroyed before the decoration object
+	 */
+	ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED = 2,
+};
+#endif /* ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM */
+
+#ifndef ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
+#define ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ * window decoration modes
+ *
+ * These values describe window decoration modes.
+ */
+enum zxdg_toplevel_decoration_v1_mode {
+	/**
+	 * no server-side window decoration
+	 */
+	ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1,
+	/**
+	 * server-side window decoration
+	 */
+	ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2,
+};
+#endif /* ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM */
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ * @struct zxdg_toplevel_decoration_v1_listener
+ */
+struct zxdg_toplevel_decoration_v1_listener {
+	/**
+	 * suggest a surface change
+	 *
+	 * The configure event asks the client to change its decoration
+	 * mode. The configured state should not be applied immediately.
+	 * Clients must send an ack_configure in response to this event.
+	 * See xdg_surface.configure and xdg_surface.ack_configure for
+	 * details.
+	 *
+	 * A configure event can be sent at any time. The specified mode
+	 * must be obeyed by the client.
+	 * @param mode the decoration mode
+	 */
+	void (*configure)(void *data,
+			  struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
+			  uint32_t mode);
+};
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ */
+static inline int
+zxdg_toplevel_decoration_v1_add_listener(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
+					 const struct zxdg_toplevel_decoration_v1_listener *listener, void *data)
+{
+	return wl_proxy_add_listener((struct wl_proxy *) zxdg_toplevel_decoration_v1,
+				     (void (**)(void)) listener, data);
+}
+
+#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY 0
+#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE 1
+#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE 2
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ */
+#define ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ */
+#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ */
+#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ */
+#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE_SINCE_VERSION 1
+
+/** @ingroup iface_zxdg_toplevel_decoration_v1 */
+static inline void
+zxdg_toplevel_decoration_v1_set_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, void *user_data)
+{
+	wl_proxy_set_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1, user_data);
+}
+
+/** @ingroup iface_zxdg_toplevel_decoration_v1 */
+static inline void *
+zxdg_toplevel_decoration_v1_get_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
+{
+	return wl_proxy_get_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1);
+}
+
+static inline uint32_t
+zxdg_toplevel_decoration_v1_get_version(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
+{
+	return wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ *
+ * Switch back to a mode without any server-side decorations at the next
+ * commit.
+ */
+static inline void
+zxdg_toplevel_decoration_v1_destroy(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
+{
+	wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
+			 ZXDG_TOPLEVEL_DECORATION_V1_DESTROY);
+
+	wl_proxy_destroy((struct wl_proxy *) zxdg_toplevel_decoration_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ *
+ * Set the toplevel surface decoration mode. This informs the compositor
+ * that the client prefers the provided decoration mode.
+ *
+ * After requesting a decoration mode, the compositor will respond by
+ * emitting an xdg_surface.configure event. The client should then update
+ * its content, drawing it without decorations if the received mode is
+ * server-side decorations. The client must also acknowledge the configure
+ * when committing the new content (see xdg_surface.ack_configure).
+ *
+ * The compositor can decide not to use the client's mode and enforce a
+ * different mode instead.
+ *
+ * Clients whose decoration mode depend on the xdg_toplevel state may send
+ * a set_mode request in response to an xdg_surface.configure event and wait
+ * for the next xdg_surface.configure event to prevent unwanted state.
+ * Such clients are responsible for preventing configure loops and must
+ * make sure not to send multiple successive set_mode requests with the
+ * same decoration mode.
+ */
+static inline void
+zxdg_toplevel_decoration_v1_set_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode)
+{
+	wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
+			 ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, mode);
+}
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ *
+ * Unset the toplevel surface decoration mode. This informs the compositor
+ * that the client doesn't prefer a particular decoration mode.
+ *
+ * This request has the same semantics as set_mode.
+ */
+static inline void
+zxdg_toplevel_decoration_v1_unset_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
+{
+	wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
+			 ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE);
+}
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif

+ 185 - 0
vendor/gioui.org/app/wayland_xdg_shell.c

@@ -0,0 +1,185 @@
+//go:build ((linux && !android) || freebsd) && !nowayland
+// +build linux,!android freebsd
+// +build !nowayland
+
+/* Generated by wayland-scanner 1.19.0 */
+
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0  /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+#define WL_PRIVATE __attribute__ ((visibility("hidden")))
+#else
+#define WL_PRIVATE
+#endif
+
+extern const struct wl_interface wl_output_interface;
+extern const struct wl_interface wl_seat_interface;
+extern const struct wl_interface wl_surface_interface;
+extern const struct wl_interface xdg_popup_interface;
+extern const struct wl_interface xdg_positioner_interface;
+extern const struct wl_interface xdg_surface_interface;
+extern const struct wl_interface xdg_toplevel_interface;
+
+static const struct wl_interface *xdg_shell_types[] = {
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	&xdg_positioner_interface,
+	&xdg_surface_interface,
+	&wl_surface_interface,
+	&xdg_toplevel_interface,
+	&xdg_popup_interface,
+	&xdg_surface_interface,
+	&xdg_positioner_interface,
+	&xdg_toplevel_interface,
+	&wl_seat_interface,
+	NULL,
+	NULL,
+	NULL,
+	&wl_seat_interface,
+	NULL,
+	&wl_seat_interface,
+	NULL,
+	NULL,
+	&wl_output_interface,
+	&wl_seat_interface,
+	NULL,
+	&xdg_positioner_interface,
+	NULL,
+};
+
+static const struct wl_message xdg_wm_base_requests[] = {
+	{ "destroy", "", xdg_shell_types + 0 },
+	{ "create_positioner", "n", xdg_shell_types + 4 },
+	{ "get_xdg_surface", "no", xdg_shell_types + 5 },
+	{ "pong", "u", xdg_shell_types + 0 },
+};
+
+static const struct wl_message xdg_wm_base_events[] = {
+	{ "ping", "u", xdg_shell_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
+	"xdg_wm_base", 3,
+	4, xdg_wm_base_requests,
+	1, xdg_wm_base_events,
+};
+
+static const struct wl_message xdg_positioner_requests[] = {
+	{ "destroy", "", xdg_shell_types + 0 },
+	{ "set_size", "ii", xdg_shell_types + 0 },
+	{ "set_anchor_rect", "iiii", xdg_shell_types + 0 },
+	{ "set_anchor", "u", xdg_shell_types + 0 },
+	{ "set_gravity", "u", xdg_shell_types + 0 },
+	{ "set_constraint_adjustment", "u", xdg_shell_types + 0 },
+	{ "set_offset", "ii", xdg_shell_types + 0 },
+	{ "set_reactive", "3", xdg_shell_types + 0 },
+	{ "set_parent_size", "3ii", xdg_shell_types + 0 },
+	{ "set_parent_configure", "3u", xdg_shell_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
+	"xdg_positioner", 3,
+	10, xdg_positioner_requests,
+	0, NULL,
+};
+
+static const struct wl_message xdg_surface_requests[] = {
+	{ "destroy", "", xdg_shell_types + 0 },
+	{ "get_toplevel", "n", xdg_shell_types + 7 },
+	{ "get_popup", "n?oo", xdg_shell_types + 8 },
+	{ "set_window_geometry", "iiii", xdg_shell_types + 0 },
+	{ "ack_configure", "u", xdg_shell_types + 0 },
+};
+
+static const struct wl_message xdg_surface_events[] = {
+	{ "configure", "u", xdg_shell_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface xdg_surface_interface = {
+	"xdg_surface", 3,
+	5, xdg_surface_requests,
+	1, xdg_surface_events,
+};
+
+static const struct wl_message xdg_toplevel_requests[] = {
+	{ "destroy", "", xdg_shell_types + 0 },
+	{ "set_parent", "?o", xdg_shell_types + 11 },
+	{ "set_title", "s", xdg_shell_types + 0 },
+	{ "set_app_id", "s", xdg_shell_types + 0 },
+	{ "show_window_menu", "ouii", xdg_shell_types + 12 },
+	{ "move", "ou", xdg_shell_types + 16 },
+	{ "resize", "ouu", xdg_shell_types + 18 },
+	{ "set_max_size", "ii", xdg_shell_types + 0 },
+	{ "set_min_size", "ii", xdg_shell_types + 0 },
+	{ "set_maximized", "", xdg_shell_types + 0 },
+	{ "unset_maximized", "", xdg_shell_types + 0 },
+	{ "set_fullscreen", "?o", xdg_shell_types + 21 },
+	{ "unset_fullscreen", "", xdg_shell_types + 0 },
+	{ "set_minimized", "", xdg_shell_types + 0 },
+};
+
+static const struct wl_message xdg_toplevel_events[] = {
+	{ "configure", "iia", xdg_shell_types + 0 },
+	{ "close", "", xdg_shell_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
+	"xdg_toplevel", 3,
+	14, xdg_toplevel_requests,
+	2, xdg_toplevel_events,
+};
+
+static const struct wl_message xdg_popup_requests[] = {
+	{ "destroy", "", xdg_shell_types + 0 },
+	{ "grab", "ou", xdg_shell_types + 22 },
+	{ "reposition", "3ou", xdg_shell_types + 24 },
+};
+
+static const struct wl_message xdg_popup_events[] = {
+	{ "configure", "iiii", xdg_shell_types + 0 },
+	{ "popup_done", "", xdg_shell_types + 0 },
+	{ "repositioned", "3u", xdg_shell_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface xdg_popup_interface = {
+	"xdg_popup", 3,
+	3, xdg_popup_requests,
+	3, xdg_popup_events,
+};
+

+ 2003 - 0
vendor/gioui.org/app/wayland_xdg_shell.h

@@ -0,0 +1,2003 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+#ifndef XDG_SHELL_CLIENT_PROTOCOL_H
+#define XDG_SHELL_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#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
+ * <pre>
+ *
+ * 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.
+ * </pre>
+ */
+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

+ 984 - 0
vendor/gioui.org/app/window.go

@@ -0,0 +1,984 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+import (
+	"errors"
+	"fmt"
+	"image"
+	"image/color"
+	"runtime"
+	"sync"
+	"time"
+	"unicode/utf8"
+
+	"gioui.org/f32"
+	"gioui.org/font/gofont"
+	"gioui.org/gpu"
+	"gioui.org/internal/debug"
+	"gioui.org/internal/ops"
+	"gioui.org/io/event"
+	"gioui.org/io/input"
+	"gioui.org/io/key"
+	"gioui.org/io/pointer"
+	"gioui.org/io/system"
+	"gioui.org/layout"
+	"gioui.org/op"
+	"gioui.org/text"
+	"gioui.org/unit"
+	"gioui.org/widget"
+	"gioui.org/widget/material"
+)
+
+// Option configures a window.
+type Option func(unit.Metric, *Config)
+
+// Window represents an operating system window.
+//
+// The zero-value Window is useful; the GUI window is created and shown the first
+// time the [Event] method is called. On iOS or Android, the first Window represents
+// the window previously created by the platform.
+//
+// More than one Window is not supported on iOS, Android, WebAssembly.
+type Window struct {
+	initialOpts    []Option
+	initialActions []system.Action
+
+	ctx context
+	gpu gpu.GPU
+	// timer tracks the delayed invalidate goroutine.
+	timer struct {
+		// quit is shuts down the goroutine.
+		quit chan struct{}
+		// update the invalidate time.
+		update chan time.Time
+	}
+
+	animating    bool
+	hasNextFrame bool
+	nextFrame    time.Time
+	// viewport is the latest frame size with insets applied.
+	viewport image.Rectangle
+	// metric is the metric from the most recent frame.
+	metric      unit.Metric
+	queue       input.Router
+	cursor      pointer.Cursor
+	decorations struct {
+		op.Ops
+		// enabled tracks the Decorated option as
+		// given to the Option method. It may differ
+		// from Config.Decorated depending on platform
+		// capability.
+		enabled bool
+		Config
+		height        unit.Dp
+		currentHeight int
+		*material.Theme
+		*widget.Decorations
+	}
+	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     input.SemanticID
+		prevTree []input.SemanticNode
+		tree     []input.SemanticNode
+		ids      map[input.SemanticID]input.SemanticNode
+	}
+	imeState editorState
+	driver   driver
+	// gpuErr tracks the GPU error that is to be reported when
+	// the window is closed.
+	gpuErr error
+
+	// invMu protects mayInvalidate.
+	invMu         sync.Mutex
+	mayInvalidate bool
+
+	// coalesced tracks the most recent events waiting to be delivered
+	// to the client.
+	coalesced eventSummary
+	// frame tracks the most recent frame event.
+	lastFrame struct {
+		sync bool
+		size image.Point
+		off  image.Point
+		deco op.CallOp
+	}
+}
+
+type eventSummary struct {
+	wakeup       bool
+	cfg          *ConfigEvent
+	view         *ViewEvent
+	frame        *frameEvent
+	framePending bool
+	destroy      *DestroyEvent
+}
+
+type callbacks struct {
+	w *Window
+}
+
+func decoHeightOpt(h unit.Dp) Option {
+	return func(m unit.Metric, c *Config) {
+		c.decoHeight = h
+	}
+}
+
+func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, sigChan chan<- struct{}) error {
+	signal := func() {
+		if sigChan != nil {
+			// We're done with frame, let the client continue.
+			sigChan <- struct{}{}
+			// Signal at most once.
+			sigChan = nil
+		}
+	}
+	defer signal()
+	for {
+		if w.gpu == nil && !w.nocontext {
+			var err error
+			if w.ctx == nil {
+				w.ctx, err = w.driver.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.ctx != nil {
+			if err := w.ctx.Lock(); err != nil {
+				w.destroyGPU()
+				return err
+			}
+		}
+		if w.gpu == nil && !w.nocontext {
+			gpu, err := gpu.New(w.ctx.API())
+			if err != nil {
+				w.ctx.Unlock()
+				w.destroyGPU()
+				return err
+			}
+			w.gpu = gpu
+		}
+		if w.gpu != nil {
+			if err := w.frame(frame, size); err != nil {
+				w.ctx.Unlock()
+				if errors.Is(err, errOutOfDate) {
+					// GPU surface needs refreshing.
+					sync = true
+					continue
+				}
+				w.destroyGPU()
+				if errors.Is(err, gpu.ErrDeviceLost) {
+					continue
+				}
+				return err
+			}
+		}
+		w.queue.Frame(frame)
+		// Let the client continue as soon as possible, in particular before
+		// a potentially blocking Present.
+		signal()
+		var err error
+		if w.gpu != nil {
+			err = w.ctx.Present()
+			w.ctx.Unlock()
+		}
+		return err
+	}
+}
+
+func (w *Window) frame(frame *op.Ops, viewport image.Point) error {
+	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
+	}
+	return w.gpu.Frame(frame, target, viewport)
+}
+
+func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
+	w.coalesced.framePending = false
+	wrapper := &w.decorations.Ops
+	off := op.Offset(w.lastFrame.off).Push(wrapper)
+	ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal))
+	off.Pop()
+	w.lastFrame.deco.Add(wrapper)
+	if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil {
+		w.destroyGPU()
+		w.gpuErr = err
+		w.driver.Perform(system.ActionClose)
+		return
+	}
+	w.updateState()
+	w.updateCursor()
+}
+
+func (w *Window) updateState() {
+	for k := range w.semantic.ids {
+		delete(w.semantic.ids, k)
+	}
+	w.semantic.uptodate = false
+	q := &w.queue
+	switch q.TextInputState() {
+	case input.TextInputOpen:
+		w.driver.ShowTextInput(true)
+	case input.TextInputClose:
+		w.driver.ShowTextInput(false)
+	}
+	if hint, ok := q.TextInputHint(); ok {
+		w.driver.SetInputHint(hint)
+	}
+	if mime, txt, ok := q.WriteClipboard(); ok {
+		w.driver.WriteClipboard(mime, txt)
+	}
+	if q.ClipboardRequested() {
+		w.driver.ReadClipboard()
+	}
+	oldState := w.imeState
+	newState := oldState
+	newState.EditorState = q.EditorState()
+	if newState != oldState {
+		w.imeState = newState
+		w.driver.EditorStateChanged(oldState, newState)
+	}
+	if t, ok := q.WakeupTime(); ok {
+		w.setNextFrame(t)
+	}
+	w.updateAnimation()
+}
+
+// Invalidate the window such that a [FrameEvent] will be generated immediately.
+// If the window is inactive, an unspecified event is sent instead.
+//
+// Note that Invalidate is intended for externally triggered updates, such as a
+// response from a network request. The [op.InvalidateCmd] command is more efficient
+// for animation.
+//
+// Invalidate is safe for concurrent use.
+func (w *Window) Invalidate() {
+	w.invMu.Lock()
+	defer w.invMu.Unlock()
+	if w.mayInvalidate {
+		w.mayInvalidate = false
+		w.driver.Invalidate()
+	}
+}
+
+// Option applies the options to the window. The options are hints; the platform is
+// free to ignore or adjust them.
+func (w *Window) Option(opts ...Option) {
+	if len(opts) == 0 {
+		return
+	}
+	if w.driver == nil {
+		w.initialOpts = append(w.initialOpts, opts...)
+		return
+	}
+	w.Run(func() {
+		cnf := Config{Decorated: w.decorations.enabled}
+		for _, opt := range opts {
+			opt(w.metric, &cnf)
+		}
+		w.decorations.enabled = cnf.Decorated
+		decoHeight := w.decorations.height
+		if !w.decorations.enabled {
+			decoHeight = 0
+		}
+		opts = append(opts, decoHeightOpt(decoHeight))
+		w.driver.Configure(opts)
+		w.setNextFrame(time.Time{})
+		w.updateAnimation()
+	})
+}
+
+// Run f in the same thread as the native window event loop, and wait for f to
+// return or the window to close. If the window has not yet been created,
+// Run calls f directly.
+//
+// Note that most programs should not call Run; configuring a Window with
+// [CustomRenderer] is a notable exception.
+func (w *Window) Run(f func()) {
+	if w.driver == nil {
+		f()
+		return
+	}
+	done := make(chan struct{})
+	w.driver.Run(func() {
+		defer close(done)
+		f()
+	})
+	<-done
+}
+
+func (w *Window) updateAnimation() {
+	if w.driver == nil {
+		return
+	}
+	animate := false
+	if w.hasNextFrame {
+		if dt := time.Until(w.nextFrame); dt <= 0 {
+			animate = true
+		} else {
+			// Schedule redraw.
+			w.scheduleInvalidate(w.nextFrame)
+		}
+	}
+	if animate != w.animating {
+		w.animating = animate
+		w.driver.SetAnimating(animate)
+	}
+}
+
+func (w *Window) scheduleInvalidate(t time.Time) {
+	if w.timer.quit == nil {
+		w.timer.quit = make(chan struct{})
+		w.timer.update = make(chan time.Time)
+		go func() {
+			var timer *time.Timer
+			for {
+				var timeC <-chan time.Time
+				if timer != nil {
+					timeC = timer.C
+				}
+				select {
+				case <-w.timer.quit:
+					w.timer.quit <- struct{}{}
+					return
+				case t := <-w.timer.update:
+					if timer != nil {
+						timer.Stop()
+					}
+					timer = time.NewTimer(time.Until(t))
+				case <-timeC:
+					w.Invalidate()
+				}
+			}
+		}()
+	}
+	w.timer.update <- t
+}
+
+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) {
+	if d == nil {
+		panic("nil driver")
+	}
+	c.w.invMu.Lock()
+	defer c.w.invMu.Unlock()
+	c.w.driver = d
+}
+
+func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) {
+	c.w.processFrame(frame, ack)
+}
+
+func (c *callbacks) ProcessEvent(e event.Event) bool {
+	return c.w.processEvent(e)
+}
+
+// SemanticRoot returns the ID of the semantic root.
+func (c *callbacks) SemanticRoot() input.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 input.SemanticID) (input.SemanticNode, bool) {
+	c.w.updateSemantics()
+	n, found := c.w.semantic.ids[semID]
+	return n, found
+}
+
+func (c *callbacks) AppendSemanticDiffs(diffs []input.SemanticID) []input.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) (input.SemanticID, bool) {
+	c.w.updateSemantics()
+	return c.w.queue.SemanticAt(pos)
+}
+
+func (c *callbacks) EditorState() editorState {
+	return c.w.imeState
+}
+
+func (c *callbacks) SetComposingRegion(r key.Range) {
+	c.w.imeState.compose = r
+}
+
+func (c *callbacks) EditorInsert(text string) {
+	sel := c.w.imeState.Selection.Range
+	c.EditorReplace(sel, text)
+	start := sel.Start
+	if sel.End < start {
+		start = sel.End
+	}
+	sel.Start = start + utf8.RuneCountInString(text)
+	sel.End = sel.Start
+	c.SetEditorSelection(sel)
+}
+
+func (c *callbacks) EditorReplace(r key.Range, text string) {
+	c.w.imeState.Replace(r, text)
+	c.w.driver.ProcessEvent(key.EditEvent{Range: r, Text: text})
+	c.w.driver.ProcessEvent(key.SnippetEvent(c.w.imeState.Snippet.Range))
+}
+
+func (c *callbacks) SetEditorSelection(r key.Range) {
+	c.w.imeState.Selection.Range = r
+	c.w.driver.ProcessEvent(key.SelectionEvent(r))
+}
+
+func (c *callbacks) SetEditorSnippet(r key.Range) {
+	if sn := c.EditorState().Snippet.Range; sn == r {
+		// No need to expand.
+		return
+	}
+	c.w.driver.ProcessEvent(key.SnippetEvent(r))
+}
+
+func (w *Window) moveFocus(dir key.FocusDirection) {
+	w.queue.MoveFocus(dir)
+	if _, handled := w.queue.WakeupTime(); handled {
+		w.queue.RevealFocus(w.viewport)
+	} else {
+		var v image.Point
+		switch dir {
+		case key.FocusRight:
+			v = image.Pt(+1, 0)
+		case key.FocusLeft:
+			v = image.Pt(-1, 0)
+		case key.FocusDown:
+			v = image.Pt(0, +1)
+		case key.FocusUp:
+			v = image.Pt(0, -1)
+		default:
+			return
+		}
+		const scrollABit = unit.Dp(50)
+		dist := v.Mul(int(w.metric.Dp(scrollABit)))
+		w.queue.ScrollFocus(dist)
+	}
+}
+
+func (c *callbacks) ClickFocus() {
+	c.w.queue.ClickFocus()
+	c.w.setNextFrame(time.Time{})
+	c.w.updateAnimation()
+}
+
+func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) {
+	return c.w.queue.ActionAt(p)
+}
+
+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
+	}
+}
+
+// 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.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 *[]input.SemanticID, n input.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 (c *callbacks) Invalidate() {
+	c.w.setNextFrame(time.Time{})
+	c.w.updateAnimation()
+	// Guarantee a wakeup, even when not animating.
+	c.w.processEvent(wakeupEvent{})
+}
+
+func (c *callbacks) nextEvent() (event.Event, bool) {
+	return c.w.nextEvent()
+}
+
+func (w *Window) nextEvent() (event.Event, bool) {
+	s := &w.coalesced
+	defer func() {
+		// Every event counts as a wakeup.
+		s.wakeup = false
+	}()
+	switch {
+	case s.framePending:
+		// If the user didn't call FrameEvent.Event, process
+		// an empty frame.
+		w.processFrame(new(op.Ops), nil)
+	case s.view != nil:
+		e := *s.view
+		s.view = nil
+		return e, true
+	case s.destroy != nil:
+		e := *s.destroy
+		// Clear pending events after DestroyEvent is delivered.
+		*s = eventSummary{}
+		return e, true
+	case s.cfg != nil:
+		e := *s.cfg
+		s.cfg = nil
+		return e, true
+	case s.frame != nil:
+		e := *s.frame
+		s.frame = nil
+		s.framePending = true
+		return e.FrameEvent, true
+	case s.wakeup:
+		return wakeupEvent{}, true
+	}
+	w.invMu.Lock()
+	defer w.invMu.Unlock()
+	w.mayInvalidate = w.driver != nil
+	return nil, false
+}
+
+func (w *Window) processEvent(e event.Event) bool {
+	switch e2 := e.(type) {
+	case wakeupEvent:
+		w.coalesced.wakeup = true
+	case frameEvent:
+		if e2.Size == (image.Point{}) {
+			panic(errors.New("internal error: zero-sized Draw"))
+		}
+		w.metric = e2.Metric
+		w.hasNextFrame = false
+		e2.Frame = w.driver.Frame
+		e2.Source = w.queue.Source()
+		// Prepare the decorations and update the frame insets.
+		viewport := image.Rectangle{
+			Min: image.Point{
+				X: e2.Metric.Dp(e2.Insets.Left),
+				Y: e2.Metric.Dp(e2.Insets.Top),
+			},
+			Max: image.Point{
+				X: e2.Size.X - e2.Metric.Dp(e2.Insets.Right),
+				Y: e2.Size.Y - e2.Metric.Dp(e2.Insets.Bottom),
+			},
+		}
+		// Scroll to focus if viewport is shrinking in any dimension.
+		if old, new := w.viewport.Size(), viewport.Size(); new.X < old.X || new.Y < old.Y {
+			w.queue.RevealFocus(viewport)
+		}
+		w.viewport = viewport
+		wrapper := &w.decorations.Ops
+		wrapper.Reset()
+		m := op.Record(wrapper)
+		offset := w.decorate(e2.FrameEvent, wrapper)
+		w.lastFrame.deco = m.Stop()
+		w.lastFrame.size = e2.Size
+		w.lastFrame.sync = e2.Sync
+		w.lastFrame.off = offset
+		e2.Size = e2.Size.Sub(offset)
+		w.coalesced.frame = &e2
+	case DestroyEvent:
+		if w.gpuErr != nil {
+			e2.Err = w.gpuErr
+		}
+		w.destroyGPU()
+		w.invMu.Lock()
+		w.mayInvalidate = false
+		w.driver = nil
+		w.invMu.Unlock()
+		if q := w.timer.quit; q != nil {
+			q <- struct{}{}
+			<-q
+		}
+		w.coalesced.destroy = &e2
+	case ViewEvent:
+		if !e2.Valid() && w.gpu != nil {
+			w.ctx.Lock()
+			w.gpu.Release()
+			w.gpu = nil
+			w.ctx.Unlock()
+		}
+		w.coalesced.view = &e2
+	case ConfigEvent:
+		w.decorations.Decorations.Maximized = e2.Config.Mode == Maximized
+		wasFocused := w.decorations.Config.Focused
+		w.decorations.Config = e2.Config
+		e2.Config = w.effectiveConfig()
+		w.coalesced.cfg = &e2
+		if f := w.decorations.Config.Focused; f != wasFocused {
+			w.queue.Queue(key.FocusEvent{Focus: f})
+		}
+		t, handled := w.queue.WakeupTime()
+		if handled {
+			w.setNextFrame(t)
+			w.updateAnimation()
+		}
+		return handled
+	case event.Event:
+		focusDir := key.FocusDirection(-1)
+		if e, ok := e2.(key.Event); ok && e.State == key.Press {
+			isMobile := runtime.GOOS == "ios" || runtime.GOOS == "android"
+			switch {
+			case e.Name == key.NameTab && e.Modifiers == 0:
+				focusDir = key.FocusForward
+			case e.Name == key.NameTab && e.Modifiers == key.ModShift:
+				focusDir = key.FocusBackward
+			case e.Name == key.NameUpArrow && e.Modifiers == 0 && isMobile:
+				focusDir = key.FocusUp
+			case e.Name == key.NameDownArrow && e.Modifiers == 0 && isMobile:
+				focusDir = key.FocusDown
+			case e.Name == key.NameLeftArrow && e.Modifiers == 0 && isMobile:
+				focusDir = key.FocusLeft
+			case e.Name == key.NameRightArrow && e.Modifiers == 0 && isMobile:
+				focusDir = key.FocusRight
+			}
+		}
+		e := e2
+		if focusDir != -1 {
+			e = input.SystemEvent{Event: e}
+		}
+		w.queue.Queue(e)
+		t, handled := w.queue.WakeupTime()
+		if focusDir != -1 && !handled {
+			w.moveFocus(focusDir)
+			t, handled = w.queue.WakeupTime()
+		}
+		w.updateCursor()
+		if handled {
+			w.setNextFrame(t)
+			w.updateAnimation()
+		}
+		return handled
+	}
+	return true
+}
+
+// Event blocks until an event is received from the window, such as
+// [FrameEvent], or until [Invalidate] is called. The window is created
+// and shown the first time Event is called.
+func (w *Window) Event() event.Event {
+	if w.driver == nil {
+		w.init()
+	}
+	if w.driver == nil {
+		e, ok := w.nextEvent()
+		if !ok {
+			panic("window initialization failed without a DestroyEvent")
+		}
+		return e
+	}
+	return w.driver.Event()
+}
+
+func (w *Window) init() {
+	debug.Parse()
+	// Measure decoration height.
+	deco := new(widget.Decorations)
+	theme := material.NewTheme()
+	theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular()))
+	decoStyle := material.Decorations(theme, deco, 0, "")
+	gtx := layout.Context{
+		Ops: new(op.Ops),
+		// Measure in Dp.
+		Metric: unit.Metric{},
+	}
+	// Allow plenty of space.
+	gtx.Constraints.Max.Y = 200
+	dims := decoStyle.Layout(gtx)
+	decoHeight := unit.Dp(dims.Size.Y)
+	defaultOptions := []Option{
+		Size(800, 600),
+		Title("Gio"),
+		Decorated(true),
+		decoHeightOpt(decoHeight),
+	}
+	options := append(defaultOptions, w.initialOpts...)
+	w.initialOpts = nil
+	var cnf Config
+	cnf.apply(unit.Metric{}, options)
+
+	w.nocontext = cnf.CustomRenderer
+	w.decorations.Theme = theme
+	w.decorations.Decorations = deco
+	w.decorations.enabled = cnf.Decorated
+	w.decorations.height = decoHeight
+	w.imeState.compose = key.Range{Start: -1, End: -1}
+	w.semantic.ids = make(map[input.SemanticID]input.SemanticNode)
+	newWindow(&callbacks{w}, options)
+	for _, acts := range w.initialActions {
+		w.Perform(acts)
+	}
+	w.initialActions = nil
+}
+
+func (w *Window) updateCursor() {
+	if c := w.queue.Cursor(); c != w.cursor {
+		w.cursor = c
+		w.driver.SetCursor(c)
+	}
+}
+
+func (w *Window) fallbackDecorate() bool {
+	cnf := w.decorations.Config
+	return w.decorations.enabled && !cnf.Decorated && cnf.Mode != Fullscreen && !w.nocontext
+}
+
+// decorate the window if enabled and returns the corresponding Insets.
+func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
+	if !w.fallbackDecorate() {
+		return image.Pt(0, 0)
+	}
+	deco := w.decorations.Decorations
+	allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize |
+		system.ActionClose | system.ActionMove
+	style := material.Decorations(w.decorations.Theme, deco, allActions, w.decorations.Config.Title)
+	// Update the decorations based on the current window mode.
+	var actions system.Action
+	switch m := w.decorations.Config.Mode; m {
+	case Windowed:
+		actions |= system.ActionUnmaximize
+	case Minimized:
+		actions |= system.ActionMinimize
+	case Maximized:
+		actions |= system.ActionMaximize
+	case Fullscreen:
+		actions |= system.ActionFullscreen
+	default:
+		panic(fmt.Errorf("unknown WindowMode %v", m))
+	}
+	gtx := layout.Context{
+		Ops:         o,
+		Now:         e.Now,
+		Source:      e.Source,
+		Metric:      e.Metric,
+		Constraints: layout.Exact(e.Size),
+	}
+	// Update the window based on the actions on the decorations.
+	opts, acts := splitActions(deco.Update(gtx))
+	if len(opts) > 0 {
+		w.driver.Configure(opts)
+	}
+	if acts != 0 {
+		w.driver.Perform(acts)
+	}
+	style.Layout(gtx)
+	// Offset to place the frame content below the decorations.
+	decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
+	if w.decorations.currentHeight != decoHeight {
+		w.decorations.currentHeight = decoHeight
+		w.coalesced.cfg = &ConfigEvent{Config: w.effectiveConfig()}
+	}
+	return image.Pt(0, decoHeight)
+}
+
+func (w *Window) effectiveConfig() Config {
+	cnf := w.decorations.Config
+	cnf.Size.Y -= w.decorations.currentHeight
+	cnf.Decorated = w.decorations.enabled || cnf.Decorated
+	return cnf
+}
+
+// splitActions splits options from actions and return them and the remaining
+// actions.
+func splitActions(actions system.Action) ([]Option, system.Action) {
+	var opts []Option
+	walkActions(actions, func(action system.Action) {
+		switch action {
+		case system.ActionMinimize:
+			opts = append(opts, Minimized.Option())
+		case system.ActionMaximize:
+			opts = append(opts, Maximized.Option())
+		case system.ActionUnmaximize:
+			opts = append(opts, Windowed.Option())
+		case system.ActionFullscreen:
+			opts = append(opts, Fullscreen.Option())
+		default:
+			return
+		}
+		actions &^= action
+	})
+	return opts, actions
+}
+
+// Perform the actions on the window.
+func (w *Window) Perform(actions system.Action) {
+	opts, acts := splitActions(actions)
+	w.Option(opts...)
+	if acts == 0 {
+		return
+	}
+	if w.driver == nil {
+		w.initialActions = append(w.initialActions, acts)
+		return
+	}
+	w.Run(func() {
+		w.driver.Perform(actions)
+	})
+}
+
+// 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 mode will be changed to Windowed.
+func Size(w, h unit.Dp) Option {
+	if w <= 0 {
+		panic("width must be larger than or equal to 0")
+	}
+	if h <= 0 {
+		panic("height must be larger than or equal to 0")
+	}
+	return func(m unit.Metric, cnf *Config) {
+		cnf.Mode = Windowed
+		cnf.Size = image.Point{
+			X: m.Dp(w),
+			Y: m.Dp(h),
+		}
+	}
+}
+
+// MaxSize sets the maximum size of the window.
+func MaxSize(w, h unit.Dp) Option {
+	if w <= 0 {
+		panic("width must be larger than or equal to 0")
+	}
+	if h <= 0 {
+		panic("height must be larger than or equal to 0")
+	}
+	return func(m unit.Metric, cnf *Config) {
+		cnf.MaxSize = image.Point{
+			X: m.Dp(w),
+			Y: m.Dp(h),
+		}
+	}
+}
+
+// MinSize sets the minimum size of the window.
+func MinSize(w, h unit.Dp) Option {
+	if w <= 0 {
+		panic("width must be larger than or equal to 0")
+	}
+	if h <= 0 {
+		panic("height must be larger than or equal to 0")
+	}
+	return func(m unit.Metric, cnf *Config) {
+		cnf.MinSize = image.Point{
+			X: m.Dp(w),
+			Y: m.Dp(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.
+//
+// Caller must assume responsibility for rendering which includes
+// initializing the render backend, swapping the framebuffer and
+// handling frame pacing.
+func CustomRenderer(custom bool) Option {
+	return func(_ unit.Metric, cnf *Config) {
+		cnf.CustomRenderer = custom
+	}
+}
+
+// Decorated controls whether Gio and/or the platform are responsible
+// for drawing window decorations. Providing false indicates that
+// the application will either be undecorated or will draw its own decorations.
+func Decorated(enabled bool) Option {
+	return func(_ unit.Metric, cnf *Config) {
+		cnf.Decorated = enabled
+	}
+}
+
+// flushEvent is sent to detect when the user program
+// has completed processing of all prior events. Its an
+// [io/event.Event] but only for internal use.
+type flushEvent struct{}
+
+func (t flushEvent) ImplementsEvent() {}
+
+// theFlushEvent avoids allocating garbage when sending
+// flushEvents.
+var theFlushEvent flushEvent

+ 172 - 0
vendor/gioui.org/f32/affine.go

@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package f32
+
+import (
+	"math"
+	"strconv"
+)
+
+// Affine2D represents an affine 2D transformation. The zero value of 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
+}
+
+// Split a transform into two parts, one which is pure offset and the
+// other representing the scaling, shearing and rotation part.
+func (a *Affine2D) Split() (srs Affine2D, offset Point) {
+	return Affine2D{
+		a: a.a, b: a.b, c: 0,
+		d: a.d, e: a.e, f: 0,
+	}, Point{X: a.c, Y: 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()
+
+	// precision 6, one period, negative sign and space per number
+	const prec = 6
+	const charsPerFloat = prec + 2 + 1
+	s := make([]byte, 0, 6*charsPerFloat+6)
+
+	s = append(s, '[', '[')
+	s = strconv.AppendFloat(s, float64(sx), 'g', prec, 32)
+	s = append(s, ' ')
+	s = strconv.AppendFloat(s, float64(hx), 'g', prec, 32)
+	s = append(s, ' ')
+	s = strconv.AppendFloat(s, float64(ox), 'g', prec, 32)
+	s = append(s, ']', ' ', '[')
+	s = strconv.AppendFloat(s, float64(hy), 'g', prec, 32)
+	s = append(s, ' ')
+	s = strconv.AppendFloat(s, float64(sy), 'g', prec, 32)
+	s = append(s, ' ')
+	s = strconv.AppendFloat(s, float64(oy), 'g', prec, 32)
+	s = append(s, ']', ']')
+
+	return string(s)
+}

+ 60 - 0
vendor/gioui.org/f32/f32.go

@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+/*
+Package f32 is a float32 implementation of package image's
+Point and affine transformations.
+
+The coordinate space has the origin in the top left
+corner with the axes extending right and down.
+*/
+package f32
+
+import (
+	"image"
+	"math"
+	"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) + ")"
+}
+
+// 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}
+}
+
+// Round returns the integer point closest to p.
+func (p Point) Round() image.Point {
+	return image.Point{
+		X: int(math.Round(float64(p.X))),
+		Y: int(math.Round(float64(p.Y))),
+	}
+}

+ 126 - 0
vendor/gioui.org/font/font.go

@@ -0,0 +1,126 @@
+/*
+Package font provides type describing font faces attributes.
+*/
+package font
+
+import (
+	"github.com/go-text/typesetting/font"
+)
+
+// A FontFace is a Font and a matching Face.
+type FontFace struct {
+	Font Font
+	Face Face
+}
+
+// Style is the font style.
+type Style int
+
+// Weight is a font weight, in CSS units subtracted 400 so the zero value
+// is normal text weight.
+type Weight int
+
+// Font specify a particular typeface variant, style and weight.
+type Font struct {
+	// Typeface specifies the name(s) of the the font faces to try. See [Typeface]
+	// for details.
+	Typeface Typeface
+	// Style specifies the kind of text style.
+	Style Style
+	// Weight is the text weight.
+	Weight Weight
+}
+
+// Face is an opaque handle to a typeface. The concrete implementation depends
+// upon the kind of font and shaper in use.
+type Face interface {
+	Face() *font.Face
+}
+
+// Typeface identifies a list of font families to attempt to use for displaying
+// a string. The syntax is a comma-delimited list of family names. In order to
+// allow for the remote possibility of needing to express a font family name
+// containing a comma, name entries may be quoted using either single or double
+// quotes. Within quotes, a literal quotation mark can be expressed by escaping
+// it with `\`. A literal backslash may be expressed by escaping it with another
+// `\`.
+//
+// Here's an example Typeface:
+//
+//	Times New Roman, Georgia, serif
+//
+// This is equivalent to the above:
+//
+//	"Times New Roman", 'Georgia', serif
+//
+// Here are some valid uses of escape sequences:
+//
+//	"Contains a literal \" doublequote", 'Literal \' Singlequote', "\\ Literal backslash", '\\ another'
+//
+// This syntax has the happy side effect that most CSS "font-family" rules are
+// valid Typefaces (without the trailing semicolon).
+//
+// Generic CSS font families are supported, and are automatically expanded to lists
+// of known font families with a matching style. The supported generic families are:
+//
+//   - fantasy
+//   - math
+//   - emoji
+//   - serif
+//   - sans-serif
+//   - cursive
+//   - monospace
+type Typeface string
+
+const (
+	Regular Style = iota
+	Italic
+)
+
+const (
+	Thin       Weight = -300
+	ExtraLight Weight = -200
+	Light      Weight = -100
+	Normal     Weight = 0
+	Medium     Weight = 100
+	SemiBold   Weight = 200
+	Bold       Weight = 300
+	ExtraBold  Weight = 400
+	Black      Weight = 500
+)
+
+func (s Style) String() string {
+	switch s {
+	case Regular:
+		return "Regular"
+	case Italic:
+		return "Italic"
+	default:
+		panic("invalid Style")
+	}
+}
+
+func (w Weight) String() string {
+	switch w {
+	case Thin:
+		return "Thin"
+	case ExtraLight:
+		return "ExtraLight"
+	case Light:
+		return "Light"
+	case Normal:
+		return "Normal"
+	case Medium:
+		return "Medium"
+	case SemiBold:
+		return "SemiBold"
+	case Bold:
+		return "Bold"
+	case ExtraBold:
+		return "ExtraBold"
+	case Black:
+		return "Black"
+	default:
+		panic("invalid Weight")
+	}
+}

+ 83 - 0
vendor/gioui.org/font/gofont/gofont.go

@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// Package gofont exports the Go fonts as a text.Collection.
+//
+// See https://blog.golang.org/go-fonts for a description of the
+// fonts, and the golang.org/x/image/font/gofont packages for the
+// font data.
+package gofont
+
+import (
+	"fmt"
+	"sync"
+
+	"golang.org/x/image/font/gofont/gobold"
+	"golang.org/x/image/font/gofont/gobolditalic"
+	"golang.org/x/image/font/gofont/goitalic"
+	"golang.org/x/image/font/gofont/gomedium"
+	"golang.org/x/image/font/gofont/gomediumitalic"
+	"golang.org/x/image/font/gofont/gomono"
+	"golang.org/x/image/font/gofont/gomonobold"
+	"golang.org/x/image/font/gofont/gomonobolditalic"
+	"golang.org/x/image/font/gofont/gomonoitalic"
+	"golang.org/x/image/font/gofont/goregular"
+	"golang.org/x/image/font/gofont/gosmallcaps"
+	"golang.org/x/image/font/gofont/gosmallcapsitalic"
+
+	"gioui.org/font"
+	"gioui.org/font/opentype"
+)
+
+var (
+	regOnce    sync.Once
+	reg        []font.FontFace
+	once       sync.Once
+	collection []font.FontFace
+)
+
+func loadRegular() {
+	regOnce.Do(func() {
+		faces, err := opentype.ParseCollection(goregular.TTF)
+		if err != nil {
+			panic(fmt.Errorf("failed to parse font: %v", err))
+		}
+		reg = faces
+		collection = append(collection, reg[0])
+	})
+}
+
+// Regular returns a collection of only the Go regular font face.
+func Regular() []font.FontFace {
+	loadRegular()
+	return reg
+}
+
+// Regular returns a collection of all available Go font faces.
+func Collection() []font.FontFace {
+	loadRegular()
+	once.Do(func() {
+		register(goitalic.TTF)
+		register(gobold.TTF)
+		register(gobolditalic.TTF)
+		register(gomedium.TTF)
+		register(gomediumitalic.TTF)
+		register(gomono.TTF)
+		register(gomonobold.TTF)
+		register(gomonobolditalic.TTF)
+		register(gomonoitalic.TTF)
+		register(gosmallcaps.TTF)
+		register(gosmallcapsitalic.TTF)
+		// Ensure that any outside appends will not reuse the backing store.
+		n := len(collection)
+		collection = collection[:n:n]
+	})
+	return collection
+}
+
+func register(ttf []byte) {
+	faces, err := opentype.ParseCollection(ttf)
+	if err != nil {
+		panic(fmt.Errorf("failed to parse font: %v", err))
+	}
+	collection = append(collection, faces[0])
+}

+ 190 - 0
vendor/gioui.org/font/opentype/opentype.go

@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// Package opentype implements text layout and shaping for OpenType
+// files.
+//
+// NOTE: the OpenType specification allows for fonts to include bitmap images
+// in a variety of formats. In the interest of small binary sizes, the opentype
+// package only automatically imports the PNG image decoder. If you have a font
+// with glyphs in JPEG or TIFF formats, register those decoders with the image
+// package in order to ensure those glyphs are visible in text.
+package opentype
+
+import (
+	"bytes"
+	"fmt"
+	_ "image/png"
+
+	giofont "gioui.org/font"
+	fontapi "github.com/go-text/typesetting/font"
+	"github.com/go-text/typesetting/font/opentype"
+)
+
+// Face is a thread-safe representation of a loaded font. For efficiency, applications
+// should construct a face for any given font file once, reusing it across different
+// text shapers.
+type Face struct {
+	face *fontapi.Font
+	font giofont.Font
+}
+
+// Parse constructs a Face from source bytes.
+func Parse(src []byte) (Face, error) {
+	ld, err := opentype.NewLoader(bytes.NewReader(src))
+	if err != nil {
+		return Face{}, err
+	}
+	font, md, err := parseLoader(ld)
+	if err != nil {
+		return Face{}, fmt.Errorf("failed parsing truetype font: %w", err)
+	}
+	return Face{
+		face: font,
+		font: md,
+	}, nil
+}
+
+// ParseCollection parse an Opentype font file, with support for collections.
+// Single font files are supported, returning a slice with length 1.
+// The returned fonts are automatically wrapped in a text.FontFace with
+// inferred font font.
+// BUG(whereswaldon): the only Variant that can be detected automatically is
+// "Mono".
+func ParseCollection(src []byte) ([]giofont.FontFace, error) {
+	lds, err := opentype.NewLoaders(bytes.NewReader(src))
+	if err != nil {
+		return nil, err
+	}
+	out := make([]giofont.FontFace, len(lds))
+	for i, ld := range lds {
+		face, md, err := parseLoader(ld)
+		if err != nil {
+			return nil, fmt.Errorf("reading font %d of collection: %s", i, err)
+		}
+		ff := Face{
+			face: face,
+			font: md,
+		}
+		out[i] = giofont.FontFace{
+			Face: ff,
+			Font: ff.Font(),
+		}
+	}
+
+	return out, nil
+}
+
+func DescriptionToFont(md fontapi.Description) giofont.Font {
+	return giofont.Font{
+		Typeface: giofont.Typeface(md.Family),
+		Style:    gioStyle(md.Aspect.Style),
+		Weight:   gioWeight(md.Aspect.Weight),
+	}
+}
+
+func FontToDescription(font giofont.Font) fontapi.Description {
+	return fontapi.Description{
+		Family: string(font.Typeface),
+		Aspect: fontapi.Aspect{
+			Style:  mdStyle(font.Style),
+			Weight: mdWeight(font.Weight),
+		},
+	}
+}
+
+// parseLoader parses the contents of the loader into a face and its font.
+func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
+	ft, err := fontapi.NewFont(ld)
+	if err != nil {
+		return nil, giofont.Font{}, err
+	}
+	data := DescriptionToFont(ft.Describe())
+	return ft, data, nil
+}
+
+// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
+// Face many be invoked any number of times and is safe so long as each return value is
+// only used by one goroutine.
+func (f Face) Face() *fontapi.Face {
+	return &fontapi.Face{Font: f.face}
+}
+
+// FontFace returns a text.Font with populated font metadata for the
+// font.
+// BUG(whereswaldon): the only Variant that can be detected automatically is
+// "Mono".
+func (f Face) Font() giofont.Font {
+	return f.font
+}
+
+func gioStyle(s fontapi.Style) giofont.Style {
+	switch s {
+	case fontapi.StyleItalic:
+		return giofont.Italic
+	case fontapi.StyleNormal:
+		fallthrough
+	default:
+		return giofont.Regular
+	}
+}
+
+func mdStyle(g giofont.Style) fontapi.Style {
+	switch g {
+	case giofont.Italic:
+		return fontapi.StyleItalic
+	case giofont.Regular:
+		fallthrough
+	default:
+		return fontapi.StyleNormal
+	}
+}
+
+func gioWeight(w fontapi.Weight) giofont.Weight {
+	switch w {
+	case fontapi.WeightThin:
+		return giofont.Thin
+	case fontapi.WeightExtraLight:
+		return giofont.ExtraLight
+	case fontapi.WeightLight:
+		return giofont.Light
+	case fontapi.WeightNormal:
+		return giofont.Normal
+	case fontapi.WeightMedium:
+		return giofont.Medium
+	case fontapi.WeightSemibold:
+		return giofont.SemiBold
+	case fontapi.WeightBold:
+		return giofont.Bold
+	case fontapi.WeightExtraBold:
+		return giofont.ExtraBold
+	case fontapi.WeightBlack:
+		return giofont.Black
+	default:
+		return giofont.Normal
+	}
+}
+
+func mdWeight(g giofont.Weight) fontapi.Weight {
+	switch g {
+	case giofont.Thin:
+		return fontapi.WeightThin
+	case giofont.ExtraLight:
+		return fontapi.WeightExtraLight
+	case giofont.Light:
+		return fontapi.WeightLight
+	case giofont.Normal:
+		return fontapi.WeightNormal
+	case giofont.Medium:
+		return fontapi.WeightMedium
+	case giofont.SemiBold:
+		return fontapi.WeightSemibold
+	case giofont.Bold:
+		return fontapi.WeightBold
+	case giofont.ExtraBold:
+		return fontapi.WeightExtraBold
+	case giofont.Black:
+		return fontapi.WeightBlack
+	default:
+		return fontapi.WeightNormal
+	}
+}

+ 481 - 0
vendor/gioui.org/gesture/gesture.go

@@ -0,0 +1,481 @@
+// 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/input"
+	"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) {
+	event.Op(ops, h)
+}
+
+// Update state and report whether a pointer is inside the area.
+func (h *Hover) Update(q input.Source) bool {
+	for {
+		ev, ok := q.Event(pointer.Filter{
+			Target: h,
+			Kinds:  pointer.Enter | pointer.Leave | pointer.Cancel,
+		})
+		if !ok {
+			break
+		}
+		e, ok := ev.(pointer.Event)
+		if !ok {
+			continue
+		}
+		switch e.Kind {
+		case pointer.Leave, pointer.Cancel:
+			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
+	// hovered tracks whether the pointer is inside the gesture.
+	hovered bool
+	// entered tracks whether an Enter event has been received.
+	entered bool
+	// pid is the pointer.ID.
+	pid pointer.ID
+}
+
+// ClickEvent represent a click action, either a
+// KindPress for the beginning of a click or a
+// KindClick for a completed click.
+type ClickEvent struct {
+	Kind      ClickKind
+	Position  image.Point
+	Source    pointer.Source
+	Modifiers key.Modifiers
+	// NumClicks records successive clicks occurring
+	// within a short duration of each other.
+	NumClicks int
+}
+
+type ClickKind 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
+}
+
+// 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
+	estimator fling.Extrapolation
+	flinger   fling.Animation
+	pid       pointer.ID
+	last      int
+	// Leftover scroll.
+	scroll float32
+}
+
+type ScrollState uint8
+
+type Axis uint8
+
+const (
+	Horizontal Axis = iota
+	Vertical
+	Both
+)
+
+const (
+	// KindPress is reported for the first pointer
+	// press.
+	KindPress ClickKind = iota
+	// KindClick is reported when a click action
+	// is complete.
+	KindClick
+	// KindCancel is reported when the gesture is
+	// cancelled.
+	KindCancel
+)
+
+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
+)
+
+const touchSlop = unit.Dp(3)
+
+// Add the handler to the operation list to receive click events.
+func (c *Click) Add(ops *op.Ops) {
+	event.Op(ops, c)
+}
+
+// Hovered returns whether a pointer is inside the area.
+func (c *Click) Hovered() bool {
+	return c.hovered
+}
+
+// Pressed returns whether a pointer is pressing.
+func (c *Click) Pressed() bool {
+	return c.pressed
+}
+
+// Update state and return the next click events, if any.
+func (c *Click) Update(q input.Source) (ClickEvent, bool) {
+	for {
+		evt, ok := q.Event(pointer.Filter{
+			Target: c,
+			Kinds:  pointer.Press | pointer.Release | pointer.Enter | pointer.Leave | pointer.Cancel,
+		})
+		if !ok {
+			break
+		}
+		e, ok := evt.(pointer.Event)
+		if !ok {
+			continue
+		}
+		switch e.Kind {
+		case pointer.Release:
+			if !c.pressed || c.pid != e.PointerID {
+				break
+			}
+			c.pressed = false
+			if !c.entered || c.hovered {
+				return ClickEvent{
+					Kind:      KindClick,
+					Position:  e.Position.Round(),
+					Source:    e.Source,
+					Modifiers: e.Modifiers,
+					NumClicks: c.clicks,
+				}, true
+			} else {
+				return ClickEvent{Kind: KindCancel}, true
+			}
+		case pointer.Cancel:
+			wasPressed := c.pressed
+			c.pressed = false
+			c.hovered = false
+			c.entered = false
+			if wasPressed {
+				return ClickEvent{Kind: KindCancel}, true
+			}
+		case pointer.Press:
+			if c.pressed {
+				break
+			}
+			if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonPrimary {
+				break
+			}
+			if !c.hovered {
+				c.pid = e.PointerID
+			}
+			if c.pid != e.PointerID {
+				break
+			}
+			c.pressed = true
+			if e.Time-c.clickedAt < doubleClickDuration {
+				c.clicks++
+			} else {
+				c.clicks = 1
+			}
+			c.clickedAt = e.Time
+			return ClickEvent{Kind: KindPress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks}, true
+		case pointer.Leave:
+			if !c.pressed {
+				c.pid = e.PointerID
+			}
+			if c.pid == e.PointerID {
+				c.hovered = false
+			}
+		case pointer.Enter:
+			if !c.pressed {
+				c.pid = e.PointerID
+			}
+			if c.pid == e.PointerID {
+				c.hovered = true
+				c.entered = true
+			}
+		}
+	}
+	return ClickEvent{}, false
+}
+
+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 [pointer.Filter].
+func (s *Scroll) Add(ops *op.Ops) {
+	event.Op(ops, s)
+}
+
+// Stop any remaining fling movement.
+func (s *Scroll) Stop() {
+	s.flinger = fling.Animation{}
+}
+
+// Update state and report the scroll distance along axis.
+func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, scrollx, scrolly pointer.ScrollRange) int {
+	total := 0
+	f := pointer.Filter{
+		Target:  s,
+		Kinds:   pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
+		ScrollX: scrollx,
+		ScrollY: scrolly,
+	}
+	for {
+		evt, ok := q.Event(f)
+		if !ok {
+			break
+		}
+		e, ok := evt.(pointer.Event)
+		if !ok {
+			continue
+		}
+		switch e.Kind {
+		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(axis, 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.Dp(touchSlop)), fling.Distance; d < -slop || d > slop {
+				s.flinger.Start(cfg, t, fling.Velocity)
+			}
+			fallthrough
+		case pointer.Cancel:
+			s.dragging = false
+		case pointer.Scroll:
+			switch 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(axis, 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.Dp(touchSlop)
+				if dist := dist; dist >= slop || -slop >= dist {
+					q.Execute(pointer.GrabCmd{Tag: s, ID: e.PointerID})
+				}
+			} else {
+				s.last = v
+				total += dist
+			}
+		}
+	}
+	total += s.flinger.Tick(t)
+	if s.flinger.Active() {
+		q.Execute(op.InvalidateCmd{})
+	}
+	return total
+}
+
+func (s *Scroll) val(axis Axis, p f32.Point) float32 {
+	if 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) {
+	event.Op(ops, d)
+}
+
+// Update state and return the next drag event, if any.
+func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event, bool) {
+	for {
+		ev, ok := q.Event(pointer.Filter{
+			Target: d,
+			Kinds:  pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
+		})
+		if !ok {
+			break
+		}
+		e, ok := ev.(pointer.Event)
+		if !ok {
+			continue
+		}
+
+		switch e.Kind {
+		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.Dp(touchSlop)
+				if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
+					q.Execute(pointer.GrabCmd{Tag: d, ID: e.PointerID})
+				}
+			}
+		case pointer.Release, pointer.Cancel:
+			d.pressed = false
+			if !d.dragging || e.PointerID != d.pid {
+				continue
+			}
+			d.dragging = false
+		}
+
+		return e, true
+	}
+
+	return pointer.Event{}, false
+}
+
+// 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 ClickKind) String() string {
+	switch ct {
+	case KindPress:
+		return "KindPress"
+	case KindClick:
+		return "KindClick"
+	case KindCancel:
+		return "KindCancel"
+	default:
+		panic("invalid ClickKind")
+	}
+}
+
+func (s ScrollState) String() string {
+	switch s {
+	case StateIdle:
+		return "StateIdle"
+	case StateDragging:
+		return "StateDragging"
+	case StateFlinging:
+		return "StateFlinging"
+	default:
+		panic("unreachable")
+	}
+}

+ 40 - 0
vendor/gioui.org/gpu/api.go

@@ -0,0 +1,40 @@
+// 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

+ 153 - 0
vendor/gioui.org/gpu/caches.go

@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package gpu
+
+import (
+	"fmt"
+
+	"gioui.org/internal/f32"
+)
+
+type textureCacheKey struct {
+	filter byte
+	handle any
+}
+
+type textureCache struct {
+	res map[textureCacheKey]resourceCacheValue
+}
+
+type resourceCacheValue struct {
+	used     bool
+	resource 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 newTextureCache() *textureCache {
+	return &textureCache{
+		res: make(map[textureCacheKey]resourceCacheValue),
+	}
+}
+
+func (r *textureCache) get(key textureCacheKey) (resource, bool) {
+	v, exists := r.res[key]
+	if !exists {
+		return nil, false
+	}
+	if !v.used {
+		v.used = true
+		r.res[key] = v
+	}
+	return v.resource, exists
+}
+
+func (r *textureCache) put(key textureCacheKey, val resource) {
+	v, exists := r.res[key]
+	if exists && v.used {
+		panic(fmt.Errorf("key exists, %v", key))
+	}
+	v.used = true
+	v.resource = val
+	r.res[key] = v
+}
+
+func (r *textureCache) frame() {
+	for k, v := range r.res {
+		if v.used {
+			v.used = false
+			r.res[k] = v
+		} else {
+			delete(r.res, k)
+			v.resource.release()
+		}
+	}
+}
+
+func (r *textureCache) release() {
+	for _, v := range r.res {
+		v.resource.release()
+	}
+	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
+}

+ 146 - 0
vendor/gioui.org/gpu/clip.go

@@ -0,0 +1,146 @@
+package gpu
+
+import (
+	"encoding/binary"
+	"math"
+
+	"gioui.org/internal/f32"
+	"gioui.org/internal/stroke"
+)
+
+type quadSplitter struct {
+	bounds  f32.Rectangle
+	contour uint32
+	d       *drawOps
+
+	// scratch space used by calls to stroke.SplitCubic
+	scratch []stroke.QuadSegment
+}
+
+func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
+	// inlined code:
+	//   encodeVertex(data, meta, -1, 1, from, ctrl, to)
+	//   encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
+	//   encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
+	//   encodeVertex(data[vertStride*3:], meta, 1, -1, from, ctrl, to)
+	// this code needs to stay in sync with `vertex.encode`.
+
+	bo := binary.LittleEndian
+	data = data[:vertStride*4]
+
+	// encode the main template
+	bo.PutUint32(data[4:8], meta)
+	bo.PutUint32(data[8:12], math.Float32bits(from.X))
+	bo.PutUint32(data[12:16], math.Float32bits(from.Y))
+	bo.PutUint32(data[16:20], math.Float32bits(ctrl.X))
+	bo.PutUint32(data[20:24], math.Float32bits(ctrl.Y))
+	bo.PutUint32(data[24:28], math.Float32bits(to.X))
+	bo.PutUint32(data[28:32], math.Float32bits(to.Y))
+
+	copy(data[vertStride*1:vertStride*2], data[vertStride*0:vertStride*1])
+	copy(data[vertStride*2:vertStride*3], data[vertStride*0:vertStride*1])
+	copy(data[vertStride*3:vertStride*4], data[vertStride*0:vertStride*1])
+
+	bo.PutUint32(data[vertStride*0:vertStride*0+4], math.Float32bits(nwCorner))
+	bo.PutUint32(data[vertStride*1:vertStride*1+4], math.Float32bits(neCorner))
+	bo.PutUint32(data[vertStride*2:vertStride*2+4], math.Float32bits(swCorner))
+	bo.PutUint32(data[vertStride*3:vertStride*3+4], math.Float32bits(seCorner))
+}
+
+const (
+	nwCorner = 1*0.25 + 0*0.5
+	neCorner = 1*0.25 + 1*0.5
+	swCorner = 0*0.25 + 0*0.5
+	seCorner = 0*0.25 + 1*0.5
+)
+
+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
+}

+ 1592 - 0
vendor/gioui.org/gpu/gpu.go

@@ -0,0 +1,1592 @@
+// 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"
+	"errors"
+	"fmt"
+	"image"
+	"image/color"
+	"math"
+	"reflect"
+	"time"
+	"unsafe"
+
+	"gioui.org/gpu/internal/driver"
+	"gioui.org/internal/byteslice"
+	"gioui.org/internal/f32"
+	"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
+}
+
+type gpu struct {
+	cache *textureCache
+
+	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
+	layers        packer
+	layerFBOs     fboSet
+}
+
+type drawOps struct {
+	reader       ops.Reader
+	states       []f32.Affine2D
+	transStack   []f32.Affine2D
+	layers       []opacityLayer
+	opacityStack []int
+	vertCache    []byte
+	viewport     image.Point
+	clear        bool
+	clearColor   f32color.RGBA
+	imageOps     []imageOp
+	pathOps      []*pathOp
+	pathOpCache  []pathOp
+	qs           quadSplitter
+	pathCache    *opCache
+}
+
+type opacityLayer struct {
+	opacity float32
+	parent  int
+	// depth of the opacity stack. Layers of equal depth are
+	// independent and may be packed into one atlas.
+	depth int
+	// opStart and opEnd denote the range of drawOps.imageOps
+	// that belong to the layer.
+	opStart, opEnd int
+	// clip of the layer operations.
+	clip  image.Rectangle
+	place placement
+}
+
+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 is either a placement in the path fbos or intersection fbos,
+	// depending on clipType.
+	place placement
+	// layerOps is the number of operations this
+	// operation replaces.
+	layerOps int
+}
+
+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
+	opacity float32
+	// For materialTypeTexture.
+	data    imageOpData
+	tex     driver.Texture
+	uvTrans f32.Affine2D
+}
+
+const (
+	filterLinear  = 0
+	filterNearest = 1
+)
+
+// imageOpData is the shadow of paint.ImageOp.
+type imageOpData struct {
+	src    *image.RGBA
+	handle interface{}
+	filter byte
+}
+
+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,
+		filter: data[1],
+	}
+}
+
+func decodeColorOp(data []byte) color.NRGBA {
+	data = data[:ops.TypeColorLen]
+	return color.NRGBA{
+		R: data[1],
+		G: data[2],
+		B: data[3],
+		A: data[4],
+	}
+}
+
+func decodeLinearGradientOp(data []byte) linearGradientOpData {
+	data = data[:ops.TypeLinearGradientLen]
+	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 resource interface {
+	release()
+}
+
+type texture struct {
+	src *image.RGBA
+	tex driver.Texture
+}
+
+type blitter struct {
+	ctx                    driver.Device
+	viewport               image.Point
+	pipelines              [2][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
+	opacity       float32
+	fbo           float32
+	_             [2]float32
+}
+
+type colorUniforms struct {
+	color f32color.RGBA
+}
+
+type gradientUniforms struct {
+	color1 f32color.RGBA
+	color2 f32color.RGBA
+}
+
+type clipType uint8
+
+const (
+	clipTypeNone clipType = iota
+	clipTypePath
+	clipTypeIntersection
+)
+
+type materialType uint8
+
+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()
+	feats := d.Caps().Features
+	switch {
+	case feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
+		return newGPU(d)
+	}
+	return nil, errors.New("no available GPU driver")
+}
+
+func newGPU(ctx driver.Device) (*gpu, error) {
+	g := &gpu{
+		cache: newTextureCache(),
+	}
+	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)
+	if false && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
+		g.frameStart = time.Now()
+		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.drawOps.imageOps)
+	g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
+	g.renderer.drawLayers(g.drawOps.layers, 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(false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
+	g.coverTimer.end()
+	g.ctx.EndRenderPass()
+	g.cleanupTimer.begin()
+	g.cache.frame()
+	g.drawOps.pathCache.frame()
+	g.cleanupTimer.end()
+	if false && 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 *textureCache, data imageOpData) driver.Texture {
+	key := textureCacheKey{
+		filter: data.filter,
+		handle: data.handle,
+	}
+
+	var tex *texture
+	t, exists := cache.get(key)
+	if !exists {
+		t = &texture{
+			src: data.src,
+		}
+		cache.put(key, t)
+	}
+	tex = t.(*texture)
+	if tex.tex != nil {
+		return tex.tex
+	}
+
+	var minFilter, magFilter driver.TextureFilter
+	switch data.filter {
+	case filterLinear:
+		minFilter, magFilter = driver.FilterLinearMipmapLinear, driver.FilterLinear
+	case filterNearest:
+		minFilter, magFilter = driver.FilterNearest, driver.FilterNearest
+	}
+
+	handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA,
+		data.src.Bounds().Dx(), data.src.Bounds().Dy(),
+		minFilter, magFilter,
+		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
+	}
+	d := image.Pt(maxDim, maxDim)
+
+	r.packer.maxDims = d
+	r.intersections.maxDims = d
+	r.layers.maxDims = d
+	return r
+}
+
+func (r *renderer) release() {
+	r.pather.release()
+	r.blitter.release()
+	r.layerFBOs.delete(r.ctx, 0)
+}
+
+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 {
+		for _, p := range p {
+			p.Release()
+		}
+	}
+}
+
+func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) (pipelines [2][3]*pipeline, err error) {
+	defer func() {
+		if err != nil {
+			for _, p := range pipelines {
+				for _, p := range p {
+					if p != nil {
+						p.Release()
+					}
+				}
+			}
+		}
+	}()
+	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()
+	for i, format := range []driver.TextureFormat{driver.TextureFormatOutput, driver.TextureFormatSRGBA} {
+		{
+			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:    format,
+				Topology:       driver.TopologyTriangleStrip,
+			})
+			if err != nil {
+				return pipelines, err
+			}
+			var vertBuffer *uniformBuffer
+			if u := uniforms[materialTexture]; u != nil {
+				vertBuffer = newUniformBuffer(b, u)
+			}
+			pipelines[i][materialTexture] = &pipeline{pipe, vertBuffer}
+		}
+		{
+			var vertBuffer *uniformBuffer
+			fsh, err := b.NewFragmentShader(fsSrc[materialColor])
+			if err != nil {
+				return pipelines, err
+			}
+			defer fsh.Release()
+			pipe, err := b.NewPipeline(driver.PipelineDesc{
+				VertexShader:   vsh,
+				FragmentShader: fsh,
+				BlendDesc:      blend,
+				VertexLayout:   layout,
+				PixelFormat:    format,
+				Topology:       driver.TopologyTriangleStrip,
+			})
+			if err != nil {
+				return pipelines, err
+			}
+			if u := uniforms[materialColor]; u != nil {
+				vertBuffer = newUniformBuffer(b, u)
+			}
+			pipelines[i][materialColor] = &pipeline{pipe, vertBuffer}
+		}
+		{
+			var vertBuffer *uniformBuffer
+			fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
+			if err != nil {
+				return pipelines, err
+			}
+			defer fsh.Release()
+			pipe, err := b.NewPipeline(driver.PipelineDesc{
+				VertexShader:   vsh,
+				FragmentShader: fsh,
+				BlendDesc:      blend,
+				VertexLayout:   layout,
+				PixelFormat:    format,
+				Topology:       driver.TopologyTriangleStrip,
+			})
+			if err != nil {
+				return pipelines, err
+			}
+			if u := uniforms[materialLinearGradient]; u != nil {
+				vertBuffer = newUniformBuffer(b, u)
+			}
+			pipelines[i][materialLinearGradient] = &pipeline{pipe, vertBuffer}
+		}
+	}
+	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(f32.FRect(uv), fbo.size)
+	subScale, subOff := texSpaceTransform(f32.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
+		}
+		place, ok := r.packer.add(p.clip.Size())
+		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
+}
+
+func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
+	// Make every layer bounds contain nested layers; cull empty layers.
+	for i := len(layers) - 1; i >= 0; i-- {
+		l := layers[i]
+		if l.parent != -1 {
+			b := layers[l.parent].clip
+			layers[l.parent].clip = b.Union(l.clip)
+		}
+		if l.clip.Empty() {
+			layers = append(layers[:i], layers[i+1:]...)
+		}
+	}
+	// Pack layers.
+	r.layers.clear()
+	depth := 0
+	for i := range layers {
+		l := &layers[i]
+		// Only layers of the same depth may be packed together.
+		if l.depth != depth {
+			r.layers.newPage()
+		}
+		place, ok := r.layers.add(l.clip.Size())
+		if !ok {
+			// The layer area is at most the entire screen. Hopefully no
+			// screen is larger than GL_MAX_TEXTURE_SIZE.
+			panic(fmt.Errorf("layer size %v is larger than maximum texture size %v", l.clip.Size(), r.layers.maxDims))
+		}
+		l.place = place
+	}
+	return layers
+}
+
+func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
+	if len(r.layers.sizes) == 0 {
+		return
+	}
+	fbo := -1
+	r.layerFBOs.resize(r.ctx, driver.TextureFormatSRGBA, r.layers.sizes)
+	for i := len(layers) - 1; i >= 0; i-- {
+		l := layers[i]
+		if fbo != l.place.Idx {
+			if fbo != -1 {
+				r.ctx.EndRenderPass()
+				r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
+			}
+			fbo = l.place.Idx
+			f := r.layerFBOs.fbos[fbo]
+			r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear})
+		}
+		v := image.Rectangle{
+			Min: l.place.Pos,
+			Max: l.place.Pos.Add(l.clip.Size()),
+		}
+		r.ctx.Viewport(v.Min.X, v.Min.Y, v.Dx(), v.Dy())
+		f := r.layerFBOs.fbos[fbo]
+		r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
+		sr := f32.FRect(v)
+		uvScale, uvOffset := texSpaceTransform(sr, f.size)
+		uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
+		// Replace layer ops with one textured op.
+		ops[l.opStart] = imageOp{
+			clip: l.clip,
+			material: material{
+				material: materialTexture,
+				tex:      f.tex,
+				uvTrans:  uvTrans,
+				opacity:  l.opacity,
+			},
+			layerOps: l.opEnd - l.opStart - 1,
+		}
+	}
+	if fbo != -1 {
+		r.ctx.EndRenderPass()
+		r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
+	}
+}
+
+func (d *drawOps) reset(viewport image.Point) {
+	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]
+	d.layers = d.layers[:0]
+	d.opacityStack = d.opacityStack[: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) {
+	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
+}
+
+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.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.TypePushOpacity:
+			opacity := ops.DecodeOpacity(encOp.Data)
+			parent := -1
+			depth := len(d.opacityStack)
+			if depth > 0 {
+				parent = d.opacityStack[depth-1]
+			}
+			lidx := len(d.layers)
+			d.layers = append(d.layers, opacityLayer{
+				opacity: opacity,
+				parent:  parent,
+				depth:   depth,
+				opStart: len(d.imageOps),
+			})
+			d.opacityStack = append(d.opacityStack, lidx)
+		case ops.TypePopOpacity:
+			n := len(d.opacityStack)
+			idx := d.opacityStack[n-1]
+			d.layers[idx].opEnd = len(d.imageOps)
+			d.opacityStack = d.opacityStack[: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 := f32.FRect(op.Bounds)
+			trans, off := state.t.Split()
+			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)
+			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 := state.t.Split()
+			// 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)
+			}
+
+			bounds := cl.Round()
+			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) && len(d.opacityStack) == 0 {
+				// 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,
+			}
+			if n := len(d.opacityStack); n > 0 {
+				idx := d.opacityStack[n-1]
+				lb := d.layers[idx].clip
+				if lb.Empty() {
+					d.layers[idx].clip = img.clip
+				} else {
+					d.layers[idx].clip = lb.Union(img.clip)
+				}
+			}
+
+			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 {
+	m := material{
+		opacity: 1.,
+	}
+	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 := rect.Add(off).Round()
+		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 *textureCache, ops []imageOp) {
+	for i := range ops {
+		img := &ops[i]
+		m := img.material
+		if m.material == materialTexture {
+			img.material.tex = r.texHandle(cache, m.data)
+		}
+	}
+}
+
+func (r *renderer) prepareDrawOps(ops []imageOp) {
+	for _, img := range ops {
+		m := img.material
+		switch m.material {
+		case materialTexture:
+			r.ctx.PrepareTexture(m.tex)
+		}
+
+		var fbo FBO
+		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(isFBO bool, opOff, viewport image.Point, ops []imageOp) {
+	var coverTex driver.Texture
+	for i := 0; i < len(ops); i++ {
+		img := ops[i]
+		i += img.layerOps
+		m := img.material
+		switch m.material {
+		case materialTexture:
+			r.ctx.BindTexture(0, m.tex)
+		}
+		drc := img.clip.Add(opOff)
+
+		scale, off := clipSpaceTransform(drc, viewport)
+		var fbo FBO
+		fboIdx := 0
+		if isFBO {
+			fboIdx = 1
+		}
+		switch img.clipType {
+		case clipTypeNone:
+			p := r.blitter.pipelines[fboIdx][m.material]
+			r.ctx.BindPipeline(p.pipeline)
+			r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
+			r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, 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(f32.FRect(uv), fbo.size)
+		p := r.pather.coverer.pipelines[fboIdx][m.material]
+		r.ctx.BindPipeline(p.pipeline)
+		r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
+		r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
+	}
+}
+
+func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
+	fboIdx := 0
+	if fbo {
+		fboIdx = 1
+	}
+	p := b.pipelines[fboIdx][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()
+		uniforms = &b.texUniforms.blitUniforms
+		uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
+		uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
+	case materialLinearGradient:
+		b.linearGradientUniforms.color1 = col1
+		b.linearGradientUniforms.color2 = col2
+
+		t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
+		uniforms = &b.linearGradientUniforms.blitUniforms
+		uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
+		uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
+	}
+	uniforms.fbo = 0
+	if fbo {
+		uniforms.fbo = 1
+	}
+	uniforms.opacity = opacity
+	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 := unsafe.Slice((*byte)(unsafe.Pointer(ref.Pointer())), 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 = binary.LittleEndian.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:
+			from, ctrl0, ctrl1, to := scene.DecodeCubic(cmd)
+			qs.scratch = stroke.SplitCubic(from, ctrl0, ctrl1, to, qs.scratch[:0])
+			for _, q := range qs.scratch {
+				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
+}
+
+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
+}

+ 5 - 0
vendor/gioui.org/gpu/internal/d3d11/d3d11.go

@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// This file exists so this package builds on non-Windows platforms.
+
+package d3d11

+ 876 - 0
vendor/gioui.org/gpu/internal/d3d11/d3d11_windows.go

@@ -0,0 +1,876 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package d3d11
+
+import (
+	"errors"
+	"fmt"
+	"image"
+	"math"
+	"math/bits"
+	"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
+	mipmap  bool
+	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)
+	}
+	bindFlags := convBufferBinding(bindings)
+	miscFlags := uint32(0)
+	mipmap := minFilter == driver.FilterLinearMipmapLinear
+	nmipmaps := 1
+	if mipmap {
+		// Flags required by ID3D11DeviceContext::GenerateMips.
+		bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
+		miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
+		dim := width
+		if height > dim {
+			dim = height
+		}
+		log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
+		nmipmaps = log2 + 1
+	}
+	tex, err := b.dev.CreateTexture2D(&d3d11.TEXTURE2D_DESC{
+		Width:     uint32(width),
+		Height:    uint32(height),
+		MipLevels: uint32(nmipmaps),
+		ArraySize: 1,
+		Format:    d3dfmt,
+		SampleDesc: d3d11.DXGI_SAMPLE_DESC{
+			Count:   1,
+			Quality: 0,
+		},
+		BindFlags: bindFlags,
+		MiscFlags: miscFlags,
+	})
+	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
+		case minFilter == driver.FilterLinearMipmapLinear && magFilter == driver.FilterLinear:
+			filter = d3d11.FILTER_MIN_MAG_MIP_LINEAR
+		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, mipmap: mipmap}, 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)
+	if t.mipmap {
+		t.backend.ctx.GenerateMips(t.resView)
+	}
+}
+
+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 {
+	return unsafe.Slice((*byte)(unsafe.Pointer(ptr)), cap)
+}

+ 129 - 0
vendor/gioui.org/gpu/internal/driver/api.go

@@ -0,0 +1,129 @@
+// 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
+	// Fence is a VkFence that is set when all commands to Framebuffer has completed.
+	Fence 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()     {}

+ 238 - 0
vendor/gioui.org/gpu/internal/driver/driver.go

@@ -0,0 +1,238 @@
+// 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
+	FilterLinearMipmapLinear
+)
+
+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)
+}

+ 5 - 0
vendor/gioui.org/gpu/internal/metal/metal.go

@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// This file exists so this package builds on non-Darwin platforms.
+
+package metal

+ 1159 - 0
vendor/gioui.org/gpu/internal/metal/metal_darwin.go

@@ -0,0 +1,1159 @@
+// 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 -fobjc-arc
+#cgo LDFLAGS: -framework CoreGraphics -framework Metal -framework Foundation
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Metal/Metal.h>
+
+typedef struct {
+	void *addr;
+	NSUInteger size;
+} slice;
+
+static CFTypeRef queueNewBuffer(CFTypeRef queueRef) {
+	@autoreleasepool {
+		id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
+		return CFBridgingRetain([queue commandBuffer]);
+	}
+}
+
+static void cmdBufferCommit(CFTypeRef cmdBufRef) {
+	@autoreleasepool {
+		id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef;
+		[cmdBuf commit];
+	}
+}
+
+static void cmdBufferWaitUntilCompleted(CFTypeRef cmdBufRef) {
+	@autoreleasepool {
+		id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef;
+		[cmdBuf waitUntilCompleted];
+	}
+}
+
+static CFTypeRef cmdBufferRenderEncoder(CFTypeRef cmdBufRef, CFTypeRef textureRef, MTLLoadAction act, float r, float g, float b, float a) {
+	@autoreleasepool {
+		id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef;
+		MTLRenderPassDescriptor *desc = [MTLRenderPassDescriptor new];
+		desc.colorAttachments[0].texture = (__bridge id<MTLTexture>)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<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef;
+		return CFBridgingRetain([cmdBuf computeCommandEncoder]);
+	}
+}
+
+static CFTypeRef cmdBufferBlitEncoder(CFTypeRef cmdBufRef) {
+	@autoreleasepool {
+		id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef;
+		return CFBridgingRetain([cmdBuf blitCommandEncoder]);
+	}
+}
+
+static void renderEncEnd(CFTypeRef renderEncRef) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		[enc endEncoding];
+	}
+}
+
+static void renderEncViewport(CFTypeRef renderEncRef, MTLViewport viewport) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		[enc setViewport:viewport];
+	}
+}
+
+static void renderEncSetFragmentTexture(CFTypeRef renderEncRef, NSUInteger index, CFTypeRef texRef) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		id<MTLTexture> tex = (__bridge id<MTLTexture>)texRef;
+		[enc setFragmentTexture:tex atIndex:index];
+	}
+}
+
+static void renderEncSetFragmentSamplerState(CFTypeRef renderEncRef, NSUInteger index, CFTypeRef samplerRef) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		id<MTLSamplerState> sampler = (__bridge id<MTLSamplerState>)samplerRef;
+		[enc setFragmentSamplerState:sampler atIndex:index];
+	}
+}
+
+static void renderEncSetVertexBuffer(CFTypeRef renderEncRef, CFTypeRef bufRef, NSUInteger idx, NSUInteger offset) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef;
+		[enc setVertexBuffer:buf offset:offset atIndex:idx];
+	}
+}
+
+static void renderEncSetFragmentBuffer(CFTypeRef renderEncRef, CFTypeRef bufRef, NSUInteger idx, NSUInteger offset) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef;
+		[enc setFragmentBuffer:buf offset:offset atIndex:idx];
+	}
+}
+
+static void renderEncSetFragmentBytes(CFTypeRef renderEncRef, const void *bytes, NSUInteger length, NSUInteger idx) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		[enc setFragmentBytes:bytes length:length atIndex:idx];
+	}
+}
+
+static void renderEncSetVertexBytes(CFTypeRef renderEncRef, const void *bytes, NSUInteger length, NSUInteger idx) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		[enc setVertexBytes:bytes length:length atIndex:idx];
+	}
+}
+
+static void renderEncSetRenderPipelineState(CFTypeRef renderEncRef, CFTypeRef pipeRef) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		id<MTLRenderPipelineState> pipe = (__bridge id<MTLRenderPipelineState>)pipeRef;
+		[enc setRenderPipelineState:pipe];
+	}
+}
+
+static void renderEncDrawPrimitives(CFTypeRef renderEncRef, MTLPrimitiveType type, NSUInteger start, NSUInteger count) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		[enc drawPrimitives:type vertexStart:start vertexCount:count];
+	}
+}
+
+static void renderEncDrawIndexedPrimitives(CFTypeRef renderEncRef, MTLPrimitiveType type, CFTypeRef bufRef, NSUInteger offset, NSUInteger count) {
+	@autoreleasepool {
+		id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
+		id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef;
+		[enc drawIndexedPrimitives:type indexCount:count indexType:MTLIndexTypeUInt16 indexBuffer:buf indexBufferOffset:offset];
+	}
+}
+
+static void computeEncSetPipeline(CFTypeRef computeEncRef, CFTypeRef pipeRef) {
+	@autoreleasepool {
+		id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
+		id<MTLComputePipelineState> pipe = (__bridge id<MTLComputePipelineState>)pipeRef;
+		[enc setComputePipelineState:pipe];
+	}
+}
+
+static void computeEncSetTexture(CFTypeRef computeEncRef, NSUInteger index, CFTypeRef texRef) {
+	@autoreleasepool {
+		id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
+		id<MTLTexture> tex = (__bridge id<MTLTexture>)texRef;
+		[enc setTexture:tex atIndex:index];
+	}
+}
+
+static void computeEncEnd(CFTypeRef computeEncRef) {
+	@autoreleasepool {
+		id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
+		[enc endEncoding];
+	}
+}
+
+static void computeEncSetBuffer(CFTypeRef computeEncRef, NSUInteger index, CFTypeRef bufRef) {
+	@autoreleasepool {
+		id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
+		id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef;
+		[enc setBuffer:buf offset:0 atIndex:index];
+	}
+}
+
+static void computeEncDispatch(CFTypeRef computeEncRef, MTLSize threadgroupsPerGrid, MTLSize threadsPerThreadgroup) {
+	@autoreleasepool {
+		id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
+		[enc dispatchThreadgroups:threadgroupsPerGrid threadsPerThreadgroup:threadsPerThreadgroup];
+	}
+}
+
+static void computeEncSetBytes(CFTypeRef computeEncRef, const void *bytes, NSUInteger length, NSUInteger index) {
+	@autoreleasepool {
+		id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
+		[enc setBytes:bytes length:length atIndex:index];
+	}
+}
+
+static void blitEncEnd(CFTypeRef blitEncRef) {
+	@autoreleasepool {
+		id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
+		[enc endEncoding];
+	}
+}
+
+static void blitEncCopyFromTexture(CFTypeRef blitEncRef, CFTypeRef srcRef, MTLOrigin srcOrig, MTLSize srcSize, CFTypeRef dstRef, MTLOrigin dstOrig) {
+	@autoreleasepool {
+		id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
+		id<MTLTexture> src = (__bridge id<MTLTexture>)srcRef;
+		id<MTLTexture> dst = (__bridge id<MTLTexture>)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<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
+		id<MTLBuffer> src = (__bridge id<MTLBuffer>)bufRef;
+		id<MTLTexture> dst = (__bridge id<MTLTexture>)texRef;
+		[enc copyFromBuffer:src
+			   sourceOffset:offset
+		  sourceBytesPerRow:stride
+		sourceBytesPerImage:length
+				 sourceSize:dims
+				  toTexture:dst
+		   destinationSlice:0
+		   destinationLevel:0
+		  destinationOrigin:orig];
+	}
+}
+
+static void blitEncGenerateMipmapsForTexture(CFTypeRef blitEncRef, CFTypeRef texRef) {
+	@autoreleasepool {
+		id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
+		id<MTLTexture> tex = (__bridge id<MTLTexture>)texRef;
+		[enc generateMipmapsForTexture: tex];
+	}
+}
+
+static void blitEncCopyTextureToBuffer(CFTypeRef blitEncRef, CFTypeRef texRef, CFTypeRef bufRef, NSUInteger offset, NSUInteger stride, NSUInteger length, MTLSize dims, MTLOrigin orig) {
+	@autoreleasepool {
+		id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
+		id<MTLTexture> src = (__bridge id<MTLTexture>)texRef;
+		id<MTLBuffer> dst = (__bridge id<MTLBuffer>)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<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
+		id<MTLBuffer> src = (__bridge id<MTLBuffer>)srcRef;
+		id<MTLBuffer> dst = (__bridge id<MTLBuffer>)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, int mipmapped) {
+	@autoreleasepool {
+		id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
+		MTLTextureDescriptor *mtlDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: format
+																						   width: width
+																						  height: height
+																				   	   mipmapped: mipmapped ? YES : NO];
+		mtlDesc.usage = usage;
+		mtlDesc.storageMode =  MTLStorageModePrivate;
+		return CFBridgingRetain([dev newTextureWithDescriptor:mtlDesc]);
+	}
+}
+
+static CFTypeRef newSampler(CFTypeRef devRef, MTLSamplerMinMagFilter minFilter, MTLSamplerMinMagFilter magFilter, MTLSamplerMipFilter mipFilter) {
+	@autoreleasepool {
+		id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
+		MTLSamplerDescriptor *desc = [MTLSamplerDescriptor new];
+		desc.minFilter = minFilter;
+		desc.magFilter = magFilter;
+		desc.mipFilter = mipFilter;
+		return CFBridgingRetain([dev newSamplerStateWithDescriptor:desc]);
+	}
+}
+
+static CFTypeRef newBuffer(CFTypeRef devRef, NSUInteger size, MTLResourceOptions opts) {
+	@autoreleasepool {
+		id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
+		id<MTLBuffer> buf = [dev newBufferWithLength:size
+											 options:opts];
+		return CFBridgingRetain(buf);
+	}
+}
+
+static slice bufferContents(CFTypeRef bufRef) {
+	@autoreleasepool {
+		id<MTLBuffer> buf = (__bridge id<MTLBuffer>)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<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
+		dispatch_data_t data = dispatch_data_create(mtllib, size, DISPATCH_TARGET_QUEUE_DEFAULT, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+		id<MTLLibrary> lib = [dev newLibraryWithData:data error:nil];
+		lib.label = [NSString stringWithUTF8String:name];
+		return CFBridgingRetain(lib);
+	}
+}
+
+static CFTypeRef libraryNewFunction(CFTypeRef libRef, char *funcName) {
+	@autoreleasepool {
+		id<MTLLibrary> lib = (__bridge id<MTLLibrary>)libRef;
+		NSString *name = [NSString stringWithUTF8String:funcName];
+		return CFBridgingRetain([lib newFunctionWithName:name]);
+	}
+}
+
+static CFTypeRef newComputePipeline(CFTypeRef devRef, CFTypeRef funcRef) {
+	@autoreleasepool {
+		id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
+		id<MTLFunction> func = (__bridge id<MTLFunction>)funcRef;
+		return CFBridgingRetain([dev newComputePipelineStateWithFunction:func error:nil]);
+	}
+}
+
+static CFTypeRef newRenderPipeline(CFTypeRef devRef, CFTypeRef vertFunc, CFTypeRef fragFunc, MTLPixelFormat pixelFormat, NSUInteger bufIdx, NSUInteger nverts, MTLVertexFormat *fmts, NSUInteger *offsets, NSUInteger stride, int blend, MTLBlendFactor srcFactor, MTLBlendFactor dstFactor, NSUInteger nvertBufs, NSUInteger nfragBufs) {
+	@autoreleasepool {
+		id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
+		id<MTLFunction> vfunc = (__bridge id<MTLFunction>)vertFunc;
+		id<MTLFunction> ffunc = (__bridge id<MTLFunction>)fragFunc;
+		MTLVertexDescriptor *vdesc = [MTLVertexDescriptor vertexDescriptor];
+		vdesc.layouts[bufIdx].stride = stride;
+		for (NSUInteger i = 0; i < nverts; i++) {
+			vdesc.attributes[i].format = fmts[i];
+			vdesc.attributes[i].offset = offsets[i];
+			vdesc.attributes[i].bufferIndex = bufIdx;
+		}
+		MTLRenderPipelineDescriptor *desc = [MTLRenderPipelineDescriptor new];
+		desc.vertexFunction = vfunc;
+		desc.fragmentFunction = ffunc;
+		desc.vertexDescriptor = vdesc;
+		for (NSUInteger i = 0; i < nvertBufs; i++) {
+			if (@available(iOS 11.0, *)) {
+				desc.vertexBuffers[i].mutability = MTLMutabilityImmutable;
+			}
+		}
+		for (NSUInteger i = 0; i < nfragBufs; i++) {
+			if (@available(iOS 11.0, *)) {
+				desc.fragmentBuffers[i].mutability = MTLMutabilityImmutable;
+			}
+		}
+		desc.colorAttachments[0].pixelFormat = pixelFormat;
+		desc.colorAttachments[0].blendingEnabled = blend ? YES : NO;
+		desc.colorAttachments[0].sourceAlphaBlendFactor = srcFactor;
+		desc.colorAttachments[0].sourceRGBBlendFactor = srcFactor;
+		desc.colorAttachments[0].destinationAlphaBlendFactor = dstFactor;
+		desc.colorAttachments[0].destinationRGBBlendFactor = dstFactor;
+		return CFBridgingRetain([dev newRenderPipelineStateWithDescriptor:desc
+																	error:nil]);
+	}
+}
+*/
+import "C"
+
+type Backend struct {
+	dev      C.CFTypeRef
+	queue    C.CFTypeRef
+	pixelFmt C.MTLPixelFormat
+
+	cmdBuffer     C.CFTypeRef
+	lastCmdBuffer C.CFTypeRef
+	renderEnc     C.CFTypeRef
+	computeEnc    C.CFTypeRef
+	blitEnc       C.CFTypeRef
+
+	prog     *Program
+	topology C.MTLPrimitiveType
+
+	stagingBuf C.CFTypeRef
+	stagingOff int
+
+	indexBuf *Buffer
+
+	// bufSizes is scratch space for filling out the spvBufferSizeConstants
+	// that spirv-cross generates for emulating buffer.length expressions in
+	// shaders.
+	bufSizes []uint32
+}
+
+type Texture struct {
+	backend *Backend
+	texture C.CFTypeRef
+	sampler C.CFTypeRef
+	width   int
+	height  int
+	mipmap  bool
+	foreign bool
+}
+
+type Shader struct {
+	function C.CFTypeRef
+	inputs   []shader.InputLocation
+}
+
+type Program struct {
+	pipeline  C.CFTypeRef
+	groupSize [3]int
+}
+
+type Pipeline struct {
+	pipeline C.CFTypeRef
+	topology C.MTLPrimitiveType
+}
+
+type Buffer struct {
+	backend *Backend
+	size    int
+	buffer  C.CFTypeRef
+
+	// store is the buffer contents For buffers not allocated on the GPU.
+	store []byte
+}
+
+const (
+	uniformBufferIndex   = 0
+	attributeBufferIndex = 1
+
+	spvBufferSizeConstantsBinding = 25
+)
+
+const (
+	texUnits    = 4
+	bufferUnits = 4
+)
+
+func init() {
+	driver.NewMetalDevice = newMetalDevice
+}
+
+func newMetalDevice(api driver.Metal) (driver.Device, error) {
+	dev := C.CFTypeRef(api.Device)
+	C.CFRetain(dev)
+	queue := C.CFTypeRef(api.Queue)
+	C.CFRetain(queue)
+	b := &Backend{
+		dev:      dev,
+		queue:    queue,
+		pixelFmt: C.MTLPixelFormat(api.PixelFormat),
+		bufSizes: make([]uint32, bufferUnits),
+	}
+	return b, nil
+}
+
+func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
+	if b.lastCmdBuffer != 0 {
+		C.cmdBufferWaitUntilCompleted(b.lastCmdBuffer)
+		b.stagingOff = 0
+	}
+	if target == nil {
+		return nil
+	}
+	switch t := target.(type) {
+	case driver.MetalRenderTarget:
+		texture := C.CFTypeRef(t.Texture)
+		return &Texture{texture: texture, foreign: true}
+	case *Texture:
+		return t
+	default:
+		panic(fmt.Sprintf("metal: unsupported render target type: %T", t))
+	}
+}
+
+func (b *Backend) startBlit() C.CFTypeRef {
+	if b.blitEnc != 0 {
+		return b.blitEnc
+	}
+	b.endEncoder()
+	b.ensureCmdBuffer()
+	b.blitEnc = C.cmdBufferBlitEncoder(b.cmdBuffer)
+	if b.blitEnc == 0 {
+		panic("metal: [MTLCommandBuffer blitCommandEncoder:] failed")
+	}
+	return b.blitEnc
+}
+
+func (b *Backend) CopyTexture(dst driver.Texture, dorig image.Point, src driver.Texture, srect image.Rectangle) {
+	enc := b.startBlit()
+	dstTex := dst.(*Texture).texture
+	srcTex := src.(*Texture).texture
+	ssz := srect.Size()
+	C.blitEncCopyFromTexture(
+		enc,
+		srcTex,
+		C.MTLOrigin{
+			x: C.NSUInteger(srect.Min.X),
+			y: C.NSUInteger(srect.Min.Y),
+		},
+		C.MTLSize{
+			width:  C.NSUInteger(ssz.X),
+			height: C.NSUInteger(ssz.Y),
+			depth:  1,
+		},
+		dstTex,
+		C.MTLOrigin{
+			x: C.NSUInteger(dorig.X),
+			y: C.NSUInteger(dorig.Y),
+		},
+	)
+}
+
+func (b *Backend) EndFrame() {
+	b.endCmdBuffer(false)
+}
+
+func (b *Backend) endCmdBuffer(wait bool) {
+	b.endEncoder()
+	if b.cmdBuffer == 0 {
+		return
+	}
+	C.cmdBufferCommit(b.cmdBuffer)
+	if wait {
+		C.cmdBufferWaitUntilCompleted(b.cmdBuffer)
+	}
+	if b.lastCmdBuffer != 0 {
+		C.CFRelease(b.lastCmdBuffer)
+	}
+	b.lastCmdBuffer = b.cmdBuffer
+	b.cmdBuffer = 0
+}
+
+func (b *Backend) Caps() driver.Caps {
+	return driver.Caps{
+		MaxTextureSize: 8192,
+		Features:       driver.FeatureSRGB | driver.FeatureCompute | driver.FeatureFloatRenderTargets,
+	}
+}
+
+func (b *Backend) NewTimer() driver.Timer {
+	panic("timers not supported")
+}
+
+func (b *Backend) IsTimeContinuous() bool {
+	panic("timers not supported")
+}
+
+func (b *Backend) Release() {
+	if b.cmdBuffer != 0 {
+		C.CFRelease(b.cmdBuffer)
+	}
+	if b.lastCmdBuffer != 0 {
+		C.CFRelease(b.lastCmdBuffer)
+	}
+	if b.stagingBuf != 0 {
+		C.CFRelease(b.stagingBuf)
+	}
+	C.CFRelease(b.queue)
+	C.CFRelease(b.dev)
+	*b = Backend{}
+}
+
+func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, bindings driver.BufferBinding) (driver.Texture, error) {
+	mformat := pixelFormatFor(format)
+	var usage C.MTLTextureUsage
+	if bindings&(driver.BufferBindingTexture|driver.BufferBindingShaderStorageRead) != 0 {
+		usage |= C.MTLTextureUsageShaderRead
+	}
+	if bindings&driver.BufferBindingFramebuffer != 0 {
+		usage |= C.MTLTextureUsageRenderTarget
+	}
+	if bindings&driver.BufferBindingShaderStorageWrite != 0 {
+		usage |= C.MTLTextureUsageShaderWrite
+	}
+	min, mip := samplerFilterFor(minFilter)
+	max, _ := samplerFilterFor(magFilter)
+	mipmap := mip != C.MTLSamplerMipFilterNotMipmapped
+	mipmapped := C.int(0)
+	if mipmap {
+		mipmapped = 1
+	}
+	tex := C.newTexture(b.dev, C.NSUInteger(width), C.NSUInteger(height), mformat, usage, mipmapped)
+	if tex == 0 {
+		return nil, errors.New("metal: [MTLDevice newTextureWithDescriptor:] failed")
+	}
+	s := C.newSampler(b.dev, min, max, mip)
+	if s == 0 {
+		C.CFRelease(tex)
+		return nil, errors.New("metal: [MTLDevice newSamplerStateWithDescriptor:] failed")
+	}
+	return &Texture{backend: b, texture: tex, sampler: s, width: width, height: height, mipmap: mipmap}, nil
+}
+
+func samplerFilterFor(f driver.TextureFilter) (C.MTLSamplerMinMagFilter, C.MTLSamplerMipFilter) {
+	switch f {
+	case driver.FilterNearest:
+		return C.MTLSamplerMinMagFilterNearest, C.MTLSamplerMipFilterNotMipmapped
+	case driver.FilterLinear:
+		return C.MTLSamplerMinMagFilterLinear, C.MTLSamplerMipFilterNotMipmapped
+	case driver.FilterLinearMipmapLinear:
+		return C.MTLSamplerMinMagFilterLinear, C.MTLSamplerMipFilterLinear
+	default:
+		panic("invalid texture filter")
+	}
+}
+
+func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) {
+	vsh, fsh := desc.VertexShader.(*Shader), desc.FragmentShader.(*Shader)
+	layout := desc.VertexLayout.Inputs
+	if got, exp := len(layout), len(vsh.inputs); got != exp {
+		return nil, fmt.Errorf("metal: number of input descriptors (%d) doesn't match number of inputs (%d)", got, exp)
+	}
+	formats := make([]C.MTLVertexFormat, len(layout))
+	offsets := make([]C.NSUInteger, len(layout))
+	for i, inp := range layout {
+		index := vsh.inputs[i].Location
+		formats[index] = vertFormatFor(vsh.inputs[i])
+		offsets[index] = C.NSUInteger(inp.Offset)
+	}
+	var (
+		fmtPtr *C.MTLVertexFormat
+		offPtr *C.NSUInteger
+	)
+	if len(layout) > 0 {
+		fmtPtr = &formats[0]
+		offPtr = &offsets[0]
+	}
+	srcFactor := blendFactorFor(desc.BlendDesc.SrcFactor)
+	dstFactor := blendFactorFor(desc.BlendDesc.DstFactor)
+	blend := C.int(0)
+	if desc.BlendDesc.Enable {
+		blend = 1
+	}
+	pf := b.pixelFmt
+	if f := desc.PixelFormat; f != driver.TextureFormatOutput {
+		pf = pixelFormatFor(f)
+	}
+	pipe := C.newRenderPipeline(
+		b.dev,
+		vsh.function,
+		fsh.function,
+		pf,
+		attributeBufferIndex,
+		C.NSUInteger(len(layout)), fmtPtr, offPtr,
+		C.NSUInteger(desc.VertexLayout.Stride),
+		blend, srcFactor, dstFactor,
+		2, // Number of vertex buffers.
+		1, // Number of fragment buffers.
+	)
+	if pipe == 0 {
+		return nil, errors.New("metal: pipeline construction failed")
+	}
+	return &Pipeline{pipeline: pipe, topology: primitiveFor(desc.Topology)}, nil
+}
+
+func dataTypeSize(d shader.DataType) int {
+	switch d {
+	case shader.DataTypeFloat:
+		return 4
+	default:
+		panic("unsupported data type")
+	}
+}
+
+func blendFactorFor(f driver.BlendFactor) C.MTLBlendFactor {
+	switch f {
+	case driver.BlendFactorZero:
+		return C.MTLBlendFactorZero
+	case driver.BlendFactorOne:
+		return C.MTLBlendFactorOne
+	case driver.BlendFactorOneMinusSrcAlpha:
+		return C.MTLBlendFactorOneMinusSourceAlpha
+	case driver.BlendFactorDstColor:
+		return C.MTLBlendFactorDestinationColor
+	default:
+		panic("unsupported blend factor")
+	}
+}
+
+func vertFormatFor(f shader.InputLocation) C.MTLVertexFormat {
+	t := f.Type
+	s := f.Size
+	switch {
+	case t == shader.DataTypeFloat && s == 1:
+		return C.MTLVertexFormatFloat
+	case t == shader.DataTypeFloat && s == 2:
+		return C.MTLVertexFormatFloat2
+	case t == shader.DataTypeFloat && s == 3:
+		return C.MTLVertexFormatFloat3
+	case t == shader.DataTypeFloat && s == 4:
+		return C.MTLVertexFormatFloat4
+	default:
+		panic("unsupported data type")
+	}
+}
+
+func pixelFormatFor(f driver.TextureFormat) C.MTLPixelFormat {
+	switch f {
+	case driver.TextureFormatFloat:
+		return C.MTLPixelFormatR16Float
+	case driver.TextureFormatRGBA8:
+		return C.MTLPixelFormatRGBA8Unorm
+	case driver.TextureFormatSRGBA:
+		return C.MTLPixelFormatRGBA8Unorm_sRGB
+	default:
+		panic("unsupported pixel format")
+	}
+}
+
+func (b *Backend) NewBuffer(typ driver.BufferBinding, size int) (driver.Buffer, error) {
+	// Transfer buffer contents in command encoders on every use for
+	// smaller buffers. The advantage is that buffer re-use during a frame
+	// won't occur a GPU wait.
+	// We can't do this for buffers written to by the GPU and read by the client,
+	// and Metal doesn't require a buffer for indexed draws.
+	if size <= 4096 && typ&(driver.BufferBindingShaderStorageWrite|driver.BufferBindingIndices) == 0 {
+		return &Buffer{size: size, store: make([]byte, size)}, nil
+	}
+	buf := C.newBuffer(b.dev, C.NSUInteger(size), C.MTLResourceStorageModePrivate)
+	return &Buffer{backend: b, size: size, buffer: buf}, nil
+}
+
+func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) {
+	buf, err := b.NewBuffer(typ, len(data))
+	if err != nil {
+		return nil, err
+	}
+	buf.Upload(data)
+	return buf, nil
+}
+
+func (b *Backend) NewComputeProgram(src shader.Sources) (driver.Program, error) {
+	sh, err := b.newShader(src)
+	if err != nil {
+		return nil, err
+	}
+	defer sh.Release()
+	pipe := C.newComputePipeline(b.dev, sh.function)
+	if pipe == 0 {
+		return nil, fmt.Errorf("metal: compute program %q load failed", src.Name)
+	}
+	return &Program{pipeline: pipe, groupSize: src.WorkgroupSize}, nil
+}
+
+func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) {
+	return b.newShader(src)
+}
+
+func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) {
+	return b.newShader(src)
+}
+
+func (b *Backend) newShader(src shader.Sources) (*Shader, error) {
+	vsrc := []byte(src.MetalLib)
+	cname := C.CString(src.Name)
+	defer C.free(unsafe.Pointer(cname))
+	vlib := C.newLibrary(b.dev, cname, unsafe.Pointer(&vsrc[0]), C.size_t(len(vsrc)))
+	if vlib == 0 {
+		return nil, fmt.Errorf("metal: vertex shader %q load failed", src.Name)
+	}
+	defer C.CFRelease(vlib)
+	funcName := C.CString("main0")
+	defer C.free(unsafe.Pointer(funcName))
+	f := C.libraryNewFunction(vlib, funcName)
+	if f == 0 {
+		return nil, fmt.Errorf("metal: main function not found in %q", src.Name)
+	}
+	return &Shader{function: f, inputs: src.Inputs}, nil
+}
+
+func (b *Backend) Viewport(x, y, width, height int) {
+	enc := b.renderEnc
+	if enc == 0 {
+		panic("no active render pass")
+	}
+	C.renderEncViewport(enc, C.MTLViewport{
+		originX: C.double(x),
+		originY: C.double(y),
+		width:   C.double(width),
+		height:  C.double(height),
+		znear:   0.0,
+		zfar:    1.0,
+	})
+}
+
+func (b *Backend) DrawArrays(off, count int) {
+	enc := b.renderEnc
+	if enc == 0 {
+		panic("no active render pass")
+	}
+	C.renderEncDrawPrimitives(enc, b.topology, C.NSUInteger(off), C.NSUInteger(count))
+}
+
+func (b *Backend) DrawElements(off, count int) {
+	enc := b.renderEnc
+	if enc == 0 {
+		panic("no active render pass")
+	}
+	C.renderEncDrawIndexedPrimitives(enc, b.topology, b.indexBuf.buffer, C.NSUInteger(off), C.NSUInteger(count))
+}
+
+func primitiveFor(mode driver.Topology) C.MTLPrimitiveType {
+	switch mode {
+	case driver.TopologyTriangles:
+		return C.MTLPrimitiveTypeTriangle
+	case driver.TopologyTriangleStrip:
+		return C.MTLPrimitiveTypeTriangleStrip
+	default:
+		panic("metal: unknown draw mode")
+	}
+}
+
+func (b *Backend) BindImageTexture(unit int, tex driver.Texture) {
+	b.BindTexture(unit, tex)
+}
+
+func (b *Backend) BeginCompute() {
+	b.endEncoder()
+	b.ensureCmdBuffer()
+	for i := range b.bufSizes {
+		b.bufSizes[i] = 0
+	}
+	b.computeEnc = C.cmdBufferComputeEncoder(b.cmdBuffer)
+	if b.computeEnc == 0 {
+		panic("metal: [MTLCommandBuffer computeCommandEncoder:] failed")
+	}
+}
+
+func (b *Backend) EndCompute() {
+	if b.computeEnc == 0 {
+		panic("no active compute pass")
+	}
+	C.computeEncEnd(b.computeEnc)
+	C.CFRelease(b.computeEnc)
+	b.computeEnc = 0
+}
+
+func (b *Backend) DispatchCompute(x, y, z int) {
+	enc := b.computeEnc
+	if enc == 0 {
+		panic("no active compute pass")
+	}
+	C.computeEncSetBytes(enc, unsafe.Pointer(&b.bufSizes[0]), C.NSUInteger(len(b.bufSizes)*4), spvBufferSizeConstantsBinding)
+	threadgroupsPerGrid := C.MTLSize{
+		width: C.NSUInteger(x), height: C.NSUInteger(y), depth: C.NSUInteger(z),
+	}
+	sz := b.prog.groupSize
+	threadsPerThreadgroup := C.MTLSize{
+		width: C.NSUInteger(sz[0]), height: C.NSUInteger(sz[1]), depth: C.NSUInteger(sz[2]),
+	}
+	C.computeEncDispatch(enc, threadgroupsPerGrid, threadsPerThreadgroup)
+}
+
+func (b *Backend) stagingBuffer(size int) (C.CFTypeRef, int) {
+	if b.stagingBuf == 0 || b.stagingOff+size > len(bufferStore(b.stagingBuf)) {
+		if b.stagingBuf != 0 {
+			C.CFRelease(b.stagingBuf)
+		}
+		cap := 2 * (b.stagingOff + size)
+		b.stagingBuf = C.newBuffer(b.dev, C.NSUInteger(cap), C.MTLResourceStorageModeShared|C.MTLResourceCPUCacheModeWriteCombined)
+		if b.stagingBuf == 0 {
+			panic(fmt.Errorf("metal: failed to allocate %d bytes of staging buffer", cap))
+		}
+		b.stagingOff = 0
+	}
+	off := b.stagingOff
+	b.stagingOff += size
+	return b.stagingBuf, off
+}
+
+func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) {
+	if len(pixels) == 0 {
+		return
+	}
+	if stride == 0 {
+		stride = size.X * 4
+	}
+	dstStride := size.X * 4
+	n := size.Y * dstStride
+	buf, off := t.backend.stagingBuffer(n)
+	store := bufferSlice(buf, off, n)
+	var srcOff, dstOff int
+	for y := 0; y < size.Y; y++ {
+		srcRow := pixels[srcOff : srcOff+dstStride]
+		dstRow := store[dstOff : dstOff+dstStride]
+		copy(dstRow, srcRow)
+		dstOff += dstStride
+		srcOff += stride
+	}
+	enc := t.backend.startBlit()
+	orig := C.MTLOrigin{
+		x: C.NSUInteger(offset.X),
+		y: C.NSUInteger(offset.Y),
+	}
+	msize := C.MTLSize{
+		width:  C.NSUInteger(size.X),
+		height: C.NSUInteger(size.Y),
+		depth:  1,
+	}
+	C.blitEncCopyBufferToTexture(enc, buf, t.texture, C.NSUInteger(off), C.NSUInteger(dstStride), C.NSUInteger(len(store)), msize, orig)
+	if t.mipmap {
+		C.blitEncGenerateMipmapsForTexture(enc, t.texture)
+	}
+}
+
+func (t *Texture) Release() {
+	if t.foreign {
+		panic("metal: release of external texture")
+	}
+	C.CFRelease(t.texture)
+	C.CFRelease(t.sampler)
+	*t = Texture{}
+}
+
+func (p *Pipeline) Release() {
+	C.CFRelease(p.pipeline)
+	*p = Pipeline{}
+}
+
+func (b *Backend) PrepareTexture(tex driver.Texture) {}
+
+func (b *Backend) BindTexture(unit int, tex driver.Texture) {
+	t := tex.(*Texture)
+	if enc := b.renderEnc; enc != 0 {
+		C.renderEncSetFragmentTexture(enc, C.NSUInteger(unit), t.texture)
+		C.renderEncSetFragmentSamplerState(enc, C.NSUInteger(unit), t.sampler)
+	} else if enc := b.computeEnc; enc != 0 {
+		C.computeEncSetTexture(enc, C.NSUInteger(unit), t.texture)
+	} else {
+		panic("no active render nor compute pass")
+	}
+}
+
+func (b *Backend) ensureCmdBuffer() {
+	if b.cmdBuffer != 0 {
+		return
+	}
+	b.cmdBuffer = C.queueNewBuffer(b.queue)
+	if b.cmdBuffer == 0 {
+		panic("metal: [MTLCommandQueue cmdBuffer] failed")
+	}
+}
+
+func (b *Backend) BindPipeline(pipe driver.Pipeline) {
+	p := pipe.(*Pipeline)
+	enc := b.renderEnc
+	if enc == 0 {
+		panic("no active render pass")
+	}
+	C.renderEncSetRenderPipelineState(enc, p.pipeline)
+	b.topology = p.topology
+}
+
+func (b *Backend) BindProgram(prog driver.Program) {
+	enc := b.computeEnc
+	if enc == 0 {
+		panic("no active compute pass")
+	}
+	p := prog.(*Program)
+	C.computeEncSetPipeline(enc, p.pipeline)
+	b.prog = p
+}
+
+func (s *Shader) Release() {
+	C.CFRelease(s.function)
+	*s = Shader{}
+}
+
+func (p *Program) Release() {
+	C.CFRelease(p.pipeline)
+	*p = Program{}
+}
+
+func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) {
+	buf := buffer.(*Buffer)
+	b.bufSizes[binding] = uint32(buf.size)
+	enc := b.computeEnc
+	if enc == 0 {
+		panic("no active compute pass")
+	}
+	if buf.buffer != 0 {
+		C.computeEncSetBuffer(enc, C.NSUInteger(binding), buf.buffer)
+	} else if buf.size > 0 {
+		C.computeEncSetBytes(enc, unsafe.Pointer(&buf.store[0]), C.NSUInteger(buf.size), C.NSUInteger(binding))
+	}
+}
+
+func (b *Backend) BindUniforms(buf driver.Buffer) {
+	bf := buf.(*Buffer)
+	enc := b.renderEnc
+	if enc == 0 {
+		panic("no active render pass")
+	}
+	if bf.buffer != 0 {
+		C.renderEncSetVertexBuffer(enc, bf.buffer, uniformBufferIndex, 0)
+		C.renderEncSetFragmentBuffer(enc, bf.buffer, uniformBufferIndex, 0)
+	} else if bf.size > 0 {
+		C.renderEncSetVertexBytes(enc, unsafe.Pointer(&bf.store[0]), C.NSUInteger(bf.size), uniformBufferIndex)
+		C.renderEncSetFragmentBytes(enc, unsafe.Pointer(&bf.store[0]), C.NSUInteger(bf.size), uniformBufferIndex)
+	}
+}
+
+func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) {
+	bf := buf.(*Buffer)
+	enc := b.renderEnc
+	if enc == 0 {
+		panic("no active render pass")
+	}
+	if bf.buffer != 0 {
+		C.renderEncSetVertexBuffer(enc, bf.buffer, attributeBufferIndex, C.NSUInteger(offset))
+	} else if n := bf.size - offset; n > 0 {
+		C.renderEncSetVertexBytes(enc, unsafe.Pointer(&bf.store[offset]), C.NSUInteger(n), attributeBufferIndex)
+	}
+}
+
+func (b *Backend) BindIndexBuffer(buf driver.Buffer) {
+	b.indexBuf = buf.(*Buffer)
+}
+
+func (b *Buffer) Download(data []byte) error {
+	if len(data) > b.size {
+		panic(fmt.Errorf("len(data) (%d) larger than len(content) (%d)", len(data), b.size))
+	}
+	buf, off := b.backend.stagingBuffer(len(data))
+	enc := b.backend.startBlit()
+	C.blitEncCopyBufferToBuffer(enc, b.buffer, buf, 0, C.NSUInteger(off), C.NSUInteger(len(data)))
+	b.backend.endCmdBuffer(true)
+	store := bufferSlice(buf, off, len(data))
+	copy(data, store)
+	return nil
+}
+
+func (b *Buffer) Upload(data []byte) {
+	if len(data) > b.size {
+		panic(fmt.Errorf("len(data) (%d) larger than len(content) (%d)", len(data), b.size))
+	}
+	if b.buffer == 0 {
+		copy(b.store, data)
+		return
+	}
+	buf, off := b.backend.stagingBuffer(len(data))
+	store := bufferSlice(buf, off, len(data))
+	copy(store, data)
+	enc := b.backend.startBlit()
+	C.blitEncCopyBufferToBuffer(enc, buf, b.buffer, C.NSUInteger(off), 0, C.NSUInteger(len(store)))
+}
+
+func bufferStore(buf C.CFTypeRef) []byte {
+	contents := C.bufferContents(buf)
+	return (*(*[1 << 30]byte)(contents.addr))[:contents.size:contents.size]
+}
+
+func bufferSlice(buf C.CFTypeRef, off, len int) []byte {
+	store := bufferStore(buf)
+	return store[off : off+len]
+}
+
+func (b *Buffer) Release() {
+	if b.buffer != 0 {
+		C.CFRelease(b.buffer)
+	}
+	*b = Buffer{}
+}
+
+func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
+	if len(pixels) == 0 {
+		return nil
+	}
+	sz := src.Size()
+	orig := C.MTLOrigin{
+		x: C.NSUInteger(src.Min.X),
+		y: C.NSUInteger(src.Min.Y),
+	}
+	msize := C.MTLSize{
+		width:  C.NSUInteger(sz.X),
+		height: C.NSUInteger(sz.Y),
+		depth:  1,
+	}
+	stageStride := sz.X * 4
+	n := sz.Y * stageStride
+	buf, off := t.backend.stagingBuffer(n)
+	enc := t.backend.startBlit()
+	C.blitEncCopyTextureToBuffer(enc, t.texture, buf, C.NSUInteger(off), C.NSUInteger(stageStride), C.NSUInteger(n), msize, orig)
+	t.backend.endCmdBuffer(true)
+	store := bufferSlice(buf, off, n)
+	var srcOff, dstOff int
+	for y := 0; y < sz.Y; y++ {
+		dstRow := pixels[srcOff : srcOff+stageStride]
+		srcRow := store[dstOff : dstOff+stageStride]
+		copy(dstRow, srcRow)
+		dstOff += stageStride
+		srcOff += stride
+	}
+	return nil
+}
+
+func (b *Backend) BeginRenderPass(tex driver.Texture, d driver.LoadDesc) {
+	b.endEncoder()
+	b.ensureCmdBuffer()
+	f := tex.(*Texture)
+	col := d.ClearColor
+	var act C.MTLLoadAction
+	switch d.Action {
+	case driver.LoadActionKeep:
+		act = C.MTLLoadActionLoad
+	case driver.LoadActionClear:
+		act = C.MTLLoadActionClear
+	case driver.LoadActionInvalidate:
+		act = C.MTLLoadActionDontCare
+	}
+	b.renderEnc = C.cmdBufferRenderEncoder(b.cmdBuffer, f.texture, act, C.float(col.R), C.float(col.G), C.float(col.B), C.float(col.A))
+	if b.renderEnc == 0 {
+		panic("metal: [MTLCommandBuffer renderCommandEncoderWithDescriptor:] failed")
+	}
+}
+
+func (b *Backend) EndRenderPass() {
+	if b.renderEnc == 0 {
+		panic("no active render pass")
+	}
+	C.renderEncEnd(b.renderEnc)
+	C.CFRelease(b.renderEnc)
+	b.renderEnc = 0
+}
+
+func (b *Backend) endEncoder() {
+	if b.renderEnc != 0 {
+		panic("active render pass")
+	}
+	if b.computeEnc != 0 {
+		panic("active compute pass")
+	}
+	if b.blitEnc != 0 {
+		C.blitEncEnd(b.blitEnc)
+		C.CFRelease(b.blitEnc)
+		b.blitEnc = 0
+	}
+}
+
+func (f *Texture) ImplementsRenderTarget() {}

+ 1381 - 0
vendor/gioui.org/gpu/internal/opengl/opengl.go

@@ -0,0 +1,1381 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package opengl
+
+import (
+	"errors"
+	"fmt"
+	"image"
+	"math/bits"
+	"runtime"
+	"strings"
+	"time"
+	"unsafe"
+
+	"gioui.org/gpu/internal/driver"
+	"gioui.org/internal/gl"
+	"gioui.org/shader"
+)
+
+// Backend implements driver.Device.
+type Backend struct {
+	funcs *gl.Functions
+
+	clear      bool
+	glstate    glState
+	state      state
+	savedState glState
+	sharedCtx  bool
+
+	glver [2]int
+	gles  bool
+	feats driver.Caps
+	// floatTriple holds the settings for floating point
+	// textures.
+	floatTriple textureTriple
+	// Single channel alpha textures.
+	alphaTriple textureTriple
+	srgbaTriple textureTriple
+	storage     [storageBindings]*buffer
+
+	outputFBO gl.Framebuffer
+	sRGBFBO   *SRGBFBO
+
+	// vertArray is bound during a frame. We don't need it, but
+	// core desktop OpenGL profile 3.3 requires some array bound.
+	vertArray gl.VertexArray
+}
+
+// State tracking.
+type glState struct {
+	drawFBO     gl.Framebuffer
+	readFBO     gl.Framebuffer
+	vertAttribs [5]struct {
+		obj        gl.Buffer
+		enabled    bool
+		size       int
+		typ        gl.Enum
+		normalized bool
+		stride     int
+		offset     uintptr
+	}
+	prog     gl.Program
+	texUnits struct {
+		active gl.Enum
+		binds  [2]gl.Texture
+	}
+	arrayBuf  gl.Buffer
+	elemBuf   gl.Buffer
+	uniBuf    gl.Buffer
+	uniBufs   [2]gl.Buffer
+	storeBuf  gl.Buffer
+	storeBufs [4]gl.Buffer
+	vertArray gl.VertexArray
+	srgb      bool
+	blend     struct {
+		enable         bool
+		srcRGB, dstRGB gl.Enum
+		srcA, dstA     gl.Enum
+	}
+	clearColor        [4]float32
+	viewport          [4]int
+	unpack_row_length int
+	pack_row_length   int
+}
+
+type state struct {
+	pipeline *pipeline
+	buffer   bufferBinding
+}
+
+type bufferBinding struct {
+	obj    gl.Buffer
+	offset int
+}
+
+type timer struct {
+	funcs *gl.Functions
+	obj   gl.Query
+}
+
+type texture struct {
+	backend  *Backend
+	obj      gl.Texture
+	fbo      gl.Framebuffer
+	hasFBO   bool
+	triple   textureTriple
+	width    int
+	height   int
+	mipmap   bool
+	bindings driver.BufferBinding
+	foreign  bool
+}
+
+type pipeline struct {
+	prog     *program
+	inputs   []shader.InputLocation
+	layout   driver.VertexLayout
+	blend    driver.BlendDesc
+	topology driver.Topology
+}
+
+type buffer struct {
+	backend   *Backend
+	hasBuffer bool
+	obj       gl.Buffer
+	typ       driver.BufferBinding
+	size      int
+	immutable bool
+	// For emulation of uniform buffers.
+	data []byte
+}
+
+type glshader struct {
+	backend *Backend
+	obj     gl.Shader
+	src     shader.Sources
+}
+
+type program struct {
+	backend      *Backend
+	obj          gl.Program
+	vertUniforms uniforms
+	fragUniforms uniforms
+}
+
+type uniforms struct {
+	locs []uniformLocation
+	size int
+}
+
+type uniformLocation struct {
+	uniform gl.Uniform
+	offset  int
+	typ     shader.DataType
+	size    int
+}
+
+// textureTriple holds the type settings for
+// a TexImage2D call.
+type textureTriple struct {
+	internalFormat gl.Enum
+	format         gl.Enum
+	typ            gl.Enum
+}
+
+const (
+	storageBindings = 32
+)
+
+func init() {
+	driver.NewOpenGLDevice = newOpenGLDevice
+}
+
+// Supporting compute programs is theoretically possible with OpenGL ES 3.1. In
+// practice, there are too many driver issues, especially on Android (e.g.
+// Google Pixel, Samsung J2 are both broken i different ways). Disable support
+// and rely on Vulkan for devices that support it, and the CPU fallback for
+// devices that don't.
+const brokenGLES31 = true
+
+func newOpenGLDevice(api driver.OpenGL) (driver.Device, error) {
+	f, err := gl.NewFunctions(api.Context, api.ES)
+	if err != nil {
+		return nil, err
+	}
+	exts := strings.Split(f.GetString(gl.EXTENSIONS), " ")
+	glVer := f.GetString(gl.VERSION)
+	ver, gles, err := gl.ParseGLVersion(glVer)
+	if err != nil {
+		return nil, err
+	}
+	floatTriple, ffboErr := floatTripleFor(f, ver, exts)
+	srgbaTriple, srgbErr := srgbaTripleFor(ver, exts)
+	gles31 := gles && (ver[0] > 3 || (ver[0] == 3 && ver[1] >= 1))
+	b := &Backend{
+		glver:       ver,
+		gles:        gles,
+		funcs:       f,
+		floatTriple: floatTriple,
+		alphaTriple: alphaTripleFor(ver),
+		srgbaTriple: srgbaTriple,
+		sharedCtx:   api.Shared,
+	}
+	b.feats.BottomLeftOrigin = true
+	if srgbErr == nil {
+		b.feats.Features |= driver.FeatureSRGB
+	}
+	if ffboErr == nil {
+		b.feats.Features |= driver.FeatureFloatRenderTargets
+	}
+	if gles31 && !brokenGLES31 {
+		b.feats.Features |= driver.FeatureCompute
+	}
+	if hasExtension(exts, "GL_EXT_disjoint_timer_query_webgl2") || hasExtension(exts, "GL_EXT_disjoint_timer_query") {
+		b.feats.Features |= driver.FeatureTimers
+	}
+	b.feats.MaxTextureSize = f.GetInteger(gl.MAX_TEXTURE_SIZE)
+	if !b.sharedCtx {
+		// We have exclusive access to the context, so query the GL state once
+		// instead of at each frame.
+		b.glstate = b.queryState()
+	}
+	return b, nil
+}
+
+func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
+	b.clear = clear
+	if b.sharedCtx {
+		b.glstate = b.queryState()
+		b.savedState = b.glstate
+	}
+	b.state = state{}
+	var renderFBO gl.Framebuffer
+	if target != nil {
+		switch t := target.(type) {
+		case driver.OpenGLRenderTarget:
+			renderFBO = gl.Framebuffer(t)
+		case *texture:
+			renderFBO = t.ensureFBO()
+		default:
+			panic(fmt.Errorf("opengl: invalid render target type: %T", target))
+		}
+	}
+	b.outputFBO = renderFBO
+	b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO)
+	if b.gles {
+		// If the output framebuffer is not in the sRGB colorspace already, emulate it.
+		fbSRGB := false
+		if !b.gles || b.glver[0] > 2 {
+			var fbEncoding int
+			if !renderFBO.Valid() {
+				fbEncoding = b.funcs.GetFramebufferAttachmentParameteri(gl.FRAMEBUFFER, gl.BACK, gl.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING)
+			} else {
+				fbEncoding = b.funcs.GetFramebufferAttachmentParameteri(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING)
+			}
+			fbSRGB = fbEncoding != gl.LINEAR
+		}
+		if !fbSRGB && viewport != (image.Point{}) {
+			if b.sRGBFBO == nil {
+				sfbo, err := NewSRGBFBO(b.funcs, &b.glstate)
+				if err != nil {
+					panic(err)
+				}
+				b.sRGBFBO = sfbo
+			}
+			if err := b.sRGBFBO.Refresh(viewport); err != nil {
+				panic(err)
+			}
+			renderFBO = b.sRGBFBO.Framebuffer()
+		} else if b.sRGBFBO != nil {
+			b.sRGBFBO.Release()
+			b.sRGBFBO = nil
+		}
+	} else {
+		b.glstate.set(b.funcs, gl.FRAMEBUFFER_SRGB, true)
+		if !b.vertArray.Valid() {
+			b.vertArray = b.funcs.CreateVertexArray()
+		}
+		b.glstate.bindVertexArray(b.funcs, b.vertArray)
+	}
+	b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO)
+	if b.sRGBFBO != nil && !clear {
+		b.clearOutput(0, 0, 0, 0)
+	}
+	return &texture{backend: b, fbo: renderFBO, hasFBO: true, foreign: true}
+}
+
+func (b *Backend) EndFrame() {
+	if b.sRGBFBO != nil {
+		b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, b.outputFBO)
+		if b.clear {
+			b.SetBlend(false)
+		} else {
+			b.BlendFunc(driver.BlendFactorOne, driver.BlendFactorOneMinusSrcAlpha)
+			b.SetBlend(true)
+		}
+		b.sRGBFBO.Blit()
+	}
+	if b.sharedCtx {
+		b.restoreState(b.savedState)
+	} else if runtime.GOOS == "android" {
+		// The Android emulator needs the output framebuffer to be current when
+		// eglSwapBuffers is called.
+		b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, b.outputFBO)
+	}
+}
+
+func (b *Backend) queryState() glState {
+	s := glState{
+		prog:       gl.Program(b.funcs.GetBinding(gl.CURRENT_PROGRAM)),
+		arrayBuf:   gl.Buffer(b.funcs.GetBinding(gl.ARRAY_BUFFER_BINDING)),
+		elemBuf:    gl.Buffer(b.funcs.GetBinding(gl.ELEMENT_ARRAY_BUFFER_BINDING)),
+		drawFBO:    gl.Framebuffer(b.funcs.GetBinding(gl.FRAMEBUFFER_BINDING)),
+		clearColor: b.funcs.GetFloat4(gl.COLOR_CLEAR_VALUE),
+		viewport:   b.funcs.GetInteger4(gl.VIEWPORT),
+	}
+	if !b.gles || b.glver[0] > 2 {
+		s.unpack_row_length = b.funcs.GetInteger(gl.UNPACK_ROW_LENGTH)
+		s.pack_row_length = b.funcs.GetInteger(gl.PACK_ROW_LENGTH)
+	}
+	s.blend.enable = b.funcs.IsEnabled(gl.BLEND)
+	s.blend.srcRGB = gl.Enum(b.funcs.GetInteger(gl.BLEND_SRC_RGB))
+	s.blend.dstRGB = gl.Enum(b.funcs.GetInteger(gl.BLEND_DST_RGB))
+	s.blend.srcA = gl.Enum(b.funcs.GetInteger(gl.BLEND_SRC_ALPHA))
+	s.blend.dstA = gl.Enum(b.funcs.GetInteger(gl.BLEND_DST_ALPHA))
+	s.texUnits.active = gl.Enum(b.funcs.GetInteger(gl.ACTIVE_TEXTURE))
+	if !b.gles {
+		s.srgb = b.funcs.IsEnabled(gl.FRAMEBUFFER_SRGB)
+	}
+	if !b.gles || b.glver[0] >= 3 {
+		s.vertArray = gl.VertexArray(b.funcs.GetBinding(gl.VERTEX_ARRAY_BINDING))
+		s.readFBO = gl.Framebuffer(b.funcs.GetBinding(gl.READ_FRAMEBUFFER_BINDING))
+		s.uniBuf = gl.Buffer(b.funcs.GetBinding(gl.UNIFORM_BUFFER_BINDING))
+		for i := range s.uniBufs {
+			s.uniBufs[i] = gl.Buffer(b.funcs.GetBindingi(gl.UNIFORM_BUFFER_BINDING, i))
+		}
+	}
+	if b.gles && (b.glver[0] > 3 || (b.glver[0] == 3 && b.glver[1] >= 1)) {
+		s.storeBuf = gl.Buffer(b.funcs.GetBinding(gl.SHADER_STORAGE_BUFFER_BINDING))
+		for i := range s.storeBufs {
+			s.storeBufs[i] = gl.Buffer(b.funcs.GetBindingi(gl.SHADER_STORAGE_BUFFER_BINDING, i))
+		}
+	}
+	active := s.texUnits.active
+	for i := range s.texUnits.binds {
+		s.activeTexture(b.funcs, gl.TEXTURE0+gl.Enum(i))
+		s.texUnits.binds[i] = gl.Texture(b.funcs.GetBinding(gl.TEXTURE_BINDING_2D))
+	}
+	s.activeTexture(b.funcs, active)
+	for i := range s.vertAttribs {
+		a := &s.vertAttribs[i]
+		a.enabled = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_ENABLED) != gl.FALSE
+		a.obj = gl.Buffer(b.funcs.GetVertexAttribBinding(i, gl.VERTEX_ATTRIB_ARRAY_ENABLED))
+		a.size = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_SIZE)
+		a.typ = gl.Enum(b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_TYPE))
+		a.normalized = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_NORMALIZED) != gl.FALSE
+		a.stride = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_STRIDE)
+		a.offset = b.funcs.GetVertexAttribPointer(i, gl.VERTEX_ATTRIB_ARRAY_POINTER)
+	}
+	return s
+}
+
+func (b *Backend) restoreState(dst glState) {
+	src := &b.glstate
+	f := b.funcs
+	for i, unit := range dst.texUnits.binds {
+		src.bindTexture(f, i, unit)
+	}
+	src.activeTexture(f, dst.texUnits.active)
+	src.bindFramebuffer(f, gl.FRAMEBUFFER, dst.drawFBO)
+	src.bindFramebuffer(f, gl.READ_FRAMEBUFFER, dst.readFBO)
+	src.set(f, gl.BLEND, dst.blend.enable)
+	bf := dst.blend
+	src.setBlendFuncSeparate(f, bf.srcRGB, bf.dstRGB, bf.srcA, bf.dstA)
+	src.set(f, gl.FRAMEBUFFER_SRGB, dst.srgb)
+	src.bindVertexArray(f, dst.vertArray)
+	src.useProgram(f, dst.prog)
+	src.bindBuffer(f, gl.ELEMENT_ARRAY_BUFFER, dst.elemBuf)
+	for i, b := range dst.uniBufs {
+		src.bindBufferBase(f, gl.UNIFORM_BUFFER, i, b)
+	}
+	src.bindBuffer(f, gl.UNIFORM_BUFFER, dst.uniBuf)
+	for i, b := range dst.storeBufs {
+		src.bindBufferBase(f, gl.SHADER_STORAGE_BUFFER, i, b)
+	}
+	src.bindBuffer(f, gl.SHADER_STORAGE_BUFFER, dst.storeBuf)
+	col := dst.clearColor
+	src.setClearColor(f, col[0], col[1], col[2], col[3])
+	for i, attr := range dst.vertAttribs {
+		src.setVertexAttribArray(f, i, attr.enabled)
+		src.vertexAttribPointer(f, attr.obj, i, attr.size, attr.typ, attr.normalized, attr.stride, int(attr.offset))
+	}
+	src.bindBuffer(f, gl.ARRAY_BUFFER, dst.arrayBuf)
+	v := dst.viewport
+	src.setViewport(f, v[0], v[1], v[2], v[3])
+	src.pixelStorei(f, gl.UNPACK_ROW_LENGTH, dst.unpack_row_length)
+	src.pixelStorei(f, gl.PACK_ROW_LENGTH, dst.pack_row_length)
+}
+
+func (s *glState) setVertexAttribArray(f *gl.Functions, idx int, enabled bool) {
+	a := &s.vertAttribs[idx]
+	if enabled != a.enabled {
+		if enabled {
+			f.EnableVertexAttribArray(gl.Attrib(idx))
+		} else {
+			f.DisableVertexAttribArray(gl.Attrib(idx))
+		}
+		a.enabled = enabled
+	}
+}
+
+func (s *glState) vertexAttribPointer(f *gl.Functions, buf gl.Buffer, idx, size int, typ gl.Enum, normalized bool, stride, offset int) {
+	s.bindBuffer(f, gl.ARRAY_BUFFER, buf)
+	a := &s.vertAttribs[idx]
+	a.obj = buf
+	a.size = size
+	a.typ = typ
+	a.normalized = normalized
+	a.stride = stride
+	a.offset = uintptr(offset)
+	f.VertexAttribPointer(gl.Attrib(idx), a.size, a.typ, a.normalized, a.stride, int(a.offset))
+}
+
+func (s *glState) activeTexture(f *gl.Functions, unit gl.Enum) {
+	if unit != s.texUnits.active {
+		f.ActiveTexture(unit)
+		s.texUnits.active = unit
+	}
+}
+
+func (s *glState) bindTexture(f *gl.Functions, unit int, t gl.Texture) {
+	s.activeTexture(f, gl.TEXTURE0+gl.Enum(unit))
+	if !t.Equal(s.texUnits.binds[unit]) {
+		f.BindTexture(gl.TEXTURE_2D, t)
+		s.texUnits.binds[unit] = t
+	}
+}
+
+func (s *glState) bindVertexArray(f *gl.Functions, a gl.VertexArray) {
+	if !a.Equal(s.vertArray) {
+		f.BindVertexArray(a)
+		s.vertArray = a
+	}
+}
+
+func (s *glState) deleteFramebuffer(f *gl.Functions, fbo gl.Framebuffer) {
+	f.DeleteFramebuffer(fbo)
+	if fbo.Equal(s.drawFBO) {
+		s.drawFBO = gl.Framebuffer{}
+	}
+	if fbo.Equal(s.readFBO) {
+		s.readFBO = gl.Framebuffer{}
+	}
+}
+
+func (s *glState) deleteBuffer(f *gl.Functions, b gl.Buffer) {
+	f.DeleteBuffer(b)
+	if b.Equal(s.arrayBuf) {
+		s.arrayBuf = gl.Buffer{}
+	}
+	if b.Equal(s.elemBuf) {
+		s.elemBuf = gl.Buffer{}
+	}
+	if b.Equal(s.uniBuf) {
+		s.uniBuf = gl.Buffer{}
+	}
+	if b.Equal(s.storeBuf) {
+		s.uniBuf = gl.Buffer{}
+	}
+	for i, b2 := range s.storeBufs {
+		if b.Equal(b2) {
+			s.storeBufs[i] = gl.Buffer{}
+		}
+	}
+	for i, b2 := range s.uniBufs {
+		if b.Equal(b2) {
+			s.uniBufs[i] = gl.Buffer{}
+		}
+	}
+}
+
+func (s *glState) deleteProgram(f *gl.Functions, p gl.Program) {
+	f.DeleteProgram(p)
+	if p.Equal(s.prog) {
+		s.prog = gl.Program{}
+	}
+}
+
+func (s *glState) deleteVertexArray(f *gl.Functions, a gl.VertexArray) {
+	f.DeleteVertexArray(a)
+	if a.Equal(s.vertArray) {
+		s.vertArray = gl.VertexArray{}
+	}
+}
+
+func (s *glState) deleteTexture(f *gl.Functions, t gl.Texture) {
+	f.DeleteTexture(t)
+	binds := &s.texUnits.binds
+	for i, obj := range binds {
+		if t.Equal(obj) {
+			binds[i] = gl.Texture{}
+		}
+	}
+}
+
+func (s *glState) useProgram(f *gl.Functions, p gl.Program) {
+	if !p.Equal(s.prog) {
+		f.UseProgram(p)
+		s.prog = p
+	}
+}
+
+func (s *glState) bindFramebuffer(f *gl.Functions, target gl.Enum, fbo gl.Framebuffer) {
+	switch target {
+	case gl.FRAMEBUFFER:
+		if fbo.Equal(s.drawFBO) && fbo.Equal(s.readFBO) {
+			return
+		}
+		s.drawFBO = fbo
+		s.readFBO = fbo
+	case gl.READ_FRAMEBUFFER:
+		if fbo.Equal(s.readFBO) {
+			return
+		}
+		s.readFBO = fbo
+	case gl.DRAW_FRAMEBUFFER:
+		if fbo.Equal(s.drawFBO) {
+			return
+		}
+		s.drawFBO = fbo
+	default:
+		panic("unknown target")
+	}
+	f.BindFramebuffer(target, fbo)
+}
+
+func (s *glState) bindBufferBase(f *gl.Functions, target gl.Enum, idx int, buf gl.Buffer) {
+	switch target {
+	case gl.UNIFORM_BUFFER:
+		if buf.Equal(s.uniBuf) && buf.Equal(s.uniBufs[idx]) {
+			return
+		}
+		s.uniBuf = buf
+		s.uniBufs[idx] = buf
+	case gl.SHADER_STORAGE_BUFFER:
+		if buf.Equal(s.storeBuf) && buf.Equal(s.storeBufs[idx]) {
+			return
+		}
+		s.storeBuf = buf
+		s.storeBufs[idx] = buf
+	default:
+		panic("unknown buffer target")
+	}
+	f.BindBufferBase(target, idx, buf)
+}
+
+func (s *glState) bindBuffer(f *gl.Functions, target gl.Enum, buf gl.Buffer) {
+	switch target {
+	case gl.ARRAY_BUFFER:
+		if buf.Equal(s.arrayBuf) {
+			return
+		}
+		s.arrayBuf = buf
+	case gl.ELEMENT_ARRAY_BUFFER:
+		if buf.Equal(s.elemBuf) {
+			return
+		}
+		s.elemBuf = buf
+	case gl.UNIFORM_BUFFER:
+		if buf.Equal(s.uniBuf) {
+			return
+		}
+		s.uniBuf = buf
+	case gl.SHADER_STORAGE_BUFFER:
+		if buf.Equal(s.storeBuf) {
+			return
+		}
+		s.storeBuf = buf
+	default:
+		panic("unknown buffer target")
+	}
+	f.BindBuffer(target, buf)
+}
+
+func (s *glState) pixelStorei(f *gl.Functions, pname gl.Enum, val int) {
+	switch pname {
+	case gl.UNPACK_ROW_LENGTH:
+		if val == s.unpack_row_length {
+			return
+		}
+		s.unpack_row_length = val
+	case gl.PACK_ROW_LENGTH:
+		if val == s.pack_row_length {
+			return
+		}
+		s.pack_row_length = val
+	default:
+		panic("unsupported PixelStorei pname")
+	}
+	f.PixelStorei(pname, val)
+}
+
+func (s *glState) setClearColor(f *gl.Functions, r, g, b, a float32) {
+	col := [4]float32{r, g, b, a}
+	if col != s.clearColor {
+		f.ClearColor(r, g, b, a)
+		s.clearColor = col
+	}
+}
+
+func (s *glState) setViewport(f *gl.Functions, x, y, width, height int) {
+	view := [4]int{x, y, width, height}
+	if view != s.viewport {
+		f.Viewport(x, y, width, height)
+		s.viewport = view
+	}
+}
+
+func (s *glState) setBlendFuncSeparate(f *gl.Functions, srcRGB, dstRGB, srcA, dstA gl.Enum) {
+	if srcRGB != s.blend.srcRGB || dstRGB != s.blend.dstRGB || srcA != s.blend.srcA || dstA != s.blend.dstA {
+		s.blend.srcRGB = srcRGB
+		s.blend.dstRGB = dstRGB
+		s.blend.srcA = srcA
+		s.blend.dstA = dstA
+		f.BlendFuncSeparate(srcA, dstA, srcA, dstA)
+	}
+}
+
+func (s *glState) set(f *gl.Functions, target gl.Enum, enable bool) {
+	switch target {
+	case gl.FRAMEBUFFER_SRGB:
+		if s.srgb == enable {
+			return
+		}
+		s.srgb = enable
+	case gl.BLEND:
+		if enable == s.blend.enable {
+			return
+		}
+		s.blend.enable = enable
+	default:
+		panic("unknown enable")
+	}
+	if enable {
+		f.Enable(target)
+	} else {
+		f.Disable(target)
+	}
+}
+
+func (b *Backend) Caps() driver.Caps {
+	return b.feats
+}
+
+func (b *Backend) NewTimer() driver.Timer {
+	return &timer{
+		funcs: b.funcs,
+		obj:   b.funcs.CreateQuery(),
+	}
+}
+
+func (b *Backend) IsTimeContinuous() bool {
+	return b.funcs.GetInteger(gl.GPU_DISJOINT_EXT) == gl.FALSE
+}
+
+func (t *texture) ensureFBO() gl.Framebuffer {
+	if t.hasFBO {
+		return t.fbo
+	}
+	b := t.backend
+	oldFBO := b.glstate.drawFBO
+	defer func() {
+		b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, oldFBO)
+	}()
+	glErr(b.funcs)
+	fb := b.funcs.CreateFramebuffer()
+	b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fb)
+	if err := glErr(b.funcs); err != nil {
+		b.funcs.DeleteFramebuffer(fb)
+		panic(err)
+	}
+	b.funcs.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.obj, 0)
+	if st := b.funcs.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
+		b.funcs.DeleteFramebuffer(fb)
+		panic(fmt.Errorf("incomplete framebuffer, status = 0x%x, err = %d", st, b.funcs.GetError()))
+	}
+	t.fbo = fb
+	t.hasFBO = true
+	return fb
+}
+
+func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, binding driver.BufferBinding) (driver.Texture, error) {
+	glErr(b.funcs)
+	tex := &texture{backend: b, obj: b.funcs.CreateTexture(), width: width, height: height, bindings: binding}
+	switch format {
+	case driver.TextureFormatFloat:
+		tex.triple = b.floatTriple
+	case driver.TextureFormatSRGBA:
+		tex.triple = b.srgbaTriple
+	case driver.TextureFormatRGBA8:
+		tex.triple = textureTriple{gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE}
+	default:
+		return nil, errors.New("unsupported texture format")
+	}
+	b.BindTexture(0, tex)
+	min, mipmap := toTexFilter(minFilter)
+	mag, _ := toTexFilter(magFilter)
+	if b.gles && b.glver[0] < 3 {
+		// OpenGL ES 2 only supports mipmaps for power-of-two textures.
+		mipmap = false
+	}
+	tex.mipmap = mipmap
+	b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, mag)
+	b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, min)
+	b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
+	b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
+	if mipmap {
+		nmipmaps := 1
+		if mipmap {
+			dim := width
+			if height > dim {
+				dim = height
+			}
+			log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
+			nmipmaps = log2 + 1
+		}
+		// Immutable textures are required for BindImageTexture, and can't hurt otherwise.
+		b.funcs.TexStorage2D(gl.TEXTURE_2D, nmipmaps, tex.triple.internalFormat, width, height)
+	} else {
+		b.funcs.TexImage2D(gl.TEXTURE_2D, 0, tex.triple.internalFormat, width, height, tex.triple.format, tex.triple.typ)
+	}
+	if err := glErr(b.funcs); err != nil {
+		tex.Release()
+		return nil, err
+	}
+	return tex, nil
+}
+
+func (b *Backend) NewBuffer(typ driver.BufferBinding, size int) (driver.Buffer, error) {
+	glErr(b.funcs)
+	buf := &buffer{backend: b, typ: typ, size: size}
+	if typ&driver.BufferBindingUniforms != 0 {
+		if typ != driver.BufferBindingUniforms {
+			return nil, errors.New("uniforms buffers cannot be bound as anything else")
+		}
+		buf.data = make([]byte, size)
+	}
+	if typ&^driver.BufferBindingUniforms != 0 {
+		buf.hasBuffer = true
+		buf.obj = b.funcs.CreateBuffer()
+		if err := glErr(b.funcs); err != nil {
+			buf.Release()
+			return nil, err
+		}
+		firstBinding := firstBufferType(typ)
+		b.glstate.bindBuffer(b.funcs, firstBinding, buf.obj)
+		b.funcs.BufferData(firstBinding, size, gl.DYNAMIC_DRAW, nil)
+	}
+	return buf, nil
+}
+
+func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) {
+	glErr(b.funcs)
+	obj := b.funcs.CreateBuffer()
+	buf := &buffer{backend: b, obj: obj, typ: typ, size: len(data), hasBuffer: true}
+	firstBinding := firstBufferType(typ)
+	b.glstate.bindBuffer(b.funcs, firstBinding, buf.obj)
+	b.funcs.BufferData(firstBinding, len(data), gl.STATIC_DRAW, data)
+	buf.immutable = true
+	if err := glErr(b.funcs); err != nil {
+		buf.Release()
+		return nil, err
+	}
+	return buf, nil
+}
+
+func glErr(f *gl.Functions) error {
+	if st := f.GetError(); st != gl.NO_ERROR {
+		return fmt.Errorf("glGetError: %#x", st)
+	}
+	return nil
+}
+
+func (b *Backend) Release() {
+	if b.sRGBFBO != nil {
+		b.sRGBFBO.Release()
+	}
+	if b.vertArray.Valid() {
+		b.glstate.deleteVertexArray(b.funcs, b.vertArray)
+	}
+	*b = Backend{}
+}
+
+func (b *Backend) DispatchCompute(x, y, z int) {
+	for binding, buf := range b.storage {
+		if buf != nil {
+			b.glstate.bindBufferBase(b.funcs, gl.SHADER_STORAGE_BUFFER, binding, buf.obj)
+		}
+	}
+	b.funcs.DispatchCompute(x, y, z)
+	b.funcs.MemoryBarrier(gl.ALL_BARRIER_BITS)
+}
+
+func (b *Backend) BindImageTexture(unit int, tex driver.Texture) {
+	t := tex.(*texture)
+	var acc gl.Enum
+	switch t.bindings & (driver.BufferBindingShaderStorageRead | driver.BufferBindingShaderStorageWrite) {
+	case driver.BufferBindingShaderStorageRead:
+		acc = gl.READ_ONLY
+	case driver.BufferBindingShaderStorageWrite:
+		acc = gl.WRITE_ONLY
+	case driver.BufferBindingShaderStorageRead | driver.BufferBindingShaderStorageWrite:
+		acc = gl.READ_WRITE
+	default:
+		panic("unsupported access bits")
+	}
+	b.funcs.BindImageTexture(unit, t.obj, 0, false, 0, acc, t.triple.internalFormat)
+}
+
+func (b *Backend) BlendFunc(sfactor, dfactor driver.BlendFactor) {
+	src, dst := toGLBlendFactor(sfactor), toGLBlendFactor(dfactor)
+	b.glstate.setBlendFuncSeparate(b.funcs, src, dst, src, dst)
+}
+
+func toGLBlendFactor(f driver.BlendFactor) gl.Enum {
+	switch f {
+	case driver.BlendFactorOne:
+		return gl.ONE
+	case driver.BlendFactorOneMinusSrcAlpha:
+		return gl.ONE_MINUS_SRC_ALPHA
+	case driver.BlendFactorZero:
+		return gl.ZERO
+	case driver.BlendFactorDstColor:
+		return gl.DST_COLOR
+	default:
+		panic("unsupported blend factor")
+	}
+}
+
+func (b *Backend) SetBlend(enable bool) {
+	b.glstate.set(b.funcs, gl.BLEND, enable)
+}
+
+func (b *Backend) DrawElements(off, count int) {
+	b.prepareDraw()
+	// off is in 16-bit indices, but DrawElements take a byte offset.
+	byteOff := off * 2
+	b.funcs.DrawElements(toGLDrawMode(b.state.pipeline.topology), count, gl.UNSIGNED_SHORT, byteOff)
+}
+
+func (b *Backend) DrawArrays(off, count int) {
+	b.prepareDraw()
+	b.funcs.DrawArrays(toGLDrawMode(b.state.pipeline.topology), off, count)
+}
+
+func (b *Backend) prepareDraw() {
+	p := b.state.pipeline
+	if p == nil {
+		return
+	}
+	b.setupVertexArrays()
+}
+
+func toGLDrawMode(mode driver.Topology) gl.Enum {
+	switch mode {
+	case driver.TopologyTriangleStrip:
+		return gl.TRIANGLE_STRIP
+	case driver.TopologyTriangles:
+		return gl.TRIANGLES
+	default:
+		panic("unsupported draw mode")
+	}
+}
+
+func (b *Backend) Viewport(x, y, width, height int) {
+	b.glstate.setViewport(b.funcs, x, y, width, height)
+}
+
+func (b *Backend) clearOutput(colR, colG, colB, colA float32) {
+	b.glstate.setClearColor(b.funcs, colR, colG, colB, colA)
+	b.funcs.Clear(gl.COLOR_BUFFER_BIT)
+}
+
+func (b *Backend) NewComputeProgram(src shader.Sources) (driver.Program, error) {
+	// We don't support ES 3.1 compute, see brokenGLES31 above.
+	const GLES31Source = ""
+	p, err := gl.CreateComputeProgram(b.funcs, GLES31Source)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %v", src.Name, err)
+	}
+	return &program{
+		backend: b,
+		obj:     p,
+	}, nil
+}
+
+func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) {
+	glslSrc := b.glslFor(src)
+	sh, err := gl.CreateShader(b.funcs, gl.VERTEX_SHADER, glslSrc)
+	return &glshader{backend: b, obj: sh, src: src}, err
+}
+
+func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) {
+	glslSrc := b.glslFor(src)
+	sh, err := gl.CreateShader(b.funcs, gl.FRAGMENT_SHADER, glslSrc)
+	return &glshader{backend: b, obj: sh, src: src}, err
+}
+
+func (b *Backend) glslFor(src shader.Sources) string {
+	if b.gles {
+		return src.GLSL100ES
+	} else {
+		return src.GLSL150
+	}
+}
+
+func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) {
+	p, err := b.newProgram(desc)
+	if err != nil {
+		return nil, err
+	}
+	layout := desc.VertexLayout
+	vsrc := desc.VertexShader.(*glshader).src
+	if len(vsrc.Inputs) != len(layout.Inputs) {
+		return nil, fmt.Errorf("opengl: got %d inputs, expected %d", len(layout.Inputs), len(vsrc.Inputs))
+	}
+	for i, inp := range vsrc.Inputs {
+		if exp, got := inp.Size, layout.Inputs[i].Size; exp != got {
+			return nil, fmt.Errorf("opengl: data size mismatch for %q: got %d expected %d", inp.Name, got, exp)
+		}
+	}
+	return &pipeline{
+		prog:     p,
+		inputs:   vsrc.Inputs,
+		layout:   layout,
+		blend:    desc.BlendDesc,
+		topology: desc.Topology,
+	}, nil
+}
+
+func (b *Backend) newProgram(desc driver.PipelineDesc) (*program, error) {
+	p := b.funcs.CreateProgram()
+	if !p.Valid() {
+		return nil, errors.New("opengl: glCreateProgram failed")
+	}
+	vsh, fsh := desc.VertexShader.(*glshader), desc.FragmentShader.(*glshader)
+	b.funcs.AttachShader(p, vsh.obj)
+	b.funcs.AttachShader(p, fsh.obj)
+	for _, inp := range vsh.src.Inputs {
+		b.funcs.BindAttribLocation(p, gl.Attrib(inp.Location), inp.Name)
+	}
+	b.funcs.LinkProgram(p)
+	if b.funcs.GetProgrami(p, gl.LINK_STATUS) == 0 {
+		log := b.funcs.GetProgramInfoLog(p)
+		b.funcs.DeleteProgram(p)
+		return nil, fmt.Errorf("opengl: program link failed: %s", strings.TrimSpace(log))
+	}
+	prog := &program{
+		backend: b,
+		obj:     p,
+	}
+	b.glstate.useProgram(b.funcs, p)
+	// Bind texture uniforms.
+	for _, tex := range vsh.src.Textures {
+		u := b.funcs.GetUniformLocation(p, tex.Name)
+		if u.Valid() {
+			b.funcs.Uniform1i(u, tex.Binding)
+		}
+	}
+	for _, tex := range fsh.src.Textures {
+		u := b.funcs.GetUniformLocation(p, tex.Name)
+		if u.Valid() {
+			b.funcs.Uniform1i(u, tex.Binding)
+		}
+	}
+	prog.vertUniforms.setup(b.funcs, p, vsh.src.Uniforms.Size, vsh.src.Uniforms.Locations)
+	prog.fragUniforms.setup(b.funcs, p, fsh.src.Uniforms.Size, fsh.src.Uniforms.Locations)
+	return prog, nil
+}
+
+func (b *Backend) BindStorageBuffer(binding int, buf driver.Buffer) {
+	bf := buf.(*buffer)
+	if bf.typ&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) == 0 {
+		panic("not a shader storage buffer")
+	}
+	b.storage[binding] = bf
+}
+
+func (b *Backend) BindUniforms(buf driver.Buffer) {
+	bf := buf.(*buffer)
+	if bf.typ&driver.BufferBindingUniforms == 0 {
+		panic("not a uniform buffer")
+	}
+	b.state.pipeline.prog.vertUniforms.update(b.funcs, bf)
+	b.state.pipeline.prog.fragUniforms.update(b.funcs, bf)
+}
+
+func (b *Backend) BindProgram(prog driver.Program) {
+	p := prog.(*program)
+	b.glstate.useProgram(b.funcs, p.obj)
+}
+
+func (s *glshader) Release() {
+	s.backend.funcs.DeleteShader(s.obj)
+}
+
+func (p *program) Release() {
+	p.backend.glstate.deleteProgram(p.backend.funcs, p.obj)
+}
+
+func (u *uniforms) setup(funcs *gl.Functions, p gl.Program, uniformSize int, uniforms []shader.UniformLocation) {
+	u.locs = make([]uniformLocation, len(uniforms))
+	for i, uniform := range uniforms {
+		loc := funcs.GetUniformLocation(p, uniform.Name)
+		u.locs[i] = uniformLocation{uniform: loc, offset: uniform.Offset, typ: uniform.Type, size: uniform.Size}
+	}
+	u.size = uniformSize
+}
+
+func (p *uniforms) update(funcs *gl.Functions, buf *buffer) {
+	if buf.size < p.size {
+		panic(fmt.Errorf("uniform buffer too small, got %d need %d", buf.size, p.size))
+	}
+	data := buf.data
+	for _, u := range p.locs {
+		if !u.uniform.Valid() {
+			continue
+		}
+		data := data[u.offset:]
+		switch {
+		case u.typ == shader.DataTypeFloat && u.size == 1:
+			data := data[:4]
+			v := *(*[1]float32)(unsafe.Pointer(&data[0]))
+			funcs.Uniform1f(u.uniform, v[0])
+		case u.typ == shader.DataTypeFloat && u.size == 2:
+			data := data[:8]
+			v := *(*[2]float32)(unsafe.Pointer(&data[0]))
+			funcs.Uniform2f(u.uniform, v[0], v[1])
+		case u.typ == shader.DataTypeFloat && u.size == 3:
+			data := data[:12]
+			v := *(*[3]float32)(unsafe.Pointer(&data[0]))
+			funcs.Uniform3f(u.uniform, v[0], v[1], v[2])
+		case u.typ == shader.DataTypeFloat && u.size == 4:
+			data := data[:16]
+			v := *(*[4]float32)(unsafe.Pointer(&data[0]))
+			funcs.Uniform4f(u.uniform, v[0], v[1], v[2], v[3])
+		default:
+			panic("unsupported uniform data type or size")
+		}
+	}
+}
+
+func (b *buffer) Upload(data []byte) {
+	if b.immutable {
+		panic("immutable buffer")
+	}
+	if len(data) > b.size {
+		panic("buffer size overflow")
+	}
+	copy(b.data, data)
+	if b.hasBuffer {
+		firstBinding := firstBufferType(b.typ)
+		b.backend.glstate.bindBuffer(b.backend.funcs, firstBinding, b.obj)
+		if len(data) == b.size {
+			// the iOS GL implementation doesn't recognize when BufferSubData
+			// clears the entire buffer. Tell it and avoid GPU stalls.
+			// See also https://github.com/godotengine/godot/issues/23956.
+			b.backend.funcs.BufferData(firstBinding, b.size, gl.DYNAMIC_DRAW, data)
+		} else {
+			b.backend.funcs.BufferSubData(firstBinding, 0, data)
+		}
+	}
+}
+
+func (b *buffer) Download(data []byte) error {
+	if len(data) > b.size {
+		panic("buffer size overflow")
+	}
+	if !b.hasBuffer {
+		copy(data, b.data)
+		return nil
+	}
+	firstBinding := firstBufferType(b.typ)
+	b.backend.glstate.bindBuffer(b.backend.funcs, firstBinding, b.obj)
+	bufferMap := b.backend.funcs.MapBufferRange(firstBinding, 0, len(data), gl.MAP_READ_BIT)
+	if bufferMap == nil {
+		return fmt.Errorf("MapBufferRange: error %#x", b.backend.funcs.GetError())
+	}
+	copy(data, bufferMap)
+	if !b.backend.funcs.UnmapBuffer(firstBinding) {
+		return driver.ErrContentLost
+	}
+	return nil
+}
+
+func (b *buffer) Release() {
+	if b.hasBuffer {
+		b.backend.glstate.deleteBuffer(b.backend.funcs, b.obj)
+		b.hasBuffer = false
+	}
+}
+
+func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) {
+	gbuf := buf.(*buffer)
+	if gbuf.typ&driver.BufferBindingVertices == 0 {
+		panic("not a vertex buffer")
+	}
+	b.state.buffer = bufferBinding{obj: gbuf.obj, offset: offset}
+}
+
+func (b *Backend) setupVertexArrays() {
+	p := b.state.pipeline
+	inputs := p.inputs
+	if len(inputs) == 0 {
+		return
+	}
+	layout := p.layout
+	const max = len(b.glstate.vertAttribs)
+	var enabled [max]bool
+	buf := b.state.buffer
+	for i, inp := range inputs {
+		l := layout.Inputs[i]
+		var gltyp gl.Enum
+		switch l.Type {
+		case shader.DataTypeFloat:
+			gltyp = gl.FLOAT
+		case shader.DataTypeShort:
+			gltyp = gl.SHORT
+		default:
+			panic("unsupported data type")
+		}
+		enabled[inp.Location] = true
+		b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
+	}
+	for i := 0; i < max; i++ {
+		b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
+	}
+}
+
+func (b *Backend) BindIndexBuffer(buf driver.Buffer) {
+	gbuf := buf.(*buffer)
+	if gbuf.typ&driver.BufferBindingIndices == 0 {
+		panic("not an index buffer")
+	}
+	b.glstate.bindBuffer(b.funcs, gl.ELEMENT_ARRAY_BUFFER, gbuf.obj)
+}
+
+func (b *Backend) CopyTexture(dst driver.Texture, dstOrigin image.Point, src driver.Texture, srcRect image.Rectangle) {
+	const unit = 0
+	oldTex := b.glstate.texUnits.binds[unit]
+	defer func() {
+		b.glstate.bindTexture(b.funcs, unit, oldTex)
+	}()
+	b.glstate.bindTexture(b.funcs, unit, dst.(*texture).obj)
+	b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, src.(*texture).ensureFBO())
+	sz := srcRect.Size()
+	b.funcs.CopyTexSubImage2D(gl.TEXTURE_2D, 0, dstOrigin.X, dstOrigin.Y, srcRect.Min.X, srcRect.Min.Y, sz.X, sz.Y)
+}
+
+func (t *texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
+	glErr(t.backend.funcs)
+	t.backend.glstate.bindFramebuffer(t.backend.funcs, gl.FRAMEBUFFER, t.ensureFBO())
+	w, h := src.Dx(), src.Dy()
+	if len(pixels) < w*h*4 {
+		return errors.New("unexpected RGBA size")
+	}
+	// OpenGL ES 2 doesn't support PACK_ROW_LENGTH != 0. Avoid it if possible.
+	rowLen := 0
+	if n := stride / 4; n != w {
+		rowLen = n
+	}
+	if rowLen == 0 || t.backend.glver[0] > 2 {
+		t.backend.glstate.pixelStorei(t.backend.funcs, gl.PACK_ROW_LENGTH, rowLen)
+		t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
+	} else {
+		tmp := make([]byte, w*h*4)
+		t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp)
+		for y := 0; y < h; y++ {
+			copy(pixels[y*stride:], tmp[y*w*4:])
+		}
+	}
+	return glErr(t.backend.funcs)
+}
+
+func (b *Backend) BindPipeline(pl driver.Pipeline) {
+	p := pl.(*pipeline)
+	b.state.pipeline = p
+	b.glstate.useProgram(b.funcs, p.prog.obj)
+	b.SetBlend(p.blend.Enable)
+	b.BlendFunc(p.blend.SrcFactor, p.blend.DstFactor)
+}
+
+func (b *Backend) BeginCompute() {
+	b.funcs.MemoryBarrier(gl.ALL_BARRIER_BITS)
+}
+
+func (b *Backend) EndCompute() {
+}
+
+func (b *Backend) BeginRenderPass(tex driver.Texture, desc driver.LoadDesc) {
+	fbo := tex.(*texture).ensureFBO()
+	b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo)
+	switch desc.Action {
+	case driver.LoadActionClear:
+		c := desc.ClearColor
+		b.clearOutput(c.R, c.G, c.B, c.A)
+	case driver.LoadActionInvalidate:
+		b.funcs.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
+	}
+}
+
+func (b *Backend) EndRenderPass() {
+}
+
+func (f *texture) ImplementsRenderTarget() {}
+
+func (p *pipeline) Release() {
+	p.prog.Release()
+	*p = pipeline{}
+}
+
+func toTexFilter(f driver.TextureFilter) (int, bool) {
+	switch f {
+	case driver.FilterNearest:
+		return gl.NEAREST, false
+	case driver.FilterLinear:
+		return gl.LINEAR, false
+	case driver.FilterLinearMipmapLinear:
+		return gl.LINEAR_MIPMAP_LINEAR, true
+	default:
+		panic("unsupported texture filter")
+	}
+}
+
+func (b *Backend) PrepareTexture(tex driver.Texture) {}
+
+func (b *Backend) BindTexture(unit int, t driver.Texture) {
+	b.glstate.bindTexture(b.funcs, unit, t.(*texture).obj)
+}
+
+func (t *texture) Release() {
+	if t.foreign {
+		panic("texture not created by NewTexture")
+	}
+	if t.hasFBO {
+		t.backend.glstate.deleteFramebuffer(t.backend.funcs, t.fbo)
+	}
+	t.backend.glstate.deleteTexture(t.backend.funcs, t.obj)
+}
+
+func (t *texture) Upload(offset, size image.Point, pixels []byte, stride int) {
+	if min := size.X * size.Y * 4; min > len(pixels) {
+		panic(fmt.Errorf("size %d larger than data %d", min, len(pixels)))
+	}
+	t.backend.BindTexture(0, t)
+	// WebGL 1 doesn't support UNPACK_ROW_LENGTH != 0. Avoid it if possible.
+	rowLen := 0
+	if n := stride / 4; n != size.X {
+		rowLen = n
+	}
+	t.backend.glstate.pixelStorei(t.backend.funcs, gl.UNPACK_ROW_LENGTH, rowLen)
+	t.backend.funcs.TexSubImage2D(gl.TEXTURE_2D, 0, offset.X, offset.Y, size.X, size.Y, t.triple.format, t.triple.typ, pixels)
+	if t.mipmap {
+		t.backend.funcs.GenerateMipmap(gl.TEXTURE_2D)
+	}
+}
+
+func (t *timer) Begin() {
+	t.funcs.BeginQuery(gl.TIME_ELAPSED_EXT, t.obj)
+}
+
+func (t *timer) End() {
+	t.funcs.EndQuery(gl.TIME_ELAPSED_EXT)
+}
+
+func (t *timer) ready() bool {
+	return t.funcs.GetQueryObjectuiv(t.obj, gl.QUERY_RESULT_AVAILABLE) == gl.TRUE
+}
+
+func (t *timer) Release() {
+	t.funcs.DeleteQuery(t.obj)
+}
+
+func (t *timer) Duration() (time.Duration, bool) {
+	if !t.ready() {
+		return 0, false
+	}
+	nanos := t.funcs.GetQueryObjectuiv(t.obj, gl.QUERY_RESULT)
+	return time.Duration(nanos), true
+}
+
+// floatTripleFor determines the best texture triple for floating point FBOs.
+func floatTripleFor(f *gl.Functions, ver [2]int, exts []string) (textureTriple, error) {
+	var triples []textureTriple
+	if ver[0] >= 3 {
+		triples = append(triples, textureTriple{gl.R16F, gl.Enum(gl.RED), gl.Enum(gl.HALF_FLOAT)})
+	}
+	// According to the OES_texture_half_float specification, EXT_color_buffer_half_float is needed to
+	// render to FBOs. However, the Safari WebGL1 implementation does support half-float FBOs but does not
+	// report EXT_color_buffer_half_float support. The triples are verified below, so it doesn't matter if we're
+	// wrong.
+	if hasExtension(exts, "GL_OES_texture_half_float") || hasExtension(exts, "GL_EXT_color_buffer_half_float") {
+		// Try single channel.
+		triples = append(triples, textureTriple{gl.LUMINANCE, gl.Enum(gl.LUMINANCE), gl.Enum(gl.HALF_FLOAT_OES)})
+		// Fallback to 4 channels.
+		triples = append(triples, textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.HALF_FLOAT_OES)})
+	}
+	if hasExtension(exts, "GL_OES_texture_float") || hasExtension(exts, "GL_EXT_color_buffer_float") {
+		triples = append(triples, textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.FLOAT)})
+	}
+	tex := f.CreateTexture()
+	defer f.DeleteTexture(tex)
+	defTex := gl.Texture(f.GetBinding(gl.TEXTURE_BINDING_2D))
+	defer f.BindTexture(gl.TEXTURE_2D, defTex)
+	f.BindTexture(gl.TEXTURE_2D, tex)
+	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
+	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
+	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
+	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
+	fbo := f.CreateFramebuffer()
+	defer f.DeleteFramebuffer(fbo)
+	defFBO := gl.Framebuffer(f.GetBinding(gl.FRAMEBUFFER_BINDING))
+	f.BindFramebuffer(gl.FRAMEBUFFER, fbo)
+	defer f.BindFramebuffer(gl.FRAMEBUFFER, defFBO)
+	var attempts []string
+	for _, tt := range triples {
+		const size = 256
+		f.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, size, size, tt.format, tt.typ)
+		f.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)
+		st := f.CheckFramebufferStatus(gl.FRAMEBUFFER)
+		if st == gl.FRAMEBUFFER_COMPLETE {
+			return tt, nil
+		}
+		attempts = append(attempts, fmt.Sprintf("(0x%x, 0x%x, 0x%x): 0x%x", tt.internalFormat, tt.format, tt.typ, st))
+	}
+	return textureTriple{}, fmt.Errorf("floating point fbos not supported (attempted %s)", attempts)
+}
+
+func srgbaTripleFor(ver [2]int, exts []string) (textureTriple, error) {
+	switch {
+	case ver[0] >= 3:
+		return textureTriple{gl.SRGB8_ALPHA8, gl.Enum(gl.RGBA), gl.Enum(gl.UNSIGNED_BYTE)}, nil
+	case hasExtension(exts, "GL_EXT_sRGB"):
+		return textureTriple{gl.SRGB_ALPHA_EXT, gl.Enum(gl.SRGB_ALPHA_EXT), gl.Enum(gl.UNSIGNED_BYTE)}, nil
+	default:
+		return textureTriple{}, errors.New("no sRGB texture formats found")
+	}
+}
+
+func alphaTripleFor(ver [2]int) textureTriple {
+	intf, f := gl.Enum(gl.R8), gl.Enum(gl.RED)
+	if ver[0] < 3 {
+		// R8, RED not supported on OpenGL ES 2.0.
+		intf, f = gl.LUMINANCE, gl.Enum(gl.LUMINANCE)
+	}
+	return textureTriple{intf, f, gl.UNSIGNED_BYTE}
+}
+
+func hasExtension(exts []string, ext string) bool {
+	for _, e := range exts {
+		if ext == e {
+			return true
+		}
+	}
+	return false
+}
+
+func firstBufferType(typ driver.BufferBinding) gl.Enum {
+	switch {
+	case typ&driver.BufferBindingIndices != 0:
+		return gl.ELEMENT_ARRAY_BUFFER
+	case typ&driver.BufferBindingVertices != 0:
+		return gl.ARRAY_BUFFER
+	case typ&driver.BufferBindingUniforms != 0:
+		return gl.UNIFORM_BUFFER
+	case typ&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0:
+		return gl.SHADER_STORAGE_BUFFER
+	default:
+		panic("unsupported buffer type")
+	}
+}

+ 176 - 0
vendor/gioui.org/gpu/internal/opengl/srgb.go

@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package opengl
+
+import (
+	"errors"
+	"fmt"
+	"image"
+	"runtime"
+	"strings"
+
+	"gioui.org/internal/byteslice"
+	"gioui.org/internal/gl"
+)
+
+// SRGBFBO implements an intermediate sRGB FBO
+// for gamma-correct rendering on platforms without
+// sRGB enabled native framebuffers.
+type SRGBFBO struct {
+	c        *gl.Functions
+	state    *glState
+	viewport image.Point
+	fbo      gl.Framebuffer
+	tex      gl.Texture
+	blitted  bool
+	quad     gl.Buffer
+	prog     gl.Program
+	format   textureTriple
+}
+
+func NewSRGBFBO(f *gl.Functions, state *glState) (*SRGBFBO, error) {
+	glVer := f.GetString(gl.VERSION)
+	ver, _, err := gl.ParseGLVersion(glVer)
+	if err != nil {
+		return nil, err
+	}
+	exts := strings.Split(f.GetString(gl.EXTENSIONS), " ")
+	srgbTriple, err := srgbaTripleFor(ver, exts)
+	if err != nil {
+		// Fall back to the linear RGB colorspace, at the cost of color precision loss.
+		srgbTriple = textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.UNSIGNED_BYTE)}
+	}
+	s := &SRGBFBO{
+		c:      f,
+		state:  state,
+		format: srgbTriple,
+		fbo:    f.CreateFramebuffer(),
+		tex:    f.CreateTexture(),
+	}
+	state.bindTexture(f, 0, s.tex)
+	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
+	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
+	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
+	f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
+	return s, nil
+}
+
+func (s *SRGBFBO) Blit() {
+	if !s.blitted {
+		prog, err := gl.CreateProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"})
+		if err != nil {
+			panic(err)
+		}
+		s.prog = prog
+		s.state.useProgram(s.c, prog)
+		s.c.Uniform1i(s.c.GetUniformLocation(prog, "tex"), 0)
+		s.quad = s.c.CreateBuffer()
+		s.state.bindBuffer(s.c, gl.ARRAY_BUFFER, s.quad)
+		coords := byteslice.Slice([]float32{
+			-1, +1, 0, 1,
+			+1, +1, 1, 1,
+			-1, -1, 0, 0,
+			+1, -1, 1, 0,
+		})
+		s.c.BufferData(gl.ARRAY_BUFFER, len(coords), gl.STATIC_DRAW, coords)
+		s.blitted = true
+	}
+	s.state.useProgram(s.c, s.prog)
+	s.state.bindTexture(s.c, 0, s.tex)
+	s.state.vertexAttribPointer(s.c, s.quad, 0 /* pos */, 2, gl.FLOAT, false, 4*4, 0)
+	s.state.vertexAttribPointer(s.c, s.quad, 1 /* uv */, 2, gl.FLOAT, false, 4*4, 4*2)
+	s.state.setVertexAttribArray(s.c, 0, true)
+	s.state.setVertexAttribArray(s.c, 1, true)
+	s.c.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
+	s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.fbo)
+	s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
+}
+
+func (s *SRGBFBO) Framebuffer() gl.Framebuffer {
+	return s.fbo
+}
+
+func (s *SRGBFBO) Refresh(viewport image.Point) error {
+	if viewport.X == 0 || viewport.Y == 0 {
+		return errors.New("srgb: zero-sized framebuffer")
+	}
+	if s.viewport == viewport {
+		return nil
+	}
+	s.viewport = viewport
+	s.state.bindTexture(s.c, 0, s.tex)
+	s.c.TexImage2D(gl.TEXTURE_2D, 0, s.format.internalFormat, viewport.X, viewport.Y, s.format.format, s.format.typ)
+	s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.fbo)
+	s.c.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, s.tex, 0)
+	if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
+		return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError())
+	}
+
+	if runtime.GOOS == "js" {
+		// With macOS Safari, rendering to and then reading from a SRGB8_ALPHA8
+		// texture result in twice gamma corrected colors. Using a plain RGBA
+		// texture seems to work.
+		s.state.setClearColor(s.c, .5, .5, .5, 1.0)
+		s.c.Clear(gl.COLOR_BUFFER_BIT)
+		var pixel [4]byte
+		s.c.ReadPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel[:])
+		if pixel[0] == 128 { // Correct sRGB color value is ~188
+			s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.X, viewport.Y, gl.RGBA, gl.UNSIGNED_BYTE)
+			if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
+				return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError())
+			}
+		}
+	}
+
+	return nil
+}
+
+func (s *SRGBFBO) Release() {
+	s.state.deleteFramebuffer(s.c, s.fbo)
+	s.state.deleteTexture(s.c, s.tex)
+	if s.blitted {
+		s.state.deleteBuffer(s.c, s.quad)
+		s.state.deleteProgram(s.c, s.prog)
+	}
+	s.c = nil
+}
+
+const (
+	blitVSrc = `
+#version 100
+
+precision highp float;
+
+attribute vec2 pos;
+attribute vec2 uv;
+
+varying vec2 vUV;
+
+void main() {
+    gl_Position = vec4(pos, 0, 1);
+    vUV = uv;
+}
+`
+	blitFSrc = `
+#version 100
+
+precision mediump float;
+
+uniform sampler2D tex;
+varying vec2 vUV;
+
+vec3 gamma(vec3 rgb) {
+	vec3 exp = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055);
+	vec3 lin = rgb * vec3(12.92);
+	bvec3 cut = lessThan(rgb, vec3(0.0031308));
+	return vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b);
+}
+
+void main() {
+    vec4 col = texture2D(tex, vUV);
+	vec3 rgb = col.rgb;
+	rgb = gamma(rgb);
+	gl_FragColor = vec4(rgb, col.a);
+}
+`
+)

+ 1163 - 0
vendor/gioui.org/gpu/internal/vulkan/vulkan.go

@@ -0,0 +1,1163 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build (linux || freebsd) && !novulkan
+// +build linux freebsd
+// +build !novulkan
+
+package vulkan
+
+import (
+	"errors"
+	"fmt"
+	"image"
+	"math/bits"
+
+	"gioui.org/gpu/internal/driver"
+	"gioui.org/internal/vk"
+	"gioui.org/shader"
+)
+
+type Backend struct {
+	physDev vk.PhysicalDevice
+	dev     vk.Device
+	queue   vk.Queue
+	cmdPool struct {
+		current vk.CommandBuffer
+		pool    vk.CommandPool
+		used    int
+		buffers []vk.CommandBuffer
+	}
+	outFormat vk.Format
+	staging   struct {
+		buf  *Buffer
+		mem  []byte
+		size int
+		cap  int
+	}
+	defers     []func(d vk.Device)
+	frameSig   vk.Semaphore
+	frameFence vk.Fence
+	waitSems   []vk.Semaphore
+	waitStages []vk.PipelineStageFlags
+	sigSems    []vk.Semaphore
+	fence      vk.Fence
+
+	allPipes []*Pipeline
+
+	pipe *Pipeline
+
+	passes map[passKey]vk.RenderPass
+
+	// bindings and offset are temporary storage for BindVertexBuffer.
+	bindings []vk.Buffer
+	offsets  []vk.DeviceSize
+
+	desc struct {
+		dirty    bool
+		texBinds [texUnits]*Texture
+		bufBinds [storageUnits]*Buffer
+	}
+
+	caps driver.Features
+}
+
+type passKey struct {
+	fmt         vk.Format
+	loadAct     vk.AttachmentLoadOp
+	initLayout  vk.ImageLayout
+	finalLayout vk.ImageLayout
+}
+
+type Texture struct {
+	backend    *Backend
+	img        vk.Image
+	mem        vk.DeviceMemory
+	view       vk.ImageView
+	sampler    vk.Sampler
+	fbo        vk.Framebuffer
+	format     vk.Format
+	mipmaps    int
+	layout     vk.ImageLayout
+	passLayout vk.ImageLayout
+	width      int
+	height     int
+	acquire    vk.Semaphore
+	foreign    bool
+
+	scope struct {
+		stage  vk.PipelineStageFlags
+		access vk.AccessFlags
+	}
+}
+
+type Shader struct {
+	dev       vk.Device
+	module    vk.ShaderModule
+	pushRange vk.PushConstantRange
+	src       shader.Sources
+}
+
+type Pipeline struct {
+	backend    *Backend
+	pipe       vk.Pipeline
+	pushRanges []vk.PushConstantRange
+	ninputs    int
+	desc       *descPool
+}
+
+type descPool struct {
+	layout     vk.PipelineLayout
+	descLayout vk.DescriptorSetLayout
+	pool       vk.DescriptorPool
+	sets       []vk.DescriptorSet
+	size       int
+	texBinds   []int
+	imgBinds   []int
+	bufBinds   []int
+}
+
+type Buffer struct {
+	backend *Backend
+	buf     vk.Buffer
+	store   []byte
+	mem     vk.DeviceMemory
+	usage   vk.BufferUsageFlags
+
+	scope struct {
+		stage  vk.PipelineStageFlags
+		access vk.AccessFlags
+	}
+}
+
+const (
+	texUnits     = 4
+	storageUnits = 4
+)
+
+func init() {
+	driver.NewVulkanDevice = newVulkanDevice
+}
+
+func newVulkanDevice(api driver.Vulkan) (driver.Device, error) {
+	b := &Backend{
+		physDev:   vk.PhysicalDevice(api.PhysDevice),
+		dev:       vk.Device(api.Device),
+		outFormat: vk.Format(api.Format),
+		caps:      driver.FeatureCompute,
+		passes:    make(map[passKey]vk.RenderPass),
+	}
+	b.queue = vk.GetDeviceQueue(b.dev, api.QueueFamily, api.QueueIndex)
+	cmdPool, err := vk.CreateCommandPool(b.dev, api.QueueFamily)
+	if err != nil {
+		return nil, err
+	}
+	b.cmdPool.pool = cmdPool
+	props := vk.GetPhysicalDeviceFormatProperties(b.physDev, vk.FORMAT_R16_SFLOAT)
+	reqs := vk.FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | vk.FORMAT_FEATURE_SAMPLED_IMAGE_BIT
+	if props&reqs == reqs {
+		b.caps |= driver.FeatureFloatRenderTargets
+	}
+	reqs = vk.FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | vk.FORMAT_FEATURE_SAMPLED_IMAGE_BIT | vk.FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT
+	props = vk.GetPhysicalDeviceFormatProperties(b.physDev, vk.FORMAT_R8G8B8A8_SRGB)
+	if props&reqs == reqs {
+		b.caps |= driver.FeatureSRGB
+	}
+	fence, err := vk.CreateFence(b.dev, 0)
+	if err != nil {
+		return nil, mapErr(err)
+	}
+	b.fence = fence
+	return b, nil
+}
+
+func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
+	b.staging.size = 0
+	b.cmdPool.used = 0
+	b.runDefers()
+	b.resetPipes()
+
+	if target == nil {
+		return nil
+	}
+	switch t := target.(type) {
+	case driver.VulkanRenderTarget:
+		layout := vk.IMAGE_LAYOUT_UNDEFINED
+		if !clear {
+			layout = vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
+		}
+		b.frameSig = vk.Semaphore(t.SignalSem)
+		b.frameFence = vk.Fence(t.Fence)
+		tex := &Texture{
+			img:        vk.Image(t.Image),
+			fbo:        vk.Framebuffer(t.Framebuffer),
+			width:      viewport.X,
+			height:     viewport.Y,
+			layout:     layout,
+			passLayout: vk.IMAGE_LAYOUT_PRESENT_SRC_KHR,
+			format:     b.outFormat,
+			acquire:    vk.Semaphore(t.WaitSem),
+			foreign:    true,
+		}
+		return tex
+	case *Texture:
+		return t
+	default:
+		panic(fmt.Sprintf("vulkan: unsupported render target type: %T", t))
+	}
+}
+
+func (b *Backend) deferFunc(f func(d vk.Device)) {
+	b.defers = append(b.defers, f)
+}
+
+func (b *Backend) runDefers() {
+	for _, f := range b.defers {
+		f(b.dev)
+	}
+	b.defers = b.defers[:0]
+}
+
+func (b *Backend) resetPipes() {
+	for i := len(b.allPipes) - 1; i >= 0; i-- {
+		p := b.allPipes[i]
+		if p.pipe == 0 {
+			// Released pipeline.
+			b.allPipes = append(b.allPipes[:i], b.allPipes[:i+1]...)
+			continue
+		}
+		if p.desc.size > 0 {
+			p.desc.size = 0
+		}
+	}
+}
+
+func (b *Backend) EndFrame() {
+	if b.frameSig != 0 {
+		b.sigSems = append(b.sigSems, b.frameSig)
+		b.frameSig = 0
+	}
+	fence := b.frameFence
+	if fence == 0 {
+		// We're internally synchronized.
+		fence = b.fence
+	}
+	b.submitCmdBuf(fence)
+	if b.frameFence == 0 {
+		vk.WaitForFences(b.dev, fence)
+		vk.ResetFences(b.dev, fence)
+	}
+}
+
+func (b *Backend) Caps() driver.Caps {
+	return driver.Caps{
+		MaxTextureSize: 4096,
+		Features:       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() {
+	vk.DeviceWaitIdle(b.dev)
+	if buf := b.staging.buf; buf != nil {
+		vk.UnmapMemory(b.dev, b.staging.buf.mem)
+		buf.Release()
+	}
+	b.runDefers()
+	for _, rp := range b.passes {
+		vk.DestroyRenderPass(b.dev, rp)
+	}
+	vk.DestroyFence(b.dev, b.fence)
+	vk.FreeCommandBuffers(b.dev, b.cmdPool.pool, b.cmdPool.buffers...)
+	vk.DestroyCommandPool(b.dev, b.cmdPool.pool)
+	*b = Backend{}
+}
+
+func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, bindings driver.BufferBinding) (driver.Texture, error) {
+	vkfmt := formatFor(format)
+	usage := vk.IMAGE_USAGE_TRANSFER_DST_BIT | vk.IMAGE_USAGE_TRANSFER_SRC_BIT
+	passLayout := vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
+	if bindings&driver.BufferBindingTexture != 0 {
+		usage |= vk.IMAGE_USAGE_SAMPLED_BIT
+		passLayout = vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
+	}
+	if bindings&driver.BufferBindingFramebuffer != 0 {
+		usage |= vk.IMAGE_USAGE_COLOR_ATTACHMENT_BIT
+	}
+	if bindings&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0 {
+		usage |= vk.IMAGE_USAGE_STORAGE_BIT
+	}
+	filterFor := func(f driver.TextureFilter) vk.Filter {
+		switch f {
+		case driver.FilterLinear, driver.FilterLinearMipmapLinear:
+			return vk.FILTER_LINEAR
+		case driver.FilterNearest:
+			return vk.FILTER_NEAREST
+		}
+		panic("unknown filter")
+	}
+	mipmapMode := vk.SAMPLER_MIPMAP_MODE_NEAREST
+	mipmap := minFilter == driver.FilterLinearMipmapLinear
+	nmipmaps := 1
+	if mipmap {
+		mipmapMode = vk.SAMPLER_MIPMAP_MODE_LINEAR
+		dim := width
+		if height > dim {
+			dim = height
+		}
+		log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
+		nmipmaps = log2 + 1
+	}
+	sampler, err := vk.CreateSampler(b.dev, filterFor(minFilter), filterFor(magFilter), mipmapMode)
+	if err != nil {
+		return nil, mapErr(err)
+	}
+	img, mem, err := vk.CreateImage(b.physDev, b.dev, vkfmt, width, height, nmipmaps, usage)
+	if err != nil {
+		vk.DestroySampler(b.dev, sampler)
+		return nil, mapErr(err)
+	}
+	view, err := vk.CreateImageView(b.dev, img, vkfmt)
+	if err != nil {
+		vk.DestroySampler(b.dev, sampler)
+		vk.DestroyImage(b.dev, img)
+		vk.FreeMemory(b.dev, mem)
+		return nil, mapErr(err)
+	}
+	t := &Texture{backend: b, img: img, mem: mem, view: view, sampler: sampler, layout: vk.IMAGE_LAYOUT_UNDEFINED, passLayout: passLayout, width: width, height: height, format: vkfmt, mipmaps: nmipmaps}
+	if bindings&driver.BufferBindingFramebuffer != 0 {
+		pass, err := vk.CreateRenderPass(b.dev, vkfmt, vk.ATTACHMENT_LOAD_OP_DONT_CARE,
+			vk.IMAGE_LAYOUT_UNDEFINED, vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, nil)
+		if err != nil {
+			return nil, mapErr(err)
+		}
+		defer vk.DestroyRenderPass(b.dev, pass)
+		fbo, err := vk.CreateFramebuffer(b.dev, pass, view, width, height)
+		if err != nil {
+			return nil, mapErr(err)
+		}
+		t.fbo = fbo
+	}
+	return t, nil
+}
+
+func (b *Backend) NewBuffer(bindings driver.BufferBinding, size int) (driver.Buffer, error) {
+	if bindings&driver.BufferBindingUniforms != 0 {
+		// Implement uniform buffers as inline push constants.
+		return &Buffer{store: make([]byte, size)}, nil
+	}
+	usage := vk.BUFFER_USAGE_TRANSFER_DST_BIT | vk.BUFFER_USAGE_TRANSFER_SRC_BIT
+	if bindings&driver.BufferBindingIndices != 0 {
+		usage |= vk.BUFFER_USAGE_INDEX_BUFFER_BIT
+	}
+	if bindings&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0 {
+		usage |= vk.BUFFER_USAGE_STORAGE_BUFFER_BIT
+	}
+	if bindings&driver.BufferBindingVertices != 0 {
+		usage |= vk.BUFFER_USAGE_VERTEX_BUFFER_BIT
+	}
+	buf, err := b.newBuffer(size, usage, vk.MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
+	return buf, mapErr(err)
+}
+
+func (b *Backend) newBuffer(size int, usage vk.BufferUsageFlags, props vk.MemoryPropertyFlags) (*Buffer, error) {
+	buf, mem, err := vk.CreateBuffer(b.physDev, b.dev, size, usage, props)
+	return &Buffer{backend: b, buf: buf, mem: mem, usage: usage}, err
+}
+
+func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) {
+	buf, err := b.NewBuffer(typ, len(data))
+	if err != nil {
+		return nil, err
+	}
+	buf.Upload(data)
+	return buf, nil
+}
+
+func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) {
+	sh, err := b.newShader(src, vk.SHADER_STAGE_VERTEX_BIT)
+	return sh, mapErr(err)
+}
+
+func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) {
+	sh, err := b.newShader(src, vk.SHADER_STAGE_FRAGMENT_BIT)
+	return sh, mapErr(err)
+}
+
+func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) {
+	vs := desc.VertexShader.(*Shader)
+	fs := desc.FragmentShader.(*Shader)
+	var ranges []vk.PushConstantRange
+	if r := vs.pushRange; r != (vk.PushConstantRange{}) {
+		ranges = append(ranges, r)
+	}
+	if r := fs.pushRange; r != (vk.PushConstantRange{}) {
+		ranges = append(ranges, r)
+	}
+	descPool, err := createPipelineLayout(b.dev, fs.src, ranges)
+	if err != nil {
+		return nil, mapErr(err)
+	}
+	blend := desc.BlendDesc
+	factorFor := func(f driver.BlendFactor) vk.BlendFactor {
+		switch f {
+		case driver.BlendFactorZero:
+			return vk.BLEND_FACTOR_ZERO
+		case driver.BlendFactorOne:
+			return vk.BLEND_FACTOR_ONE
+		case driver.BlendFactorOneMinusSrcAlpha:
+			return vk.BLEND_FACTOR_ONE_MINUS_SRC_ALPHA
+		case driver.BlendFactorDstColor:
+			return vk.BLEND_FACTOR_DST_COLOR
+		default:
+			panic("unknown blend factor")
+		}
+	}
+	var top vk.PrimitiveTopology
+	switch desc.Topology {
+	case driver.TopologyTriangles:
+		top = vk.PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
+	case driver.TopologyTriangleStrip:
+		top = vk.PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
+	default:
+		panic("unknown topology")
+	}
+	var binds []vk.VertexInputBindingDescription
+	var attrs []vk.VertexInputAttributeDescription
+	inputs := desc.VertexLayout.Inputs
+	for i, inp := range inputs {
+		binds = append(binds, vk.VertexInputBindingDescription{
+			Binding: i,
+			Stride:  desc.VertexLayout.Stride,
+		})
+		attrs = append(attrs, vk.VertexInputAttributeDescription{
+			Binding:  i,
+			Location: vs.src.Inputs[i].Location,
+			Format:   vertFormatFor(vs.src.Inputs[i]),
+			Offset:   inp.Offset,
+		})
+	}
+	fmt := b.outFormat
+	if f := desc.PixelFormat; f != driver.TextureFormatOutput {
+		fmt = formatFor(f)
+	}
+	pass, err := vk.CreateRenderPass(b.dev, fmt, vk.ATTACHMENT_LOAD_OP_DONT_CARE,
+		vk.IMAGE_LAYOUT_UNDEFINED, vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, nil)
+	if err != nil {
+		return nil, mapErr(err)
+	}
+	defer vk.DestroyRenderPass(b.dev, pass)
+	pipe, err := vk.CreateGraphicsPipeline(b.dev, pass, vs.module, fs.module, blend.Enable, factorFor(blend.SrcFactor), factorFor(blend.DstFactor), top, binds, attrs, descPool.layout)
+	if err != nil {
+		descPool.release(b.dev)
+		return nil, mapErr(err)
+	}
+	p := &Pipeline{backend: b, pipe: pipe, desc: descPool, pushRanges: ranges, ninputs: len(inputs)}
+	b.allPipes = append(b.allPipes, p)
+	return p, nil
+}
+
+func (b *Backend) NewComputeProgram(src shader.Sources) (driver.Program, error) {
+	sh, err := b.newShader(src, vk.SHADER_STAGE_COMPUTE_BIT)
+	if err != nil {
+		return nil, mapErr(err)
+	}
+	defer sh.Release()
+	descPool, err := createPipelineLayout(b.dev, src, nil)
+	if err != nil {
+		return nil, mapErr(err)
+	}
+	pipe, err := vk.CreateComputePipeline(b.dev, sh.module, descPool.layout)
+	if err != nil {
+		descPool.release(b.dev)
+		return nil, mapErr(err)
+	}
+	return &Pipeline{backend: b, pipe: pipe, desc: descPool}, nil
+}
+
+func vertFormatFor(f shader.InputLocation) vk.Format {
+	t := f.Type
+	s := f.Size
+	switch {
+	case t == shader.DataTypeFloat && s == 1:
+		return vk.FORMAT_R32_SFLOAT
+	case t == shader.DataTypeFloat && s == 2:
+		return vk.FORMAT_R32G32_SFLOAT
+	case t == shader.DataTypeFloat && s == 3:
+		return vk.FORMAT_R32G32B32_SFLOAT
+	case t == shader.DataTypeFloat && s == 4:
+		return vk.FORMAT_R32G32B32A32_SFLOAT
+	default:
+		panic("unsupported data type")
+	}
+}
+
+func createPipelineLayout(d vk.Device, src shader.Sources, ranges []vk.PushConstantRange) (*descPool, error) {
+	var (
+		descLayouts []vk.DescriptorSetLayout
+		descLayout  vk.DescriptorSetLayout
+	)
+	texBinds := make([]int, len(src.Textures))
+	imgBinds := make([]int, len(src.Images))
+	bufBinds := make([]int, len(src.StorageBuffers))
+	var descBinds []vk.DescriptorSetLayoutBinding
+	for i, t := range src.Textures {
+		descBinds = append(descBinds, vk.DescriptorSetLayoutBinding{
+			Binding:        t.Binding,
+			StageFlags:     vk.SHADER_STAGE_FRAGMENT_BIT,
+			DescriptorType: vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+		})
+		texBinds[i] = t.Binding
+	}
+	for i, img := range src.Images {
+		descBinds = append(descBinds, vk.DescriptorSetLayoutBinding{
+			Binding:        img.Binding,
+			StageFlags:     vk.SHADER_STAGE_COMPUTE_BIT,
+			DescriptorType: vk.DESCRIPTOR_TYPE_STORAGE_IMAGE,
+		})
+		imgBinds[i] = img.Binding
+	}
+	for i, buf := range src.StorageBuffers {
+		descBinds = append(descBinds, vk.DescriptorSetLayoutBinding{
+			Binding:        buf.Binding,
+			StageFlags:     vk.SHADER_STAGE_COMPUTE_BIT,
+			DescriptorType: vk.DESCRIPTOR_TYPE_STORAGE_BUFFER,
+		})
+		bufBinds[i] = buf.Binding
+	}
+	if len(descBinds) > 0 {
+		var err error
+		descLayout, err = vk.CreateDescriptorSetLayout(d, descBinds)
+		if err != nil {
+			return nil, err
+		}
+		descLayouts = append(descLayouts, descLayout)
+	}
+	layout, err := vk.CreatePipelineLayout(d, ranges, descLayouts)
+	if err != nil {
+		if descLayout != 0 {
+			vk.DestroyDescriptorSetLayout(d, descLayout)
+		}
+		return nil, err
+	}
+	descPool := &descPool{
+		texBinds:   texBinds,
+		bufBinds:   bufBinds,
+		imgBinds:   imgBinds,
+		layout:     layout,
+		descLayout: descLayout,
+	}
+	return descPool, nil
+}
+
+func (b *Backend) newShader(src shader.Sources, stage vk.ShaderStageFlags) (*Shader, error) {
+	mod, err := vk.CreateShaderModule(b.dev, src.SPIRV)
+	if err != nil {
+		return nil, err
+	}
+
+	sh := &Shader{dev: b.dev, module: mod, src: src}
+	if locs := src.Uniforms.Locations; len(locs) > 0 {
+		pushOffset := 0x7fffffff
+		for _, l := range locs {
+			if l.Offset < pushOffset {
+				pushOffset = l.Offset
+			}
+		}
+		sh.pushRange = vk.BuildPushConstantRange(stage, pushOffset, src.Uniforms.Size)
+	}
+	return sh, nil
+}
+
+func (b *Backend) CopyTexture(dstTex driver.Texture, dorig image.Point, srcFBO driver.Texture, srect image.Rectangle) {
+	dst := dstTex.(*Texture)
+	src := srcFBO.(*Texture)
+	cmdBuf := b.ensureCmdBuf()
+	op := vk.BuildImageCopy(srect.Min.X, srect.Min.Y, dorig.X, dorig.Y, srect.Dx(), srect.Dy())
+	src.imageBarrier(cmdBuf,
+		vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+		vk.PIPELINE_STAGE_TRANSFER_BIT,
+		vk.ACCESS_TRANSFER_READ_BIT,
+	)
+	dst.imageBarrier(cmdBuf,
+		vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+		vk.PIPELINE_STAGE_TRANSFER_BIT,
+		vk.ACCESS_TRANSFER_WRITE_BIT,
+	)
+	vk.CmdCopyImage(cmdBuf, src.img, src.layout, dst.img, dst.layout, []vk.ImageCopy{op})
+}
+
+func (b *Backend) Viewport(x, y, width, height int) {
+	cmdBuf := b.currentCmdBuf()
+	vp := vk.BuildViewport(float32(x), float32(y), float32(width), float32(height))
+	vk.CmdSetViewport(cmdBuf, 0, vp)
+}
+
+func (b *Backend) DrawArrays(off, count int) {
+	cmdBuf := b.currentCmdBuf()
+	if b.desc.dirty {
+		b.pipe.desc.bindDescriptorSet(b, cmdBuf, vk.PIPELINE_BIND_POINT_GRAPHICS, b.desc.texBinds, b.desc.bufBinds)
+		b.desc.dirty = false
+	}
+	vk.CmdDraw(cmdBuf, count, 1, off, 0)
+}
+
+func (b *Backend) DrawElements(off, count int) {
+	cmdBuf := b.currentCmdBuf()
+	if b.desc.dirty {
+		b.pipe.desc.bindDescriptorSet(b, cmdBuf, vk.PIPELINE_BIND_POINT_GRAPHICS, b.desc.texBinds, b.desc.bufBinds)
+		b.desc.dirty = false
+	}
+	vk.CmdDrawIndexed(cmdBuf, count, 1, off, 0, 0)
+}
+
+func (b *Backend) BindImageTexture(unit int, tex driver.Texture) {
+	t := tex.(*Texture)
+	b.desc.texBinds[unit] = t
+	b.desc.dirty = true
+	t.imageBarrier(b.currentCmdBuf(),
+		vk.IMAGE_LAYOUT_GENERAL,
+		vk.PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+		vk.ACCESS_SHADER_READ_BIT|vk.ACCESS_SHADER_WRITE_BIT,
+	)
+}
+
+func (b *Backend) DispatchCompute(x, y, z int) {
+	cmdBuf := b.currentCmdBuf()
+	if b.desc.dirty {
+		b.pipe.desc.bindDescriptorSet(b, cmdBuf, vk.PIPELINE_BIND_POINT_COMPUTE, b.desc.texBinds, b.desc.bufBinds)
+		b.desc.dirty = false
+	}
+	vk.CmdDispatch(cmdBuf, x, y, z)
+}
+
+func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) {
+	if stride == 0 {
+		stride = size.X * 4
+	}
+	cmdBuf := t.backend.ensureCmdBuf()
+	dstStride := size.X * 4
+	n := size.Y * dstStride
+	stage, mem, off := t.backend.stagingBuffer(n)
+	var srcOff, dstOff int
+	for y := 0; y < size.Y; y++ {
+		srcRow := pixels[srcOff : srcOff+dstStride]
+		dstRow := mem[dstOff : dstOff+dstStride]
+		copy(dstRow, srcRow)
+		dstOff += dstStride
+		srcOff += stride
+	}
+	op := vk.BuildBufferImageCopy(off, dstStride/4, offset.X, offset.Y, size.X, size.Y)
+	t.imageBarrier(cmdBuf,
+		vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+		vk.PIPELINE_STAGE_TRANSFER_BIT,
+		vk.ACCESS_TRANSFER_WRITE_BIT,
+	)
+	vk.CmdCopyBufferToImage(cmdBuf, stage.buf, t.img, t.layout, op)
+	// Build mipmaps by repeating linear blits.
+	w, h := t.width, t.height
+	for i := 1; i < t.mipmaps; i++ {
+		nw, nh := w/2, h/2
+		if nh < 1 {
+			nh = 1
+		}
+		if nw < 1 {
+			nw = 1
+		}
+		// Transition previous (source) level.
+		b := vk.BuildImageMemoryBarrier(
+			t.img,
+			vk.ACCESS_TRANSFER_WRITE_BIT, vk.ACCESS_TRANSFER_READ_BIT,
+			vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+			i-1, 1,
+		)
+		vk.CmdPipelineBarrier(cmdBuf, vk.PIPELINE_STAGE_TRANSFER_BIT, vk.PIPELINE_STAGE_TRANSFER_BIT, vk.DEPENDENCY_BY_REGION_BIT, nil, nil, []vk.ImageMemoryBarrier{b})
+		// Blit to this mipmap level.
+		blit := vk.BuildImageBlit(0, 0, 0, 0, w, h, nw, nh, i-1, i)
+		vk.CmdBlitImage(cmdBuf, t.img, vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, t.img, vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, []vk.ImageBlit{blit}, vk.FILTER_LINEAR)
+		w, h = nw, nh
+	}
+	if t.mipmaps > 1 {
+		// Add barrier for last blit.
+		b := vk.BuildImageMemoryBarrier(
+			t.img,
+			vk.ACCESS_TRANSFER_WRITE_BIT, vk.ACCESS_TRANSFER_READ_BIT,
+			vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+			t.mipmaps-1, 1,
+		)
+		vk.CmdPipelineBarrier(cmdBuf, vk.PIPELINE_STAGE_TRANSFER_BIT, vk.PIPELINE_STAGE_TRANSFER_BIT, vk.DEPENDENCY_BY_REGION_BIT, nil, nil, []vk.ImageMemoryBarrier{b})
+		t.layout = vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
+	}
+}
+
+func (t *Texture) Release() {
+	if t.foreign {
+		panic("external textures cannot be released")
+	}
+	freet := *t
+	t.backend.deferFunc(func(d vk.Device) {
+		if freet.fbo != 0 {
+			vk.DestroyFramebuffer(d, freet.fbo)
+		}
+		vk.DestroySampler(d, freet.sampler)
+		vk.DestroyImageView(d, freet.view)
+		vk.DestroyImage(d, freet.img)
+		vk.FreeMemory(d, freet.mem)
+	})
+	*t = Texture{}
+}
+
+func (p *Pipeline) Release() {
+	freep := *p
+	p.backend.deferFunc(func(d vk.Device) {
+		freep.desc.release(d)
+		vk.DestroyPipeline(d, freep.pipe)
+	})
+	*p = Pipeline{}
+}
+
+func (p *descPool) release(d vk.Device) {
+	if p := p.pool; p != 0 {
+		vk.DestroyDescriptorPool(d, p)
+	}
+	if l := p.descLayout; l != 0 {
+		vk.DestroyDescriptorSetLayout(d, l)
+	}
+	vk.DestroyPipelineLayout(d, p.layout)
+}
+
+func (p *descPool) bindDescriptorSet(b *Backend, cmdBuf vk.CommandBuffer, bindPoint vk.PipelineBindPoint, texBinds [texUnits]*Texture, bufBinds [storageUnits]*Buffer) {
+	if p.size == len(p.sets) {
+		l := p.descLayout
+		if l == 0 {
+			panic("vulkan: descriptor set is dirty, but pipeline has empty layout")
+		}
+		newCap := len(p.sets) * 2
+		if pool := p.pool; pool != 0 {
+			b.deferFunc(func(d vk.Device) {
+				vk.DestroyDescriptorPool(d, pool)
+			})
+		}
+		const initialPoolSize = 100
+		if newCap < initialPoolSize {
+			newCap = initialPoolSize
+		}
+		var poolSizes []vk.DescriptorPoolSize
+		if n := len(p.texBinds); n > 0 {
+			poolSizes = append(poolSizes, vk.BuildDescriptorPoolSize(vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, newCap*n))
+		}
+		if n := len(p.imgBinds); n > 0 {
+			poolSizes = append(poolSizes, vk.BuildDescriptorPoolSize(vk.DESCRIPTOR_TYPE_STORAGE_IMAGE, newCap*n))
+		}
+		if n := len(p.bufBinds); n > 0 {
+			poolSizes = append(poolSizes, vk.BuildDescriptorPoolSize(vk.DESCRIPTOR_TYPE_STORAGE_BUFFER, newCap*n))
+		}
+		pool, err := vk.CreateDescriptorPool(b.dev, newCap, poolSizes)
+		if err != nil {
+			panic(fmt.Errorf("vulkan: failed to allocate descriptor pool with %d descriptors: %v", newCap, err))
+		}
+		p.pool = pool
+		sets, err := vk.AllocateDescriptorSets(b.dev, p.pool, l, newCap)
+		if err != nil {
+			panic(fmt.Errorf("vulkan: failed to allocate descriptor with %d sets: %v", newCap, err))
+		}
+		p.sets = sets
+		p.size = 0
+	}
+	descSet := p.sets[p.size]
+	p.size++
+	for _, bind := range p.texBinds {
+		tex := texBinds[bind]
+		write := vk.BuildWriteDescriptorSetImage(descSet, bind, vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, tex.sampler, tex.view, vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+		vk.UpdateDescriptorSet(b.dev, write)
+	}
+	for _, bind := range p.imgBinds {
+		tex := texBinds[bind]
+		write := vk.BuildWriteDescriptorSetImage(descSet, bind, vk.DESCRIPTOR_TYPE_STORAGE_IMAGE, 0, tex.view, vk.IMAGE_LAYOUT_GENERAL)
+		vk.UpdateDescriptorSet(b.dev, write)
+	}
+	for _, bind := range p.bufBinds {
+		buf := bufBinds[bind]
+		write := vk.BuildWriteDescriptorSetBuffer(descSet, bind, vk.DESCRIPTOR_TYPE_STORAGE_BUFFER, buf.buf)
+		vk.UpdateDescriptorSet(b.dev, write)
+	}
+	vk.CmdBindDescriptorSets(cmdBuf, bindPoint, p.layout, 0, []vk.DescriptorSet{descSet})
+}
+
+func (t *Texture) imageBarrier(cmdBuf vk.CommandBuffer, layout vk.ImageLayout, stage vk.PipelineStageFlags, access vk.AccessFlags) {
+	srcStage := t.scope.stage
+	if srcStage == 0 && t.layout == layout {
+		t.scope.stage = stage
+		t.scope.access = access
+		return
+	}
+	if srcStage == 0 {
+		srcStage = vk.PIPELINE_STAGE_TOP_OF_PIPE_BIT
+	}
+	b := vk.BuildImageMemoryBarrier(
+		t.img,
+		t.scope.access, access,
+		t.layout, layout,
+		0, vk.REMAINING_MIP_LEVELS,
+	)
+	vk.CmdPipelineBarrier(cmdBuf, srcStage, stage, vk.DEPENDENCY_BY_REGION_BIT, nil, nil, []vk.ImageMemoryBarrier{b})
+	t.layout = layout
+	t.scope.stage = stage
+	t.scope.access = access
+}
+
+func (b *Backend) PrepareTexture(tex driver.Texture) {
+	t := tex.(*Texture)
+	cmdBuf := b.ensureCmdBuf()
+	t.imageBarrier(cmdBuf,
+		vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+		vk.PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+		vk.ACCESS_SHADER_READ_BIT,
+	)
+}
+
+func (b *Backend) BindTexture(unit int, tex driver.Texture) {
+	t := tex.(*Texture)
+	b.desc.texBinds[unit] = t
+	b.desc.dirty = true
+}
+
+func (b *Backend) BindPipeline(pipe driver.Pipeline) {
+	b.bindPipeline(pipe.(*Pipeline), vk.PIPELINE_BIND_POINT_GRAPHICS)
+}
+
+func (b *Backend) BindProgram(prog driver.Program) {
+	b.bindPipeline(prog.(*Pipeline), vk.PIPELINE_BIND_POINT_COMPUTE)
+}
+
+func (b *Backend) bindPipeline(p *Pipeline, point vk.PipelineBindPoint) {
+	b.pipe = p
+	b.desc.dirty = p.desc.descLayout != 0
+	cmdBuf := b.currentCmdBuf()
+	vk.CmdBindPipeline(cmdBuf, point, p.pipe)
+}
+
+func (s *Shader) Release() {
+	vk.DestroyShaderModule(s.dev, s.module)
+	*s = Shader{}
+}
+
+func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) {
+	buf := buffer.(*Buffer)
+	b.desc.bufBinds[binding] = buf
+	b.desc.dirty = true
+	buf.barrier(b.currentCmdBuf(),
+		vk.PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+		vk.ACCESS_SHADER_READ_BIT|vk.ACCESS_SHADER_WRITE_BIT,
+	)
+}
+
+func (b *Backend) BindUniforms(buffer driver.Buffer) {
+	buf := buffer.(*Buffer)
+	cmdBuf := b.currentCmdBuf()
+	for _, s := range b.pipe.pushRanges {
+		off := vk.PushConstantRangeOffset(s)
+		vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, vk.PushConstantRangeStageFlags(s), off, buf.store[off:off+vk.PushConstantRangeSize(s)])
+	}
+}
+
+func (b *Backend) BindVertexBuffer(buffer driver.Buffer, offset int) {
+	buf := buffer.(*Buffer)
+	cmdBuf := b.currentCmdBuf()
+	b.bindings = b.bindings[:0]
+	b.offsets = b.offsets[:0]
+	for i := 0; i < b.pipe.ninputs; i++ {
+		b.bindings = append(b.bindings, buf.buf)
+		b.offsets = append(b.offsets, vk.DeviceSize(offset))
+	}
+	vk.CmdBindVertexBuffers(cmdBuf, 0, b.bindings, b.offsets)
+}
+
+func (b *Backend) BindIndexBuffer(buffer driver.Buffer) {
+	buf := buffer.(*Buffer)
+	cmdBuf := b.currentCmdBuf()
+	vk.CmdBindIndexBuffer(cmdBuf, buf.buf, 0, vk.INDEX_TYPE_UINT16)
+}
+
+func (b *Buffer) Download(data []byte) error {
+	if b.buf == 0 {
+		copy(data, b.store)
+		return nil
+	}
+	stage, mem, off := b.backend.stagingBuffer(len(data))
+	cmdBuf := b.backend.ensureCmdBuf()
+	b.barrier(cmdBuf,
+		vk.PIPELINE_STAGE_TRANSFER_BIT,
+		vk.ACCESS_TRANSFER_READ_BIT,
+	)
+	vk.CmdCopyBuffer(cmdBuf, b.buf, stage.buf, 0, off, len(data))
+	stage.scope.stage = vk.PIPELINE_STAGE_TRANSFER_BIT
+	stage.scope.access = vk.ACCESS_TRANSFER_WRITE_BIT
+	stage.barrier(cmdBuf,
+		vk.PIPELINE_STAGE_HOST_BIT,
+		vk.ACCESS_HOST_READ_BIT,
+	)
+	b.backend.submitCmdBuf(b.backend.fence)
+	vk.WaitForFences(b.backend.dev, b.backend.fence)
+	vk.ResetFences(b.backend.dev, b.backend.fence)
+	copy(data, mem)
+	return nil
+}
+
+func (b *Buffer) Upload(data []byte) {
+	if b.buf == 0 {
+		copy(b.store, data)
+		return
+	}
+	stage, mem, off := b.backend.stagingBuffer(len(data))
+	copy(mem, data)
+	cmdBuf := b.backend.ensureCmdBuf()
+	b.barrier(cmdBuf,
+		vk.PIPELINE_STAGE_TRANSFER_BIT,
+		vk.ACCESS_TRANSFER_WRITE_BIT,
+	)
+	vk.CmdCopyBuffer(cmdBuf, stage.buf, b.buf, off, 0, len(data))
+	var access vk.AccessFlags
+	if b.usage&vk.BUFFER_USAGE_INDEX_BUFFER_BIT != 0 {
+		access |= vk.ACCESS_INDEX_READ_BIT
+	}
+	if b.usage&vk.BUFFER_USAGE_VERTEX_BUFFER_BIT != 0 {
+		access |= vk.ACCESS_VERTEX_ATTRIBUTE_READ_BIT
+	}
+	if access != 0 {
+		b.barrier(cmdBuf,
+			vk.PIPELINE_STAGE_VERTEX_INPUT_BIT,
+			access,
+		)
+	}
+}
+
+func (b *Buffer) barrier(cmdBuf vk.CommandBuffer, stage vk.PipelineStageFlags, access vk.AccessFlags) {
+	srcStage := b.scope.stage
+	if srcStage == 0 {
+		b.scope.stage = stage
+		b.scope.access = access
+		return
+	}
+	barrier := vk.BuildBufferMemoryBarrier(
+		b.buf,
+		b.scope.access, access,
+	)
+	vk.CmdPipelineBarrier(cmdBuf, srcStage, stage, vk.DEPENDENCY_BY_REGION_BIT, nil, []vk.BufferMemoryBarrier{barrier}, nil)
+	b.scope.stage = stage
+	b.scope.access = access
+}
+
+func (b *Buffer) Release() {
+	freeb := *b
+	if freeb.buf != 0 {
+		b.backend.deferFunc(func(d vk.Device) {
+			vk.DestroyBuffer(d, freeb.buf)
+			vk.FreeMemory(d, freeb.mem)
+		})
+	}
+	*b = Buffer{}
+}
+
+func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
+	if len(pixels) == 0 {
+		return nil
+	}
+	sz := src.Size()
+	stageStride := sz.X * 4
+	n := sz.Y * stageStride
+	stage, mem, off := t.backend.stagingBuffer(n)
+	cmdBuf := t.backend.ensureCmdBuf()
+	region := vk.BuildBufferImageCopy(off, stageStride/4, src.Min.X, src.Min.Y, sz.X, sz.Y)
+	t.imageBarrier(cmdBuf,
+		vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+		vk.PIPELINE_STAGE_TRANSFER_BIT,
+		vk.ACCESS_TRANSFER_READ_BIT,
+	)
+	vk.CmdCopyImageToBuffer(cmdBuf, t.img, t.layout, stage.buf, []vk.BufferImageCopy{region})
+	stage.scope.stage = vk.PIPELINE_STAGE_TRANSFER_BIT
+	stage.scope.access = vk.ACCESS_TRANSFER_WRITE_BIT
+	stage.barrier(cmdBuf,
+		vk.PIPELINE_STAGE_HOST_BIT,
+		vk.ACCESS_HOST_READ_BIT,
+	)
+	t.backend.submitCmdBuf(t.backend.fence)
+	vk.WaitForFences(t.backend.dev, t.backend.fence)
+	vk.ResetFences(t.backend.dev, t.backend.fence)
+	var srcOff, dstOff int
+	for y := 0; y < sz.Y; y++ {
+		dstRow := pixels[srcOff : srcOff+stageStride]
+		srcRow := mem[dstOff : dstOff+stageStride]
+		copy(dstRow, srcRow)
+		dstOff += stageStride
+		srcOff += stride
+	}
+	return nil
+}
+
+func (b *Backend) currentCmdBuf() vk.CommandBuffer {
+	cur := b.cmdPool.current
+	if cur == nil {
+		panic("vulkan: invalid operation outside a render or compute pass")
+	}
+	return cur
+}
+
+func (b *Backend) ensureCmdBuf() vk.CommandBuffer {
+	if b.cmdPool.current != nil {
+		return b.cmdPool.current
+	}
+	if b.cmdPool.used < len(b.cmdPool.buffers) {
+		buf := b.cmdPool.buffers[b.cmdPool.used]
+		b.cmdPool.current = buf
+	} else {
+		buf, err := vk.AllocateCommandBuffer(b.dev, b.cmdPool.pool)
+		if err != nil {
+			panic(err)
+		}
+		b.cmdPool.buffers = append(b.cmdPool.buffers, buf)
+		b.cmdPool.current = buf
+	}
+	b.cmdPool.used++
+	buf := b.cmdPool.current
+	if err := vk.BeginCommandBuffer(buf); err != nil {
+		panic(err)
+	}
+	return buf
+}
+
+func (b *Backend) BeginRenderPass(tex driver.Texture, d driver.LoadDesc) {
+	t := tex.(*Texture)
+	var vkop vk.AttachmentLoadOp
+	switch d.Action {
+	case driver.LoadActionClear:
+		vkop = vk.ATTACHMENT_LOAD_OP_CLEAR
+	case driver.LoadActionInvalidate:
+		vkop = vk.ATTACHMENT_LOAD_OP_DONT_CARE
+	case driver.LoadActionKeep:
+		vkop = vk.ATTACHMENT_LOAD_OP_LOAD
+	}
+	cmdBuf := b.ensureCmdBuf()
+	if sem := t.acquire; sem != 0 {
+		// The render pass targets a framebuffer that has an associated acquire semaphore.
+		// Wait for it by forming an execution barrier.
+		b.waitSems = append(b.waitSems, sem)
+		b.waitStages = append(b.waitStages, vk.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)
+		// But only for the first pass in a frame.
+		t.acquire = 0
+	}
+	t.imageBarrier(cmdBuf,
+		vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+		vk.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+		vk.ACCESS_COLOR_ATTACHMENT_READ_BIT|vk.ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+	)
+	pass := b.lookupPass(t.format, vkop, t.layout, t.passLayout)
+	col := d.ClearColor
+	vk.CmdBeginRenderPass(cmdBuf, pass, t.fbo, t.width, t.height, [4]float32{col.R, col.G, col.B, col.A})
+	t.layout = t.passLayout
+	// If the render pass describes an automatic image layout transition to its final layout, there
+	// is an implicit image barrier with destination PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT. Make
+	// sure any subsequent barrier includes the transition.
+	// See also https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#VkSubpassDependency.
+	t.scope.stage |= vk.PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT
+}
+
+func (b *Backend) EndRenderPass() {
+	vk.CmdEndRenderPass(b.cmdPool.current)
+}
+
+func (b *Backend) BeginCompute() {
+	b.ensureCmdBuf()
+}
+
+func (b *Backend) EndCompute() {
+}
+
+func (b *Backend) lookupPass(fmt vk.Format, loadAct vk.AttachmentLoadOp, initLayout, finalLayout vk.ImageLayout) vk.RenderPass {
+	key := passKey{fmt: fmt, loadAct: loadAct, initLayout: initLayout, finalLayout: finalLayout}
+	if pass, ok := b.passes[key]; ok {
+		return pass
+	}
+	pass, err := vk.CreateRenderPass(b.dev, fmt, loadAct, initLayout, finalLayout, nil)
+	if err != nil {
+		panic(err)
+	}
+	b.passes[key] = pass
+	return pass
+}
+
+func (b *Backend) submitCmdBuf(fence vk.Fence) {
+	buf := b.cmdPool.current
+	if buf == nil && fence == 0 {
+		return
+	}
+	buf = b.ensureCmdBuf()
+	b.cmdPool.current = nil
+	if err := vk.EndCommandBuffer(buf); err != nil {
+		panic(err)
+	}
+	if err := vk.QueueSubmit(b.queue, buf, b.waitSems, b.waitStages, b.sigSems, fence); err != nil {
+		panic(err)
+	}
+	b.waitSems = b.waitSems[:0]
+	b.sigSems = b.sigSems[:0]
+	b.waitStages = b.waitStages[:0]
+}
+
+func (b *Backend) stagingBuffer(size int) (*Buffer, []byte, int) {
+	if b.staging.size+size > b.staging.cap {
+		if b.staging.buf != nil {
+			vk.UnmapMemory(b.dev, b.staging.buf.mem)
+			b.staging.buf.Release()
+			b.staging.cap = 0
+		}
+		cap := 2 * (b.staging.size + size)
+		buf, err := b.newBuffer(cap, vk.BUFFER_USAGE_TRANSFER_SRC_BIT|vk.BUFFER_USAGE_TRANSFER_DST_BIT,
+			vk.MEMORY_PROPERTY_HOST_VISIBLE_BIT|vk.MEMORY_PROPERTY_HOST_COHERENT_BIT)
+		if err != nil {
+			panic(err)
+		}
+		mem, err := vk.MapMemory(b.dev, buf.mem, 0, cap)
+		if err != nil {
+			buf.Release()
+			panic(err)
+		}
+		b.staging.buf = buf
+		b.staging.mem = mem
+		b.staging.size = 0
+		b.staging.cap = cap
+	}
+	off := b.staging.size
+	b.staging.size += size
+	mem := b.staging.mem[off : off+size]
+	return b.staging.buf, mem, off
+}
+
+func formatFor(format driver.TextureFormat) vk.Format {
+	switch format {
+	case driver.TextureFormatRGBA8:
+		return vk.FORMAT_R8G8B8A8_UNORM
+	case driver.TextureFormatSRGBA:
+		return vk.FORMAT_R8G8B8A8_SRGB
+	case driver.TextureFormatFloat:
+		return vk.FORMAT_R16_SFLOAT
+	default:
+		panic("unsupported texture format")
+	}
+}
+
+func mapErr(err error) error {
+	var vkErr vk.Error
+	if errors.As(err, &vkErr) && vkErr == vk.ERROR_DEVICE_LOST {
+		return driver.ErrDeviceLost
+	}
+	return err
+}
+
+func (f *Texture) ImplementsRenderTarget() {}

+ 5 - 0
vendor/gioui.org/gpu/internal/vulkan/vulkan_nosupport.go

@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package vulkan
+
+// Empty file to avoid the build error for platforms without Vulkan support.

+ 109 - 0
vendor/gioui.org/gpu/pack.go

@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package gpu
+
+import (
+	"image"
+)
+
+// packer packs a set of many smaller rectangles into
+// much fewer larger atlases.
+type packer struct {
+	maxDims image.Point
+	spaces  []image.Rectangle
+
+	sizes []image.Point
+	pos   image.Point
+}
+
+type placement struct {
+	Idx int
+	Pos image.Point
+}
+
+// add adds the given rectangle to the atlases and
+// return the allocated position.
+func (p *packer) add(s image.Point) (placement, bool) {
+	if place, ok := p.tryAdd(s); ok {
+		return place, true
+	}
+	p.newPage()
+	return p.tryAdd(s)
+}
+
+func (p *packer) clear() {
+	p.sizes = p.sizes[:0]
+	p.spaces = p.spaces[:0]
+}
+
+func (p *packer) newPage() {
+	p.pos = image.Point{}
+	p.sizes = append(p.sizes, image.Point{})
+	p.spaces = p.spaces[:0]
+	p.spaces = append(p.spaces, image.Rectangle{
+		Max: image.Point{X: 1e6, Y: 1e6},
+	})
+}
+
+func (p *packer) tryAdd(s image.Point) (placement, bool) {
+	if len(p.spaces) == 0 || len(p.sizes) == 0 {
+		return placement{}, false
+	}
+
+	var (
+		bestIdx  *image.Rectangle
+		bestSize = p.maxDims
+		lastSize = p.sizes[len(p.sizes)-1]
+	)
+	// Go backwards to prioritize smaller spaces.
+	for i := range p.spaces {
+		space := &p.spaces[i]
+		rightSpace := space.Dx() - s.X
+		bottomSpace := space.Dy() - s.Y
+		if rightSpace < 0 || bottomSpace < 0 {
+			continue
+		}
+		size := lastSize
+		if x := space.Min.X + s.X; x > size.X {
+			if x > p.maxDims.X {
+				continue
+			}
+			size.X = x
+		}
+		if y := space.Min.Y + s.Y; y > size.Y {
+			if y > p.maxDims.Y {
+				continue
+			}
+			size.Y = y
+		}
+		if size.X*size.Y < bestSize.X*bestSize.Y {
+			bestIdx = space
+			bestSize = size
+		}
+	}
+	if bestIdx == nil {
+		return placement{}, false
+	}
+	// Remove space.
+	bestSpace := *bestIdx
+	*bestIdx = p.spaces[len(p.spaces)-1]
+	p.spaces = p.spaces[:len(p.spaces)-1]
+	// Put s in the top left corner and add the (at most)
+	// two smaller spaces.
+	pos := bestSpace.Min
+	if rem := bestSpace.Dy() - s.Y; rem > 0 {
+		p.spaces = append(p.spaces, image.Rectangle{
+			Min: image.Point{X: pos.X, Y: pos.Y + s.Y},
+			Max: image.Point{X: bestSpace.Max.X, Y: bestSpace.Max.Y},
+		})
+	}
+	if rem := bestSpace.Dx() - s.X; rem > 0 {
+		p.spaces = append(p.spaces, image.Rectangle{
+			Min: image.Point{X: pos.X + s.X, Y: pos.Y},
+			Max: image.Point{X: bestSpace.Max.X, Y: pos.Y + s.Y},
+		})
+	}
+	idx := len(p.sizes) - 1
+	p.sizes[idx] = bestSize
+	return placement{Idx: idx, Pos: pos}, true
+}

+ 424 - 0
vendor/gioui.org/gpu/path.go

@@ -0,0 +1,424 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package gpu
+
+// GPU accelerated path drawing using the algorithms from
+// Pathfinder (https://github.com/servo/pathfinder).
+
+import (
+	"encoding/binary"
+	"image"
+	"math"
+	"unsafe"
+
+	"gioui.org/gpu/internal/driver"
+	"gioui.org/internal/byteslice"
+	"gioui.org/internal/f32"
+	"gioui.org/internal/f32color"
+	"gioui.org/shader"
+	"gioui.org/shader/gio"
+)
+
+type pather struct {
+	ctx driver.Device
+
+	viewport image.Point
+
+	stenciler *stenciler
+	coverer   *coverer
+}
+
+type coverer struct {
+	ctx                    driver.Device
+	pipelines              [2][3]*pipeline
+	texUniforms            *coverTexUniforms
+	colUniforms            *coverColUniforms
+	linearGradientUniforms *coverLinearGradientUniforms
+}
+
+type coverTexUniforms struct {
+	coverUniforms
+	_ [12]byte // Padding to multiple of 16.
+}
+
+type coverColUniforms struct {
+	coverUniforms
+	_ [128 - unsafe.Sizeof(coverUniforms{}) - unsafe.Sizeof(colorUniforms{})]byte // Padding to 128 bytes.
+	colorUniforms
+}
+
+type coverLinearGradientUniforms struct {
+	coverUniforms
+	_ [128 - unsafe.Sizeof(coverUniforms{}) - unsafe.Sizeof(gradientUniforms{})]byte // Padding to 128.
+	gradientUniforms
+}
+
+type coverUniforms struct {
+	transform        [4]float32
+	uvCoverTransform [4]float32
+	uvTransformR1    [4]float32
+	uvTransformR2    [4]float32
+	fbo              float32
+}
+
+type stenciler struct {
+	ctx      driver.Device
+	pipeline struct {
+		pipeline *pipeline
+		uniforms *stencilUniforms
+	}
+	ipipeline struct {
+		pipeline *pipeline
+		uniforms *intersectUniforms
+	}
+	fbos          fboSet
+	intersections fboSet
+	indexBuf      driver.Buffer
+}
+
+type stencilUniforms struct {
+	transform  [4]float32
+	pathOffset [2]float32
+	_          [8]byte // Padding to multiple of 16.
+}
+
+type intersectUniforms struct {
+	vert struct {
+		uvTransform    [4]float32
+		subUVTransform [4]float32
+	}
+}
+
+type fboSet struct {
+	fbos []FBO
+}
+
+type FBO struct {
+	size image.Point
+	tex  driver.Texture
+}
+
+type pathData struct {
+	ncurves int
+	data    driver.Buffer
+}
+
+// vertex data suitable for passing to vertex programs.
+type vertex struct {
+	// Corner encodes the corner: +0.5 for south, +.25 for east.
+	Corner       float32
+	MaxY         float32
+	FromX, FromY float32
+	CtrlX, CtrlY float32
+	ToX, ToY     float32
+}
+
+// encode needs to stay in-sync with the code in clip.go encodeQuadTo.
+func (v vertex) encode(d []byte, maxy uint32) {
+	d = d[0:32]
+	bo := binary.LittleEndian
+	bo.PutUint32(d[0:4], math.Float32bits(v.Corner))
+	bo.PutUint32(d[4:8], maxy)
+	bo.PutUint32(d[8:12], math.Float32bits(v.FromX))
+	bo.PutUint32(d[12:16], math.Float32bits(v.FromY))
+	bo.PutUint32(d[16:20], math.Float32bits(v.CtrlX))
+	bo.PutUint32(d[20:24], math.Float32bits(v.CtrlY))
+	bo.PutUint32(d[24:28], math.Float32bits(v.ToX))
+	bo.PutUint32(d[28:32], math.Float32bits(v.ToY))
+}
+
+const (
+	// Number of path quads per draw batch.
+	pathBatchSize = 10000
+	// Size of a vertex as sent to gpu
+	vertStride = 8 * 4
+)
+
+func newPather(ctx driver.Device) *pather {
+	return &pather{
+		ctx:       ctx,
+		stenciler: newStenciler(ctx),
+		coverer:   newCoverer(ctx),
+	}
+}
+
+func newCoverer(ctx driver.Device) *coverer {
+	c := &coverer{
+		ctx: ctx,
+	}
+	c.colUniforms = new(coverColUniforms)
+	c.texUniforms = new(coverTexUniforms)
+	c.linearGradientUniforms = new(coverLinearGradientUniforms)
+	pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
+		[3]interface{}{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
+	)
+	if err != nil {
+		panic(err)
+	}
+	c.pipelines = pipelines
+	return c
+}
+
+func newStenciler(ctx driver.Device) *stenciler {
+	// Allocate a suitably large index buffer for drawing paths.
+	indices := make([]uint16, pathBatchSize*6)
+	for i := 0; i < pathBatchSize; i++ {
+		i := uint16(i)
+		indices[i*6+0] = i*4 + 0
+		indices[i*6+1] = i*4 + 1
+		indices[i*6+2] = i*4 + 2
+		indices[i*6+3] = i*4 + 2
+		indices[i*6+4] = i*4 + 1
+		indices[i*6+5] = i*4 + 3
+	}
+	indexBuf, err := ctx.NewImmutableBuffer(driver.BufferBindingIndices, byteslice.Slice(indices))
+	if err != nil {
+		panic(err)
+	}
+	progLayout := driver.VertexLayout{
+		Inputs: []driver.InputDesc{
+			{Type: shader.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).Corner))},
+			{Type: shader.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).MaxY))},
+			{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).FromX))},
+			{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).CtrlX))},
+			{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).ToX))},
+		},
+		Stride: vertStride,
+	}
+	iprogLayout := driver.VertexLayout{
+		Inputs: []driver.InputDesc{
+			{Type: shader.DataTypeFloat, Size: 2, Offset: 0},
+			{Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
+		},
+		Stride: 4 * 4,
+	}
+	st := &stenciler{
+		ctx:      ctx,
+		indexBuf: indexBuf,
+	}
+	vsh, fsh, err := newShaders(ctx, gio.Shader_stencil_vert, gio.Shader_stencil_frag)
+	if err != nil {
+		panic(err)
+	}
+	defer vsh.Release()
+	defer fsh.Release()
+	st.pipeline.uniforms = new(stencilUniforms)
+	vertUniforms := newUniformBuffer(ctx, st.pipeline.uniforms)
+	pipe, err := st.ctx.NewPipeline(driver.PipelineDesc{
+		VertexShader:   vsh,
+		FragmentShader: fsh,
+		VertexLayout:   progLayout,
+		BlendDesc: driver.BlendDesc{
+			Enable:    true,
+			SrcFactor: driver.BlendFactorOne,
+			DstFactor: driver.BlendFactorOne,
+		},
+		PixelFormat: driver.TextureFormatFloat,
+		Topology:    driver.TopologyTriangles,
+	})
+	st.pipeline.pipeline = &pipeline{pipe, vertUniforms}
+	if err != nil {
+		panic(err)
+	}
+	vsh, fsh, err = newShaders(ctx, gio.Shader_intersect_vert, gio.Shader_intersect_frag)
+	if err != nil {
+		panic(err)
+	}
+	defer vsh.Release()
+	defer fsh.Release()
+	st.ipipeline.uniforms = new(intersectUniforms)
+	vertUniforms = newUniformBuffer(ctx, &st.ipipeline.uniforms.vert)
+	ipipe, err := st.ctx.NewPipeline(driver.PipelineDesc{
+		VertexShader:   vsh,
+		FragmentShader: fsh,
+		VertexLayout:   iprogLayout,
+		BlendDesc: driver.BlendDesc{
+			Enable:    true,
+			SrcFactor: driver.BlendFactorDstColor,
+			DstFactor: driver.BlendFactorZero,
+		},
+		PixelFormat: driver.TextureFormatFloat,
+		Topology:    driver.TopologyTriangleStrip,
+	})
+	st.ipipeline.pipeline = &pipeline{ipipe, vertUniforms}
+	if err != nil {
+		panic(err)
+	}
+	return st
+}
+
+func (s *fboSet) resize(ctx driver.Device, format driver.TextureFormat, sizes []image.Point) {
+	// Add fbos.
+	for i := len(s.fbos); i < len(sizes); i++ {
+		s.fbos = append(s.fbos, FBO{})
+	}
+	// Resize fbos.
+	for i, sz := range sizes {
+		f := &s.fbos[i]
+		// Resizing or recreating FBOs can introduce rendering stalls.
+		// Avoid if the space waste is not too high.
+		resize := sz.X > f.size.X || sz.Y > f.size.Y
+		waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
+		resize = resize || waste > 1.2
+		if resize {
+			if f.tex != nil {
+				f.tex.Release()
+			}
+			// Add 5% extra space in each dimension to minimize resizing.
+			sz = sz.Mul(105).Div(100)
+			max := ctx.Caps().MaxTextureSize
+			if sz.Y > max {
+				sz.Y = max
+			}
+			if sz.X > max {
+				sz.X = max
+			}
+			tex, err := ctx.NewTexture(format, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
+				driver.BufferBindingTexture|driver.BufferBindingFramebuffer)
+			if err != nil {
+				panic(err)
+			}
+			f.size = sz
+			f.tex = tex
+		}
+	}
+	// Delete extra fbos.
+	s.delete(ctx, len(sizes))
+}
+
+func (s *fboSet) delete(ctx driver.Device, idx int) {
+	for i := idx; i < len(s.fbos); i++ {
+		f := s.fbos[i]
+		f.tex.Release()
+	}
+	s.fbos = s.fbos[:idx]
+}
+
+func (s *stenciler) release() {
+	s.fbos.delete(s.ctx, 0)
+	s.intersections.delete(s.ctx, 0)
+	s.pipeline.pipeline.Release()
+	s.ipipeline.pipeline.Release()
+	s.indexBuf.Release()
+}
+
+func (p *pather) release() {
+	p.stenciler.release()
+	p.coverer.release()
+}
+
+func (c *coverer) release() {
+	for _, p := range c.pipelines {
+		for _, p := range p {
+			p.Release()
+		}
+	}
+}
+
+func buildPath(ctx driver.Device, p []byte) pathData {
+	buf, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices, p)
+	if err != nil {
+		panic(err)
+	}
+	return pathData{
+		ncurves: len(p) / vertStride,
+		data:    buf,
+	}
+}
+
+func (p pathData) release() {
+	p.data.Release()
+}
+
+func (p *pather) begin(sizes []image.Point) {
+	p.stenciler.begin(sizes)
+}
+
+func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
+	p.stenciler.stencilPath(bounds, offset, uv, data)
+}
+
+func (s *stenciler) beginIntersect(sizes []image.Point) {
+	// 8 bit coverage is enough, but OpenGL ES only supports single channel
+	// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
+	// no floating point support is available.
+	s.intersections.resize(s.ctx, driver.TextureFormatFloat, sizes)
+}
+
+func (s *stenciler) cover(idx int) FBO {
+	return s.fbos.fbos[idx]
+}
+
+func (s *stenciler) begin(sizes []image.Point) {
+	s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
+}
+
+func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
+	s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
+	// Transform UI coordinates to OpenGL coordinates.
+	texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
+	scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
+	orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
+	s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
+	s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y}
+	s.pipeline.pipeline.UploadUniforms(s.ctx)
+	// Draw in batches that fit in uint16 indices.
+	start := 0
+	nquads := data.ncurves / 4
+	for start < nquads {
+		batch := nquads - start
+		if max := pathBatchSize; batch > max {
+			batch = max
+		}
+		off := vertStride * start * 4
+		s.ctx.BindVertexBuffer(data.data, off)
+		s.ctx.DrawElements(0, batch*6)
+		start += batch
+	}
+}
+
+func (p *pather) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
+	p.coverer.cover(mat, isFBO, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
+}
+
+func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
+	var uniforms *coverUniforms
+	switch mat {
+	case materialColor:
+		c.colUniforms.color = col
+		uniforms = &c.colUniforms.coverUniforms
+	case materialLinearGradient:
+		c.linearGradientUniforms.color1 = col1
+		c.linearGradientUniforms.color2 = col2
+
+		t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
+		c.linearGradientUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
+		c.linearGradientUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
+		uniforms = &c.linearGradientUniforms.coverUniforms
+	case materialTexture:
+		t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
+		c.texUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
+		c.texUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
+		uniforms = &c.texUniforms.coverUniforms
+	}
+	uniforms.fbo = 0
+	if isFBO {
+		uniforms.fbo = 1
+	}
+	uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
+	uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
+	fboIdx := 0
+	if isFBO {
+		fboIdx = 1
+	}
+	c.pipelines[fboIdx][mat].UploadUniforms(c.ctx)
+	c.ctx.DrawArrays(0, 4)
+}
+
+func init() {
+	// Check that struct vertex has the expected size and
+	// that it contains no padding.
+	if unsafe.Sizeof(*(*vertex)(nil)) != vertStride {
+		panic("unexpected struct size")
+	}
+}

+ 94 - 0
vendor/gioui.org/gpu/timer.go

@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package gpu
+
+import (
+	"time"
+
+	"gioui.org/gpu/internal/driver"
+)
+
+type timers struct {
+	backend driver.Device
+	timers  []*timer
+}
+
+type timer struct {
+	Elapsed time.Duration
+	backend driver.Device
+	timer   driver.Timer
+	state   timerState
+}
+
+type timerState uint8
+
+const (
+	timerIdle timerState = iota
+	timerRunning
+	timerWaiting
+)
+
+func newTimers(b driver.Device) *timers {
+	return &timers{
+		backend: b,
+	}
+}
+
+func (t *timers) newTimer() *timer {
+	if t == nil {
+		return nil
+	}
+	tt := &timer{
+		backend: t.backend,
+		timer:   t.backend.NewTimer(),
+	}
+	t.timers = append(t.timers, tt)
+	return tt
+}
+
+func (t *timer) begin() {
+	if t == nil || t.state != timerIdle {
+		return
+	}
+	t.timer.Begin()
+	t.state = timerRunning
+}
+
+func (t *timer) end() {
+	if t == nil || t.state != timerRunning {
+		return
+	}
+	t.timer.End()
+	t.state = timerWaiting
+}
+
+func (t *timers) ready() bool {
+	if t == nil {
+		return false
+	}
+	for _, tt := range t.timers {
+		switch tt.state {
+		case timerIdle:
+			continue
+		case timerRunning:
+			return false
+		}
+		d, ok := tt.timer.Duration()
+		if !ok {
+			return false
+		}
+		tt.state = timerIdle
+		tt.Elapsed = d
+	}
+	return t.backend.IsTimeContinuous()
+}
+
+func (t *timers) Release() {
+	if t == nil {
+		return
+	}
+	for _, tt := range t.timers {
+		tt.timer.Release()
+	}
+	t.timers = nil
+}

+ 36 - 0
vendor/gioui.org/internal/byteslice/byteslice.go

@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// Package byteslice provides byte slice views of other Go values  such as
+// slices and structs.
+package byteslice
+
+import (
+	"reflect"
+	"unsafe"
+)
+
+// Struct returns a byte slice view of a struct.
+func Struct(s interface{}) []byte {
+	v := reflect.ValueOf(s)
+	sz := int(v.Elem().Type().Size())
+	return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz)
+}
+
+// Uint32 returns a byte slice view of a uint32 slice.
+func Uint32(s []uint32) []byte {
+	n := len(s)
+	if n == 0 {
+		return nil
+	}
+	blen := n * int(unsafe.Sizeof(s[0]))
+	return unsafe.Slice((*byte)(unsafe.Pointer(&s[0])), blen)
+}
+
+// Slice returns a byte slice view of a slice.
+func Slice(s interface{}) []byte {
+	v := reflect.ValueOf(s)
+	first := v.Index(0)
+	sz := int(first.Type().Size())
+	res := unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz*v.Cap())
+	return res[:sz*v.Len()]
+}

+ 21 - 0
vendor/gioui.org/internal/cocoainit/cocoa_darwin.go

@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// Package cocoainit initializes support for multithreaded
+// programs in Cocoa.
+package cocoainit
+
+/*
+#cgo CFLAGS: -xobjective-c -fobjc-arc
+#cgo LDFLAGS: -framework Foundation
+#import <Foundation/Foundation.h>
+
+static inline void activate_cocoa_multithreading() {
+    [[NSThread new] start];
+}
+#pragma GCC visibility push(hidden)
+*/
+import "C"
+
+func init() {
+	C.activate_cocoa_multithreading()
+}

+ 1694 - 0
vendor/gioui.org/internal/d3d11/d3d11_windows.go

@@ -0,0 +1,1694 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package d3d11
+
+import (
+	"fmt"
+	"math"
+	"syscall"
+	"unsafe"
+
+	"gioui.org/internal/f32color"
+
+	"golang.org/x/sys/windows"
+)
+
+type DXGI_SWAP_CHAIN_DESC struct {
+	BufferDesc   DXGI_MODE_DESC
+	SampleDesc   DXGI_SAMPLE_DESC
+	BufferUsage  uint32
+	BufferCount  uint32
+	OutputWindow windows.Handle
+	Windowed     uint32
+	SwapEffect   uint32
+	Flags        uint32
+}
+
+type DXGI_SAMPLE_DESC struct {
+	Count   uint32
+	Quality uint32
+}
+
+type DXGI_MODE_DESC struct {
+	Width            uint32
+	Height           uint32
+	RefreshRate      DXGI_RATIONAL
+	Format           uint32
+	ScanlineOrdering uint32
+	Scaling          uint32
+}
+
+type DXGI_RATIONAL struct {
+	Numerator   uint32
+	Denominator uint32
+}
+
+type TEXTURE2D_DESC struct {
+	Width          uint32
+	Height         uint32
+	MipLevels      uint32
+	ArraySize      uint32
+	Format         uint32
+	SampleDesc     DXGI_SAMPLE_DESC
+	Usage          uint32
+	BindFlags      uint32
+	CPUAccessFlags uint32
+	MiscFlags      uint32
+}
+
+type SAMPLER_DESC struct {
+	Filter         uint32
+	AddressU       uint32
+	AddressV       uint32
+	AddressW       uint32
+	MipLODBias     float32
+	MaxAnisotropy  uint32
+	ComparisonFunc uint32
+	BorderColor    [4]float32
+	MinLOD         float32
+	MaxLOD         float32
+}
+
+type SHADER_RESOURCE_VIEW_DESC_TEX2D struct {
+	SHADER_RESOURCE_VIEW_DESC
+	Texture2D TEX2D_SRV
+}
+
+type SHADER_RESOURCE_VIEW_DESC_BUFFEREX struct {
+	SHADER_RESOURCE_VIEW_DESC
+	Buffer BUFFEREX_SRV
+}
+
+type UNORDERED_ACCESS_VIEW_DESC_TEX2D struct {
+	UNORDERED_ACCESS_VIEW_DESC
+	Texture2D TEX2D_UAV
+}
+
+type UNORDERED_ACCESS_VIEW_DESC_BUFFER struct {
+	UNORDERED_ACCESS_VIEW_DESC
+	Buffer BUFFER_UAV
+}
+
+type SHADER_RESOURCE_VIEW_DESC struct {
+	Format        uint32
+	ViewDimension uint32
+}
+
+type UNORDERED_ACCESS_VIEW_DESC struct {
+	Format        uint32
+	ViewDimension uint32
+}
+
+type TEX2D_SRV struct {
+	MostDetailedMip uint32
+	MipLevels       uint32
+}
+
+type BUFFEREX_SRV struct {
+	FirstElement uint32
+	NumElements  uint32
+	Flags        uint32
+}
+
+type TEX2D_UAV struct {
+	MipSlice uint32
+}
+
+type BUFFER_UAV struct {
+	FirstElement uint32
+	NumElements  uint32
+	Flags        uint32
+}
+
+type INPUT_ELEMENT_DESC struct {
+	SemanticName         *byte
+	SemanticIndex        uint32
+	Format               uint32
+	InputSlot            uint32
+	AlignedByteOffset    uint32
+	InputSlotClass       uint32
+	InstanceDataStepRate uint32
+}
+
+type IDXGISwapChain struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+		SetPrivateData          uintptr
+		SetPrivateDataInterface uintptr
+		GetPrivateData          uintptr
+		GetParent               uintptr
+		GetDevice               uintptr
+		Present                 uintptr
+		GetBuffer               uintptr
+		SetFullscreenState      uintptr
+		GetFullscreenState      uintptr
+		GetDesc                 uintptr
+		ResizeBuffers           uintptr
+		ResizeTarget            uintptr
+		GetContainingOutput     uintptr
+		GetFrameStatistics      uintptr
+		GetLastPresentCount     uintptr
+	}
+}
+
+type Debug struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+		SetFeatureMask             uintptr
+		GetFeatureMask             uintptr
+		SetPresentPerRenderOpDelay uintptr
+		GetPresentPerRenderOpDelay uintptr
+		SetSwapChain               uintptr
+		GetSwapChain               uintptr
+		ValidateContext            uintptr
+		ReportLiveDeviceObjects    uintptr
+		ValidateContextForDispatch uintptr
+	}
+}
+
+type Device struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+		CreateBuffer                         uintptr
+		CreateTexture1D                      uintptr
+		CreateTexture2D                      uintptr
+		CreateTexture3D                      uintptr
+		CreateShaderResourceView             uintptr
+		CreateUnorderedAccessView            uintptr
+		CreateRenderTargetView               uintptr
+		CreateDepthStencilView               uintptr
+		CreateInputLayout                    uintptr
+		CreateVertexShader                   uintptr
+		CreateGeometryShader                 uintptr
+		CreateGeometryShaderWithStreamOutput uintptr
+		CreatePixelShader                    uintptr
+		CreateHullShader                     uintptr
+		CreateDomainShader                   uintptr
+		CreateComputeShader                  uintptr
+		CreateClassLinkage                   uintptr
+		CreateBlendState                     uintptr
+		CreateDepthStencilState              uintptr
+		CreateRasterizerState                uintptr
+		CreateSamplerState                   uintptr
+		CreateQuery                          uintptr
+		CreatePredicate                      uintptr
+		CreateCounter                        uintptr
+		CreateDeferredContext                uintptr
+		OpenSharedResource                   uintptr
+		CheckFormatSupport                   uintptr
+		CheckMultisampleQualityLevels        uintptr
+		CheckCounterInfo                     uintptr
+		CheckCounter                         uintptr
+		CheckFeatureSupport                  uintptr
+		GetPrivateData                       uintptr
+		SetPrivateData                       uintptr
+		SetPrivateDataInterface              uintptr
+		GetFeatureLevel                      uintptr
+		GetCreationFlags                     uintptr
+		GetDeviceRemovedReason               uintptr
+		GetImmediateContext                  uintptr
+		SetExceptionMode                     uintptr
+		GetExceptionMode                     uintptr
+	}
+}
+
+type DeviceContext struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+		GetDevice                                 uintptr
+		GetPrivateData                            uintptr
+		SetPrivateData                            uintptr
+		SetPrivateDataInterface                   uintptr
+		VSSetConstantBuffers                      uintptr
+		PSSetShaderResources                      uintptr
+		PSSetShader                               uintptr
+		PSSetSamplers                             uintptr
+		VSSetShader                               uintptr
+		DrawIndexed                               uintptr
+		Draw                                      uintptr
+		Map                                       uintptr
+		Unmap                                     uintptr
+		PSSetConstantBuffers                      uintptr
+		IASetInputLayout                          uintptr
+		IASetVertexBuffers                        uintptr
+		IASetIndexBuffer                          uintptr
+		DrawIndexedInstanced                      uintptr
+		DrawInstanced                             uintptr
+		GSSetConstantBuffers                      uintptr
+		GSSetShader                               uintptr
+		IASetPrimitiveTopology                    uintptr
+		VSSetShaderResources                      uintptr
+		VSSetSamplers                             uintptr
+		Begin                                     uintptr
+		End                                       uintptr
+		GetData                                   uintptr
+		SetPredication                            uintptr
+		GSSetShaderResources                      uintptr
+		GSSetSamplers                             uintptr
+		OMSetRenderTargets                        uintptr
+		OMSetRenderTargetsAndUnorderedAccessViews uintptr
+		OMSetBlendState                           uintptr
+		OMSetDepthStencilState                    uintptr
+		SOSetTargets                              uintptr
+		DrawAuto                                  uintptr
+		DrawIndexedInstancedIndirect              uintptr
+		DrawInstancedIndirect                     uintptr
+		Dispatch                                  uintptr
+		DispatchIndirect                          uintptr
+		RSSetState                                uintptr
+		RSSetViewports                            uintptr
+		RSSetScissorRects                         uintptr
+		CopySubresourceRegion                     uintptr
+		CopyResource                              uintptr
+		UpdateSubresource                         uintptr
+		CopyStructureCount                        uintptr
+		ClearRenderTargetView                     uintptr
+		ClearUnorderedAccessViewUint              uintptr
+		ClearUnorderedAccessViewFloat             uintptr
+		ClearDepthStencilView                     uintptr
+		GenerateMips                              uintptr
+		SetResourceMinLOD                         uintptr
+		GetResourceMinLOD                         uintptr
+		ResolveSubresource                        uintptr
+		ExecuteCommandList                        uintptr
+		HSSetShaderResources                      uintptr
+		HSSetShader                               uintptr
+		HSSetSamplers                             uintptr
+		HSSetConstantBuffers                      uintptr
+		DSSetShaderResources                      uintptr
+		DSSetShader                               uintptr
+		DSSetSamplers                             uintptr
+		DSSetConstantBuffers                      uintptr
+		CSSetShaderResources                      uintptr
+		CSSetUnorderedAccessViews                 uintptr
+		CSSetShader                               uintptr
+		CSSetSamplers                             uintptr
+		CSSetConstantBuffers                      uintptr
+		VSGetConstantBuffers                      uintptr
+		PSGetShaderResources                      uintptr
+		PSGetShader                               uintptr
+		PSGetSamplers                             uintptr
+		VSGetShader                               uintptr
+		PSGetConstantBuffers                      uintptr
+		IAGetInputLayout                          uintptr
+		IAGetVertexBuffers                        uintptr
+		IAGetIndexBuffer                          uintptr
+		GSGetConstantBuffers                      uintptr
+		GSGetShader                               uintptr
+		IAGetPrimitiveTopology                    uintptr
+		VSGetShaderResources                      uintptr
+		VSGetSamplers                             uintptr
+		GetPredication                            uintptr
+		GSGetShaderResources                      uintptr
+		GSGetSamplers                             uintptr
+		OMGetRenderTargets                        uintptr
+		OMGetRenderTargetsAndUnorderedAccessViews uintptr
+		OMGetBlendState                           uintptr
+		OMGetDepthStencilState                    uintptr
+		SOGetTargets                              uintptr
+		RSGetState                                uintptr
+		RSGetViewports                            uintptr
+		RSGetScissorRects                         uintptr
+		HSGetShaderResources                      uintptr
+		HSGetShader                               uintptr
+		HSGetSamplers                             uintptr
+		HSGetConstantBuffers                      uintptr
+		DSGetShaderResources                      uintptr
+		DSGetShader                               uintptr
+		DSGetSamplers                             uintptr
+		DSGetConstantBuffers                      uintptr
+		CSGetShaderResources                      uintptr
+		CSGetUnorderedAccessViews                 uintptr
+		CSGetShader                               uintptr
+		CSGetSamplers                             uintptr
+		CSGetConstantBuffers                      uintptr
+		ClearState                                uintptr
+		Flush                                     uintptr
+		GetType                                   uintptr
+		GetContextFlags                           uintptr
+		FinishCommandList                         uintptr
+	}
+}
+
+type RenderTargetView struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type Resource struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type Texture2D struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type Buffer struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type SamplerState struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type PixelShader struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type ShaderResourceView struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type UnorderedAccessView struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type DepthStencilView struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type BlendState struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type DepthStencilState struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type VertexShader struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type ComputeShader struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type RasterizerState struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type InputLayout struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+		GetBufferPointer uintptr
+		GetBufferSize    uintptr
+	}
+}
+
+type DEPTH_STENCIL_DESC struct {
+	DepthEnable      uint32
+	DepthWriteMask   uint32
+	DepthFunc        uint32
+	StencilEnable    uint32
+	StencilReadMask  uint8
+	StencilWriteMask uint8
+	FrontFace        DEPTH_STENCILOP_DESC
+	BackFace         DEPTH_STENCILOP_DESC
+}
+
+type DEPTH_STENCILOP_DESC struct {
+	StencilFailOp      uint32
+	StencilDepthFailOp uint32
+	StencilPassOp      uint32
+	StencilFunc        uint32
+}
+
+type DEPTH_STENCIL_VIEW_DESC_TEX2D struct {
+	Format        uint32
+	ViewDimension uint32
+	Flags         uint32
+	Texture2D     TEX2D_DSV
+}
+
+type TEX2D_DSV struct {
+	MipSlice uint32
+}
+
+type BLEND_DESC struct {
+	AlphaToCoverageEnable  uint32
+	IndependentBlendEnable uint32
+	RenderTarget           [8]RENDER_TARGET_BLEND_DESC
+}
+
+type RENDER_TARGET_BLEND_DESC struct {
+	BlendEnable           uint32
+	SrcBlend              uint32
+	DestBlend             uint32
+	BlendOp               uint32
+	SrcBlendAlpha         uint32
+	DestBlendAlpha        uint32
+	BlendOpAlpha          uint32
+	RenderTargetWriteMask uint8
+}
+
+type IDXGIObject struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+		SetPrivateData          uintptr
+		SetPrivateDataInterface uintptr
+		GetPrivateData          uintptr
+		GetParent               uintptr
+	}
+}
+
+type IDXGIAdapter struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+		SetPrivateData          uintptr
+		SetPrivateDataInterface uintptr
+		GetPrivateData          uintptr
+		GetParent               uintptr
+		EnumOutputs             uintptr
+		GetDesc                 uintptr
+		CheckInterfaceSupport   uintptr
+		GetDesc1                uintptr
+	}
+}
+
+type IDXGIFactory struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+		SetPrivateData          uintptr
+		SetPrivateDataInterface uintptr
+		GetPrivateData          uintptr
+		GetParent               uintptr
+		EnumAdapters            uintptr
+		MakeWindowAssociation   uintptr
+		GetWindowAssociation    uintptr
+		CreateSwapChain         uintptr
+		CreateSoftwareAdapter   uintptr
+	}
+}
+
+type IDXGIDebug struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+		ReportLiveObjects uintptr
+	}
+}
+
+type IDXGIDevice struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+		SetPrivateData          uintptr
+		SetPrivateDataInterface uintptr
+		GetPrivateData          uintptr
+		GetParent               uintptr
+		GetAdapter              uintptr
+		CreateSurface           uintptr
+		QueryResourceResidency  uintptr
+		SetGPUThreadPriority    uintptr
+		GetGPUThreadPriority    uintptr
+	}
+}
+
+type IUnknown struct {
+	Vtbl *struct {
+		_IUnknownVTbl
+	}
+}
+
+type _IUnknownVTbl struct {
+	QueryInterface uintptr
+	AddRef         uintptr
+	Release        uintptr
+}
+
+type BUFFER_DESC struct {
+	ByteWidth           uint32
+	Usage               uint32
+	BindFlags           uint32
+	CPUAccessFlags      uint32
+	MiscFlags           uint32
+	StructureByteStride uint32
+}
+
+type GUID struct {
+	Data1   uint32
+	Data2   uint16
+	Data3   uint16
+	Data4_0 uint8
+	Data4_1 uint8
+	Data4_2 uint8
+	Data4_3 uint8
+	Data4_4 uint8
+	Data4_5 uint8
+	Data4_6 uint8
+	Data4_7 uint8
+}
+
+type VIEWPORT struct {
+	TopLeftX float32
+	TopLeftY float32
+	Width    float32
+	Height   float32
+	MinDepth float32
+	MaxDepth float32
+}
+
+type SUBRESOURCE_DATA struct {
+	pSysMem *byte
+}
+
+type BOX struct {
+	Left   uint32
+	Top    uint32
+	Front  uint32
+	Right  uint32
+	Bottom uint32
+	Back   uint32
+}
+
+type MAPPED_SUBRESOURCE struct {
+	PData      uintptr
+	RowPitch   uint32
+	DepthPitch uint32
+}
+
+type ErrorCode struct {
+	Name string
+	Code uint32
+}
+
+type RASTERIZER_DESC struct {
+	FillMode              uint32
+	CullMode              uint32
+	FrontCounterClockwise uint32
+	DepthBias             int32
+	DepthBiasClamp        float32
+	SlopeScaledDepthBias  float32
+	DepthClipEnable       uint32
+	ScissorEnable         uint32
+	MultisampleEnable     uint32
+	AntialiasedLineEnable uint32
+}
+
+var (
+	IID_Texture2D    = GUID{0x6f15aaf2, 0xd208, 0x4e89, 0x9a, 0xb4, 0x48, 0x95, 0x35, 0xd3, 0x4f, 0x9c}
+	IID_IDXGIDebug   = GUID{0x119E7452, 0xDE9E, 0x40fe, 0x88, 0x06, 0x88, 0xF9, 0x0C, 0x12, 0xB4, 0x41}
+	IID_IDXGIDevice  = GUID{0x54ec77fa, 0x1377, 0x44e6, 0x8c, 0x32, 0x88, 0xfd, 0x5f, 0x44, 0xc8, 0x4c}
+	IID_IDXGIFactory = GUID{0x7b7166ec, 0x21c7, 0x44ae, 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3, 0x69}
+	IID_ID3D11Debug  = GUID{0x79cf2233, 0x7536, 0x4948, 0x9d, 0x36, 0x1e, 0x46, 0x92, 0xdc, 0x57, 0x60}
+
+	DXGI_DEBUG_ALL = GUID{0xe48ae283, 0xda80, 0x490b, 0x87, 0xe6, 0x43, 0xe9, 0xa9, 0xcf, 0xda, 0x8}
+)
+
+var (
+	d3d11 = windows.NewLazySystemDLL("d3d11.dll")
+
+	_D3D11CreateDevice             = d3d11.NewProc("D3D11CreateDevice")
+	_D3D11CreateDeviceAndSwapChain = d3d11.NewProc("D3D11CreateDeviceAndSwapChain")
+
+	dxgi = windows.NewLazySystemDLL("dxgi.dll")
+
+	_DXGIGetDebugInterface1 = dxgi.NewProc("DXGIGetDebugInterface1")
+)
+
+const (
+	SDK_VERSION          = 7
+	DRIVER_TYPE_HARDWARE = 1
+
+	DXGI_FORMAT_UNKNOWN             = 0
+	DXGI_FORMAT_R16_FLOAT           = 54
+	DXGI_FORMAT_R32_FLOAT           = 41
+	DXGI_FORMAT_R32_TYPELESS        = 39
+	DXGI_FORMAT_R32G32_FLOAT        = 16
+	DXGI_FORMAT_R32G32B32_FLOAT     = 6
+	DXGI_FORMAT_R32G32B32A32_FLOAT  = 2
+	DXGI_FORMAT_R8G8B8A8_UNORM      = 28
+	DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
+	DXGI_FORMAT_R16_SINT            = 59
+	DXGI_FORMAT_R16G16_SINT         = 38
+	DXGI_FORMAT_R16_UINT            = 57
+	DXGI_FORMAT_D24_UNORM_S8_UINT   = 45
+	DXGI_FORMAT_R16G16_FLOAT        = 34
+	DXGI_FORMAT_R16G16B16A16_FLOAT  = 10
+
+	DXGI_DEBUG_RLO_SUMMARY         = 0x1
+	DXGI_DEBUG_RLO_DETAIL          = 0x2
+	DXGI_DEBUG_RLO_IGNORE_INTERNAL = 0x4
+
+	FORMAT_SUPPORT_TEXTURE2D     = 0x20
+	FORMAT_SUPPORT_RENDER_TARGET = 0x4000
+
+	DXGI_USAGE_RENDER_TARGET_OUTPUT = 1 << (1 + 4)
+
+	CPU_ACCESS_READ = 0x20000
+
+	MAP_READ = 1
+
+	DXGI_SWAP_EFFECT_DISCARD = 0
+
+	FEATURE_LEVEL_9_1  = 0x9100
+	FEATURE_LEVEL_9_3  = 0x9300
+	FEATURE_LEVEL_11_0 = 0xb000
+
+	USAGE_IMMUTABLE = 1
+	USAGE_STAGING   = 3
+
+	BIND_VERTEX_BUFFER    = 0x1
+	BIND_INDEX_BUFFER     = 0x2
+	BIND_CONSTANT_BUFFER  = 0x4
+	BIND_SHADER_RESOURCE  = 0x8
+	BIND_RENDER_TARGET    = 0x20
+	BIND_DEPTH_STENCIL    = 0x40
+	BIND_UNORDERED_ACCESS = 0x80
+
+	PRIMITIVE_TOPOLOGY_TRIANGLELIST  = 4
+	PRIMITIVE_TOPOLOGY_TRIANGLESTRIP = 5
+
+	FILTER_MIN_MAG_LINEAR_MIP_POINT = 0x14
+	FILTER_MIN_MAG_MIP_LINEAR       = 0x15
+	FILTER_MIN_MAG_MIP_POINT        = 0
+
+	TEXTURE_ADDRESS_MIRROR = 2
+	TEXTURE_ADDRESS_CLAMP  = 3
+	TEXTURE_ADDRESS_WRAP   = 1
+
+	SRV_DIMENSION_BUFFER    = 1
+	UAV_DIMENSION_BUFFER    = 1
+	SRV_DIMENSION_BUFFEREX  = 11
+	SRV_DIMENSION_TEXTURE2D = 4
+	UAV_DIMENSION_TEXTURE2D = 4
+
+	BUFFER_UAV_FLAG_RAW   = 0x1
+	BUFFEREX_SRV_FLAG_RAW = 0x1
+
+	RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS = 0x20
+	RESOURCE_MISC_GENERATE_MIPS          = 0x1
+
+	CREATE_DEVICE_DEBUG = 0x2
+
+	FILL_SOLID = 3
+
+	CULL_NONE = 1
+
+	CLEAR_DEPTH   = 0x1
+	CLEAR_STENCIL = 0x2
+
+	DSV_DIMENSION_TEXTURE2D = 3
+
+	DEPTH_WRITE_MASK_ALL = 1
+
+	COMPARISON_GREATER       = 5
+	COMPARISON_GREATER_EQUAL = 7
+
+	BLEND_OP_ADD        = 1
+	BLEND_ONE           = 2
+	BLEND_INV_SRC_ALPHA = 6
+	BLEND_ZERO          = 1
+	BLEND_DEST_COLOR    = 9
+	BLEND_DEST_ALPHA    = 7
+
+	COLOR_WRITE_ENABLE_ALL = 1 | 2 | 4 | 8
+
+	DXGI_STATUS_OCCLUDED      = 0x087A0001
+	DXGI_ERROR_DEVICE_RESET   = 0x887A0007
+	DXGI_ERROR_DEVICE_REMOVED = 0x887A0005
+	D3DDDIERR_DEVICEREMOVED   = 1<<31 | 0x876<<16 | 2160
+
+	RLDO_SUMMARY         = 1
+	RLDO_DETAIL          = 2
+	RLDO_IGNORE_INTERNAL = 4
+)
+
+func CreateDevice(driverType uint32, flags uint32) (*Device, *DeviceContext, uint32, error) {
+	var (
+		dev     *Device
+		ctx     *DeviceContext
+		featLvl uint32
+	)
+	r, _, _ := _D3D11CreateDevice.Call(
+		0,                                 // pAdapter
+		uintptr(driverType),               // driverType
+		0,                                 // Software
+		uintptr(flags),                    // Flags
+		0,                                 // pFeatureLevels
+		0,                                 // FeatureLevels
+		SDK_VERSION,                       // SDKVersion
+		uintptr(unsafe.Pointer(&dev)),     // ppDevice
+		uintptr(unsafe.Pointer(&featLvl)), // pFeatureLevel
+		uintptr(unsafe.Pointer(&ctx)),     // ppImmediateContext
+	)
+	if r != 0 {
+		return nil, nil, 0, ErrorCode{Name: "D3D11CreateDevice", Code: uint32(r)}
+	}
+	return dev, ctx, featLvl, nil
+}
+
+func CreateDeviceAndSwapChain(driverType uint32, flags uint32, swapDesc *DXGI_SWAP_CHAIN_DESC) (*Device, *DeviceContext, *IDXGISwapChain, uint32, error) {
+	var (
+		dev     *Device
+		ctx     *DeviceContext
+		swchain *IDXGISwapChain
+		featLvl uint32
+	)
+	r, _, _ := _D3D11CreateDeviceAndSwapChain.Call(
+		0,                                 // pAdapter
+		uintptr(driverType),               // driverType
+		0,                                 // Software
+		uintptr(flags),                    // Flags
+		0,                                 // pFeatureLevels
+		0,                                 // FeatureLevels
+		SDK_VERSION,                       // SDKVersion
+		uintptr(unsafe.Pointer(swapDesc)), // pSwapChainDesc
+		uintptr(unsafe.Pointer(&swchain)), // ppSwapChain
+		uintptr(unsafe.Pointer(&dev)),     // ppDevice
+		uintptr(unsafe.Pointer(&featLvl)), // pFeatureLevel
+		uintptr(unsafe.Pointer(&ctx)),     // ppImmediateContext
+	)
+	if r != 0 {
+		return nil, nil, nil, 0, ErrorCode{Name: "D3D11CreateDeviceAndSwapChain", Code: uint32(r)}
+	}
+	return dev, ctx, swchain, featLvl, nil
+}
+
+func DXGIGetDebugInterface1() (*IDXGIDebug, error) {
+	var dbg *IDXGIDebug
+	r, _, _ := _DXGIGetDebugInterface1.Call(
+		0, // Flags
+		uintptr(unsafe.Pointer(&IID_IDXGIDebug)),
+		uintptr(unsafe.Pointer(&dbg)),
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DXGIGetDebugInterface1", Code: uint32(r)}
+	}
+	return dbg, nil
+}
+
+func ReportLiveObjects() error {
+	dxgi, err := DXGIGetDebugInterface1()
+	if err != nil {
+		return err
+	}
+	defer IUnknownRelease(unsafe.Pointer(dxgi), dxgi.Vtbl.Release)
+	dxgi.ReportLiveObjects(&DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_DETAIL|DXGI_DEBUG_RLO_IGNORE_INTERNAL)
+	return nil
+}
+
+func (d *IDXGIDebug) ReportLiveObjects(guid *GUID, flags uint32) {
+	syscall.Syscall6(
+		d.Vtbl.ReportLiveObjects,
+		3,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(guid)),
+		uintptr(flags),
+		0,
+		0,
+		0,
+	)
+}
+
+func (d *Device) CheckFormatSupport(format uint32) (uint32, error) {
+	var support uint32
+	r, _, _ := syscall.Syscall(
+		d.Vtbl.CheckFormatSupport,
+		3,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(format),
+		uintptr(unsafe.Pointer(&support)),
+	)
+	if r != 0 {
+		return 0, ErrorCode{Name: "DeviceCheckFormatSupport", Code: uint32(r)}
+	}
+	return support, nil
+}
+
+func (d *Device) CreateBuffer(desc *BUFFER_DESC, data []byte) (*Buffer, error) {
+	var dataDesc *SUBRESOURCE_DATA
+	if len(data) > 0 {
+		dataDesc = &SUBRESOURCE_DATA{
+			pSysMem: &data[0],
+		}
+	}
+	var buf *Buffer
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreateBuffer,
+		4,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(desc)),
+		uintptr(unsafe.Pointer(dataDesc)),
+		uintptr(unsafe.Pointer(&buf)),
+		0, 0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateBuffer", Code: uint32(r)}
+	}
+	return buf, nil
+}
+
+func (d *Device) CreateDepthStencilViewTEX2D(res *Resource, desc *DEPTH_STENCIL_VIEW_DESC_TEX2D) (*DepthStencilView, error) {
+	var view *DepthStencilView
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreateDepthStencilView,
+		4,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(res)),
+		uintptr(unsafe.Pointer(desc)),
+		uintptr(unsafe.Pointer(&view)),
+		0, 0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateDepthStencilView", Code: uint32(r)}
+	}
+	return view, nil
+}
+
+func (d *Device) CreatePixelShader(bytecode []byte) (*PixelShader, error) {
+	var shader *PixelShader
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreatePixelShader,
+		5,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(&bytecode[0])),
+		uintptr(len(bytecode)),
+		0, // pClassLinkage
+		uintptr(unsafe.Pointer(&shader)),
+		0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreatePixelShader", Code: uint32(r)}
+	}
+	return shader, nil
+}
+
+func (d *Device) CreateVertexShader(bytecode []byte) (*VertexShader, error) {
+	var shader *VertexShader
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreateVertexShader,
+		5,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(&bytecode[0])),
+		uintptr(len(bytecode)),
+		0, // pClassLinkage
+		uintptr(unsafe.Pointer(&shader)),
+		0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateVertexShader", Code: uint32(r)}
+	}
+	return shader, nil
+}
+
+func (d *Device) CreateComputeShader(bytecode []byte) (*ComputeShader, error) {
+	var shader *ComputeShader
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreateComputeShader,
+		5,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(&bytecode[0])),
+		uintptr(len(bytecode)),
+		0, // pClassLinkage
+		uintptr(unsafe.Pointer(&shader)),
+		0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateComputeShader", Code: uint32(r)}
+	}
+	return shader, nil
+}
+
+func (d *Device) CreateShaderResourceView(res *Resource, desc unsafe.Pointer) (*ShaderResourceView, error) {
+	var resView *ShaderResourceView
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreateShaderResourceView,
+		4,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(res)),
+		uintptr(desc),
+		uintptr(unsafe.Pointer(&resView)),
+		0, 0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateShaderResourceView", Code: uint32(r)}
+	}
+	return resView, nil
+}
+
+func (d *Device) CreateUnorderedAccessView(res *Resource, desc unsafe.Pointer) (*UnorderedAccessView, error) {
+	var uaView *UnorderedAccessView
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreateUnorderedAccessView,
+		4,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(res)),
+		uintptr(desc),
+		uintptr(unsafe.Pointer(&uaView)),
+		0, 0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateUnorderedAccessView", Code: uint32(r)}
+	}
+	return uaView, nil
+}
+
+func (d *Device) CreateRasterizerState(desc *RASTERIZER_DESC) (*RasterizerState, error) {
+	var state *RasterizerState
+	r, _, _ := syscall.Syscall(
+		d.Vtbl.CreateRasterizerState,
+		3,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(desc)),
+		uintptr(unsafe.Pointer(&state)),
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateRasterizerState", Code: uint32(r)}
+	}
+	return state, nil
+}
+
+func (d *Device) CreateInputLayout(descs []INPUT_ELEMENT_DESC, bytecode []byte) (*InputLayout, error) {
+	var pdesc *INPUT_ELEMENT_DESC
+	if len(descs) > 0 {
+		pdesc = &descs[0]
+	}
+	var layout *InputLayout
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreateInputLayout,
+		6,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(pdesc)),
+		uintptr(len(descs)),
+		uintptr(unsafe.Pointer(&bytecode[0])),
+		uintptr(len(bytecode)),
+		uintptr(unsafe.Pointer(&layout)),
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateInputLayout", Code: uint32(r)}
+	}
+	return layout, nil
+}
+
+func (d *Device) CreateSamplerState(desc *SAMPLER_DESC) (*SamplerState, error) {
+	var sampler *SamplerState
+	r, _, _ := syscall.Syscall(
+		d.Vtbl.CreateSamplerState,
+		3,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(desc)),
+		uintptr(unsafe.Pointer(&sampler)),
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateSamplerState", Code: uint32(r)}
+	}
+	return sampler, nil
+}
+
+func (d *Device) CreateTexture2D(desc *TEXTURE2D_DESC) (*Texture2D, error) {
+	var tex *Texture2D
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreateTexture2D,
+		4,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(desc)),
+		0, // pInitialData
+		uintptr(unsafe.Pointer(&tex)),
+		0, 0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "CreateTexture2D", Code: uint32(r)}
+	}
+	return tex, nil
+}
+
+func (d *Device) CreateRenderTargetView(res *Resource) (*RenderTargetView, error) {
+	var target *RenderTargetView
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreateRenderTargetView,
+		4,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(res)),
+		0, // pDesc
+		uintptr(unsafe.Pointer(&target)),
+		0, 0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateRenderTargetView", Code: uint32(r)}
+	}
+	return target, nil
+}
+
+func (d *Device) CreateBlendState(desc *BLEND_DESC) (*BlendState, error) {
+	var state *BlendState
+	r, _, _ := syscall.Syscall(
+		d.Vtbl.CreateBlendState,
+		3,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(desc)),
+		uintptr(unsafe.Pointer(&state)),
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateBlendState", Code: uint32(r)}
+	}
+	return state, nil
+}
+
+func (d *Device) CreateDepthStencilState(desc *DEPTH_STENCIL_DESC) (*DepthStencilState, error) {
+	var state *DepthStencilState
+	r, _, _ := syscall.Syscall(
+		d.Vtbl.CreateDepthStencilState,
+		3,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(desc)),
+		uintptr(unsafe.Pointer(&state)),
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "DeviceCreateDepthStencilState", Code: uint32(r)}
+	}
+	return state, nil
+}
+
+func (d *Device) GetFeatureLevel() int {
+	lvl, _, _ := syscall.Syscall(
+		d.Vtbl.GetFeatureLevel,
+		1,
+		uintptr(unsafe.Pointer(d)),
+		0, 0,
+	)
+	return int(lvl)
+}
+
+func (d *Device) GetImmediateContext() *DeviceContext {
+	var ctx *DeviceContext
+	syscall.Syscall(
+		d.Vtbl.GetImmediateContext,
+		2,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(&ctx)),
+		0,
+	)
+	return ctx
+}
+
+func (d *Device) ReportLiveDeviceObjects() error {
+	intf, err := IUnknownQueryInterface(unsafe.Pointer(d), d.Vtbl.QueryInterface, &IID_ID3D11Debug)
+	if err != nil {
+		return fmt.Errorf("ReportLiveObjects: failed to query ID3D11Debug interface: %v", err)
+	}
+	defer IUnknownRelease(unsafe.Pointer(intf), intf.Vtbl.Release)
+	dbg := (*Debug)(unsafe.Pointer(intf))
+	dbg.ReportLiveDeviceObjects(RLDO_DETAIL | RLDO_IGNORE_INTERNAL)
+	return nil
+}
+
+func (d *Debug) ReportLiveDeviceObjects(flags uint32) {
+	syscall.Syscall(
+		d.Vtbl.ReportLiveDeviceObjects,
+		2,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(flags),
+		0,
+	)
+}
+
+func (s *IDXGISwapChain) GetDesc() (DXGI_SWAP_CHAIN_DESC, error) {
+	var desc DXGI_SWAP_CHAIN_DESC
+	r, _, _ := syscall.Syscall(
+		s.Vtbl.GetDesc,
+		2,
+		uintptr(unsafe.Pointer(s)),
+		uintptr(unsafe.Pointer(&desc)),
+		0,
+	)
+	if r != 0 {
+		return DXGI_SWAP_CHAIN_DESC{}, ErrorCode{Name: "IDXGISwapChainGetDesc", Code: uint32(r)}
+	}
+	return desc, nil
+}
+
+func (s *IDXGISwapChain) ResizeBuffers(buffers, width, height, newFormat, flags uint32) error {
+	r, _, _ := syscall.Syscall6(
+		s.Vtbl.ResizeBuffers,
+		6,
+		uintptr(unsafe.Pointer(s)),
+		uintptr(buffers),
+		uintptr(width),
+		uintptr(height),
+		uintptr(newFormat),
+		uintptr(flags),
+	)
+	if r != 0 {
+		return ErrorCode{Name: "IDXGISwapChainResizeBuffers", Code: uint32(r)}
+	}
+	return nil
+}
+
+func (s *IDXGISwapChain) Present(SyncInterval int, Flags uint32) error {
+	r, _, _ := syscall.Syscall(
+		s.Vtbl.Present,
+		3,
+		uintptr(unsafe.Pointer(s)),
+		uintptr(SyncInterval),
+		uintptr(Flags),
+	)
+	if r != 0 {
+		return ErrorCode{Name: "IDXGISwapChainPresent", Code: uint32(r)}
+	}
+	return nil
+}
+
+func (s *IDXGISwapChain) GetBuffer(index int, riid *GUID) (*IUnknown, error) {
+	var buf *IUnknown
+	r, _, _ := syscall.Syscall6(
+		s.Vtbl.GetBuffer,
+		4,
+		uintptr(unsafe.Pointer(s)),
+		uintptr(index),
+		uintptr(unsafe.Pointer(riid)),
+		uintptr(unsafe.Pointer(&buf)),
+		0,
+		0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "IDXGISwapChainGetBuffer", Code: uint32(r)}
+	}
+	return buf, nil
+}
+
+func (c *DeviceContext) GenerateMips(res *ShaderResourceView) {
+	syscall.Syscall(
+		c.Vtbl.GenerateMips,
+		2,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(res)),
+		0,
+	)
+}
+
+func (c *DeviceContext) Unmap(resource *Resource, subResource uint32) {
+	syscall.Syscall(
+		c.Vtbl.Unmap,
+		3,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(resource)),
+		uintptr(subResource),
+	)
+}
+
+func (c *DeviceContext) Map(resource *Resource, subResource, mapType, mapFlags uint32) (MAPPED_SUBRESOURCE, error) {
+	var resMap MAPPED_SUBRESOURCE
+	r, _, _ := syscall.Syscall6(
+		c.Vtbl.Map,
+		6,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(resource)),
+		uintptr(subResource),
+		uintptr(mapType),
+		uintptr(mapFlags),
+		uintptr(unsafe.Pointer(&resMap)),
+	)
+	if r != 0 {
+		return resMap, ErrorCode{Name: "DeviceContextMap", Code: uint32(r)}
+	}
+	return resMap, nil
+}
+
+func (c *DeviceContext) CopySubresourceRegion(dst *Resource, dstSubresource, dstX, dstY, dstZ uint32, src *Resource, srcSubresource uint32, srcBox *BOX) {
+	syscall.Syscall9(
+		c.Vtbl.CopySubresourceRegion,
+		9,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(dst)),
+		uintptr(dstSubresource),
+		uintptr(dstX),
+		uintptr(dstY),
+		uintptr(dstZ),
+		uintptr(unsafe.Pointer(src)),
+		uintptr(srcSubresource),
+		uintptr(unsafe.Pointer(srcBox)),
+	)
+}
+
+func (c *DeviceContext) ClearDepthStencilView(target *DepthStencilView, flags uint32, depth float32, stencil uint8) {
+	syscall.Syscall6(
+		c.Vtbl.ClearDepthStencilView,
+		5,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(target)),
+		uintptr(flags),
+		uintptr(math.Float32bits(depth)),
+		uintptr(stencil),
+		0,
+	)
+}
+
+func (c *DeviceContext) ClearRenderTargetView(target *RenderTargetView, color *[4]float32) {
+	syscall.Syscall(
+		c.Vtbl.ClearRenderTargetView,
+		3,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(target)),
+		uintptr(unsafe.Pointer(color)),
+	)
+}
+
+func (c *DeviceContext) CSSetShaderResources(startSlot uint32, s *ShaderResourceView) {
+	syscall.Syscall6(
+		c.Vtbl.CSSetShaderResources,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(startSlot),
+		1, // NumViews
+		uintptr(unsafe.Pointer(&s)),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) CSSetUnorderedAccessViews(startSlot uint32, v *UnorderedAccessView) {
+	syscall.Syscall6(
+		c.Vtbl.CSSetUnorderedAccessViews,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(startSlot),
+		1, // NumViews
+		uintptr(unsafe.Pointer(&v)),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) CSSetShader(s *ComputeShader) {
+	syscall.Syscall6(
+		c.Vtbl.CSSetShader,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(s)),
+		0, // ppClassInstances
+		0, // NumClassInstances
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) RSSetViewports(viewport *VIEWPORT) {
+	syscall.Syscall(
+		c.Vtbl.RSSetViewports,
+		3,
+		uintptr(unsafe.Pointer(c)),
+		1, // NumViewports
+		uintptr(unsafe.Pointer(viewport)),
+	)
+}
+
+func (c *DeviceContext) VSSetShader(s *VertexShader) {
+	syscall.Syscall6(
+		c.Vtbl.VSSetShader,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(s)),
+		0, // ppClassInstances
+		0, // NumClassInstances
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) VSSetConstantBuffers(b *Buffer) {
+	syscall.Syscall6(
+		c.Vtbl.VSSetConstantBuffers,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		0, // StartSlot
+		1, // NumBuffers
+		uintptr(unsafe.Pointer(&b)),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) PSSetConstantBuffers(b *Buffer) {
+	syscall.Syscall6(
+		c.Vtbl.PSSetConstantBuffers,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		0, // StartSlot
+		1, // NumBuffers
+		uintptr(unsafe.Pointer(&b)),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) PSSetShaderResources(startSlot uint32, s *ShaderResourceView) {
+	syscall.Syscall6(
+		c.Vtbl.PSSetShaderResources,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(startSlot),
+		1, // NumViews
+		uintptr(unsafe.Pointer(&s)),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) PSSetSamplers(startSlot uint32, s *SamplerState) {
+	syscall.Syscall6(
+		c.Vtbl.PSSetSamplers,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(startSlot),
+		1, // NumSamplers
+		uintptr(unsafe.Pointer(&s)),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) PSSetShader(s *PixelShader) {
+	syscall.Syscall6(
+		c.Vtbl.PSSetShader,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(s)),
+		0, // ppClassInstances
+		0, // NumClassInstances
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) UpdateSubresource(res *Resource, dstBox *BOX, rowPitch, depthPitch uint32, data []byte) {
+	syscall.Syscall9(
+		c.Vtbl.UpdateSubresource,
+		7,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(res)),
+		0, // DstSubresource
+		uintptr(unsafe.Pointer(dstBox)),
+		uintptr(unsafe.Pointer(&data[0])),
+		uintptr(rowPitch),
+		uintptr(depthPitch),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) RSSetState(state *RasterizerState) {
+	syscall.Syscall(
+		c.Vtbl.RSSetState,
+		2,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(state)),
+		0,
+	)
+}
+
+func (c *DeviceContext) IASetInputLayout(layout *InputLayout) {
+	syscall.Syscall(
+		c.Vtbl.IASetInputLayout,
+		2,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(layout)),
+		0,
+	)
+}
+
+func (c *DeviceContext) IASetIndexBuffer(buf *Buffer, format, offset uint32) {
+	syscall.Syscall6(
+		c.Vtbl.IASetIndexBuffer,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(buf)),
+		uintptr(format),
+		uintptr(offset),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) IASetVertexBuffers(buf *Buffer, stride, offset uint32) {
+	syscall.Syscall6(
+		c.Vtbl.IASetVertexBuffers,
+		6,
+		uintptr(unsafe.Pointer(c)),
+		0, // StartSlot
+		1, // NumBuffers,
+		uintptr(unsafe.Pointer(&buf)),
+		uintptr(unsafe.Pointer(&stride)),
+		uintptr(unsafe.Pointer(&offset)),
+	)
+}
+
+func (c *DeviceContext) IASetPrimitiveTopology(mode uint32) {
+	syscall.Syscall(
+		c.Vtbl.IASetPrimitiveTopology,
+		2,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(mode),
+		0,
+	)
+}
+
+func (c *DeviceContext) OMGetRenderTargets() (*RenderTargetView, *DepthStencilView) {
+	var (
+		target           *RenderTargetView
+		depthStencilView *DepthStencilView
+	)
+	syscall.Syscall6(
+		c.Vtbl.OMGetRenderTargets,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		1, // NumViews
+		uintptr(unsafe.Pointer(&target)),
+		uintptr(unsafe.Pointer(&depthStencilView)),
+		0, 0,
+	)
+	return target, depthStencilView
+}
+
+func (c *DeviceContext) OMSetRenderTargets(target *RenderTargetView, depthStencil *DepthStencilView) {
+	syscall.Syscall6(
+		c.Vtbl.OMSetRenderTargets,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		1, // NumViews
+		uintptr(unsafe.Pointer(&target)),
+		uintptr(unsafe.Pointer(depthStencil)),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) Draw(count, start uint32) {
+	syscall.Syscall(
+		c.Vtbl.Draw,
+		3,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(count),
+		uintptr(start),
+	)
+}
+
+func (c *DeviceContext) DrawIndexed(count, start uint32, base int32) {
+	syscall.Syscall6(
+		c.Vtbl.DrawIndexed,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(count),
+		uintptr(start),
+		uintptr(base),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) Dispatch(x, y, z uint32) {
+	syscall.Syscall6(
+		c.Vtbl.Dispatch,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(x),
+		uintptr(y),
+		uintptr(z),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) OMSetBlendState(state *BlendState, factor *f32color.RGBA, sampleMask uint32) {
+	syscall.Syscall6(
+		c.Vtbl.OMSetBlendState,
+		4,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(state)),
+		uintptr(unsafe.Pointer(factor)),
+		uintptr(sampleMask),
+		0, 0,
+	)
+}
+
+func (c *DeviceContext) OMSetDepthStencilState(state *DepthStencilState, stencilRef uint32) {
+	syscall.Syscall(
+		c.Vtbl.OMSetDepthStencilState,
+		3,
+		uintptr(unsafe.Pointer(c)),
+		uintptr(unsafe.Pointer(state)),
+		uintptr(stencilRef),
+	)
+}
+
+func (d *IDXGIObject) GetParent(guid *GUID) (*IDXGIObject, error) {
+	var parent *IDXGIObject
+	r, _, _ := syscall.Syscall(
+		d.Vtbl.GetParent,
+		3,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(guid)),
+		uintptr(unsafe.Pointer(&parent)),
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "IDXGIObjectGetParent", Code: uint32(r)}
+	}
+	return parent, nil
+}
+
+func (d *IDXGIFactory) CreateSwapChain(device *IUnknown, desc *DXGI_SWAP_CHAIN_DESC) (*IDXGISwapChain, error) {
+	var swchain *IDXGISwapChain
+	r, _, _ := syscall.Syscall6(
+		d.Vtbl.CreateSwapChain,
+		4,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(device)),
+		uintptr(unsafe.Pointer(desc)),
+		uintptr(unsafe.Pointer(&swchain)),
+		0, 0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "IDXGIFactory", Code: uint32(r)}
+	}
+	return swchain, nil
+}
+
+func (d *IDXGIDevice) GetAdapter() (*IDXGIAdapter, error) {
+	var adapter *IDXGIAdapter
+	r, _, _ := syscall.Syscall(
+		d.Vtbl.GetAdapter,
+		2,
+		uintptr(unsafe.Pointer(d)),
+		uintptr(unsafe.Pointer(&adapter)),
+		0,
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "IDXGIDeviceGetAdapter", Code: uint32(r)}
+	}
+	return adapter, nil
+}
+
+func IUnknownQueryInterface(obj unsafe.Pointer, queryInterfaceMethod uintptr, guid *GUID) (*IUnknown, error) {
+	var ref *IUnknown
+	r, _, _ := syscall.Syscall(
+		queryInterfaceMethod,
+		3,
+		uintptr(obj),
+		uintptr(unsafe.Pointer(guid)),
+		uintptr(unsafe.Pointer(&ref)),
+	)
+	if r != 0 {
+		return nil, ErrorCode{Name: "IUnknownQueryInterface", Code: uint32(r)}
+	}
+	return ref, nil
+}
+
+func IUnknownAddRef(obj unsafe.Pointer, addRefMethod uintptr) {
+	syscall.Syscall(
+		addRefMethod,
+		1,
+		uintptr(obj),
+		0,
+		0,
+	)
+}
+
+func IUnknownRelease(obj unsafe.Pointer, releaseMethod uintptr) {
+	syscall.Syscall(
+		releaseMethod,
+		1,
+		uintptr(obj),
+		0,
+		0,
+	)
+}
+
+func (e ErrorCode) Error() string {
+	return fmt.Sprintf("%s: %#x", e.Name, e.Code)
+}
+
+func CreateSwapChain(dev *Device, hwnd windows.Handle) (*IDXGISwapChain, error) {
+	dxgiDev, err := IUnknownQueryInterface(unsafe.Pointer(dev), dev.Vtbl.QueryInterface, &IID_IDXGIDevice)
+	if err != nil {
+		return nil, fmt.Errorf("NewContext: %v", err)
+	}
+	adapter, err := (*IDXGIDevice)(unsafe.Pointer(dxgiDev)).GetAdapter()
+	IUnknownRelease(unsafe.Pointer(dxgiDev), dxgiDev.Vtbl.Release)
+	if err != nil {
+		return nil, fmt.Errorf("NewContext: %v", err)
+	}
+	dxgiFactory, err := (*IDXGIObject)(unsafe.Pointer(adapter)).GetParent(&IID_IDXGIFactory)
+	IUnknownRelease(unsafe.Pointer(adapter), adapter.Vtbl.Release)
+	if err != nil {
+		return nil, fmt.Errorf("NewContext: %v", err)
+	}
+	swchain, err := (*IDXGIFactory)(unsafe.Pointer(dxgiFactory)).CreateSwapChain(
+		(*IUnknown)(unsafe.Pointer(dev)),
+		&DXGI_SWAP_CHAIN_DESC{
+			BufferDesc: DXGI_MODE_DESC{
+				Format: DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
+			},
+			SampleDesc: DXGI_SAMPLE_DESC{
+				Count: 1,
+			},
+			BufferUsage:  DXGI_USAGE_RENDER_TARGET_OUTPUT,
+			BufferCount:  1,
+			OutputWindow: hwnd,
+			Windowed:     1,
+			SwapEffect:   DXGI_SWAP_EFFECT_DISCARD,
+		},
+	)
+	IUnknownRelease(unsafe.Pointer(dxgiFactory), dxgiFactory.Vtbl.Release)
+	if err != nil {
+		return nil, fmt.Errorf("NewContext: %v", err)
+	}
+	return swchain, nil
+}
+
+func CreateDepthView(d *Device, width, height, depthBits int) (*DepthStencilView, error) {
+	depthTex, err := d.CreateTexture2D(&TEXTURE2D_DESC{
+		Width:     uint32(width),
+		Height:    uint32(height),
+		MipLevels: 1,
+		ArraySize: 1,
+		Format:    DXGI_FORMAT_D24_UNORM_S8_UINT,
+		SampleDesc: DXGI_SAMPLE_DESC{
+			Count:   1,
+			Quality: 0,
+		},
+		BindFlags: BIND_DEPTH_STENCIL,
+	})
+	if err != nil {
+		return nil, err
+	}
+	depthView, err := d.CreateDepthStencilViewTEX2D(
+		(*Resource)(unsafe.Pointer(depthTex)),
+		&DEPTH_STENCIL_VIEW_DESC_TEX2D{
+			Format:        DXGI_FORMAT_D24_UNORM_S8_UINT,
+			ViewDimension: DSV_DIMENSION_TEXTURE2D,
+		},
+	)
+	IUnknownRelease(unsafe.Pointer(depthTex), depthTex.Vtbl.Release)
+	return depthView, err
+}

+ 57 - 0
vendor/gioui.org/internal/debug/debug.go

@@ -0,0 +1,57 @@
+// Package debug provides general debug feature management for Gio, including
+// the ability to toggle debug features using the GIODEBUG environment variable.
+package debug
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"sync"
+	"sync/atomic"
+)
+
+const (
+	debugVariable = "GIODEBUG"
+	textSubsystem = "text"
+	silentFeature = "silent"
+)
+
+// Text controls whether the text subsystem has debug logging enabled.
+var Text atomic.Bool
+
+var parseOnce sync.Once
+
+// Parse processes the current value of GIODEBUG. If it is unset, it does nothing.
+// Otherwise it process its value, printing usage info the stderr if the value is
+// not understood. Parse will be automatically invoked when the first application
+// window is created, allowing applications to manipulate GIODEBUG programmatically
+// before it is parsed.
+func Parse() {
+	parseOnce.Do(func() {
+		val, ok := os.LookupEnv(debugVariable)
+		if !ok {
+			return
+		}
+		print := false
+		silent := false
+		for _, part := range strings.Split(val, ",") {
+			switch part {
+			case textSubsystem:
+				Text.Store(true)
+			case silentFeature:
+				silent = true
+			default:
+				print = true
+			}
+		}
+		if print && !silent {
+			fmt.Fprintf(os.Stderr,
+				`Usage of %s:
+	A comma-delimited list of debug subsystems to enable. Currently recognized systems:
+
+	- %s: text debug info including system font resolution
+	- %s: silence this usage message even if GIODEBUG contains invalid content
+`, debugVariable, textSubsystem, silentFeature)
+		}
+	})
+}

+ 251 - 0
vendor/gioui.org/internal/egl/egl.go

@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build linux || windows || freebsd || openbsd
+// +build linux windows freebsd openbsd
+
+package egl
+
+import (
+	"errors"
+	"fmt"
+	"runtime"
+	"strings"
+
+	"gioui.org/gpu"
+)
+
+type Context struct {
+	disp    _EGLDisplay
+	eglCtx  *eglContext
+	eglSurf _EGLSurface
+}
+
+type eglContext struct {
+	config      _EGLConfig
+	ctx         _EGLContext
+	visualID    int
+	srgb        bool
+	surfaceless bool
+}
+
+var (
+	nilEGLDisplay       _EGLDisplay
+	nilEGLSurface       _EGLSurface
+	nilEGLContext       _EGLContext
+	nilEGLConfig        _EGLConfig
+	EGL_DEFAULT_DISPLAY NativeDisplayType
+)
+
+const (
+	_EGL_ALPHA_SIZE             = 0x3021
+	_EGL_BLUE_SIZE              = 0x3022
+	_EGL_CONFIG_CAVEAT          = 0x3027
+	_EGL_CONTEXT_CLIENT_VERSION = 0x3098
+	_EGL_DEPTH_SIZE             = 0x3025
+	_EGL_GL_COLORSPACE_KHR      = 0x309d
+	_EGL_GL_COLORSPACE_SRGB_KHR = 0x3089
+	_EGL_GREEN_SIZE             = 0x3023
+	_EGL_EXTENSIONS             = 0x3055
+	_EGL_NATIVE_VISUAL_ID       = 0x302e
+	_EGL_NONE                   = 0x3038
+	_EGL_OPENGL_ES2_BIT         = 0x4
+	_EGL_RED_SIZE               = 0x3024
+	_EGL_RENDERABLE_TYPE        = 0x3040
+	_EGL_SURFACE_TYPE           = 0x3033
+	_EGL_WINDOW_BIT             = 0x4
+)
+
+func (c *Context) Release() {
+	c.ReleaseSurface()
+	if c.eglCtx != nil {
+		eglDestroyContext(c.disp, c.eglCtx.ctx)
+		c.eglCtx = nil
+	}
+	eglTerminate(c.disp)
+	c.disp = nilEGLDisplay
+}
+
+func (c *Context) Present() error {
+	if !eglSwapBuffers(c.disp, c.eglSurf) {
+		return fmt.Errorf("eglSwapBuffers failed (%x)", eglGetError())
+	}
+	return nil
+}
+
+func NewContext(disp NativeDisplayType) (*Context, error) {
+	if err := loadEGL(); err != nil {
+		return nil, err
+	}
+	eglDisp := eglGetDisplay(disp)
+	// eglGetDisplay can return EGL_NO_DISPLAY yet no error
+	// (EGL_SUCCESS), in which case a default EGL display might be
+	// available.
+	if eglDisp == nilEGLDisplay {
+		eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY)
+	}
+	if eglDisp == nilEGLDisplay {
+		return nil, fmt.Errorf("eglGetDisplay failed: 0x%x", eglGetError())
+	}
+	eglCtx, err := createContext(eglDisp)
+	if err != nil {
+		return nil, err
+	}
+	c := &Context{
+		disp:   eglDisp,
+		eglCtx: eglCtx,
+	}
+	return c, nil
+}
+
+func (c *Context) RenderTarget() (gpu.RenderTarget, error) {
+	return gpu.OpenGLRenderTarget{}, nil
+}
+
+func (c *Context) API() gpu.API {
+	return gpu.OpenGL{}
+}
+
+func (c *Context) ReleaseSurface() {
+	if c.eglSurf == nilEGLSurface {
+		return
+	}
+	// Make sure any in-flight GL commands are complete.
+	eglWaitClient()
+	c.ReleaseCurrent()
+	eglDestroySurface(c.disp, c.eglSurf)
+	c.eglSurf = nilEGLSurface
+}
+
+func (c *Context) VisualID() int {
+	return c.eglCtx.visualID
+}
+
+func (c *Context) CreateSurface(win NativeWindowType) error {
+	eglSurf, err := createSurface(c.disp, c.eglCtx, win)
+	c.eglSurf = eglSurf
+	return err
+}
+
+func (c *Context) ReleaseCurrent() {
+	if c.disp != nilEGLDisplay {
+		eglMakeCurrent(c.disp, nilEGLSurface, nilEGLSurface, nilEGLContext)
+	}
+}
+
+func (c *Context) MakeCurrent() error {
+	// OpenGL contexts are implicit and thread-local. Lock the OS thread.
+	runtime.LockOSThread()
+
+	if c.eglSurf == nilEGLSurface && !c.eglCtx.surfaceless {
+		return errors.New("no surface created yet EGL_KHR_surfaceless_context is not supported")
+	}
+	if !eglMakeCurrent(c.disp, c.eglSurf, c.eglSurf, c.eglCtx.ctx) {
+		return fmt.Errorf("eglMakeCurrent error 0x%x", eglGetError())
+	}
+	return nil
+}
+
+func (c *Context) EnableVSync(enable bool) {
+	if enable {
+		eglSwapInterval(c.disp, 1)
+	} else {
+		eglSwapInterval(c.disp, 0)
+	}
+}
+
+func hasExtension(exts []string, ext string) bool {
+	for _, e := range exts {
+		if ext == e {
+			return true
+		}
+	}
+	return false
+}
+
+func createContext(disp _EGLDisplay) (*eglContext, error) {
+	major, minor, ret := eglInitialize(disp)
+	if !ret {
+		return nil, fmt.Errorf("eglInitialize failed: 0x%x", eglGetError())
+	}
+	// sRGB framebuffer support on EGL 1.5 or if EGL_KHR_gl_colorspace is supported.
+	exts := strings.Split(eglQueryString(disp, _EGL_EXTENSIONS), " ")
+	srgb := major > 1 || minor >= 5 || hasExtension(exts, "EGL_KHR_gl_colorspace")
+	attribs := []_EGLint{
+		_EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT,
+		_EGL_SURFACE_TYPE, _EGL_WINDOW_BIT,
+		_EGL_BLUE_SIZE, 8,
+		_EGL_GREEN_SIZE, 8,
+		_EGL_RED_SIZE, 8,
+		_EGL_CONFIG_CAVEAT, _EGL_NONE,
+	}
+	if srgb {
+		if runtime.GOOS == "linux" || runtime.GOOS == "android" {
+			// Some Mesa drivers crash if an sRGB framebuffer is requested without alpha.
+			// https://bugs.freedesktop.org/show_bug.cgi?id=107782.
+			//
+			// Also, some Android devices (Samsung S9) need alpha for sRGB to work.
+			attribs = append(attribs, _EGL_ALPHA_SIZE, 8)
+		}
+	}
+	attribs = append(attribs, _EGL_NONE)
+	eglCfg, ret := eglChooseConfig(disp, attribs)
+	if !ret {
+		return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError())
+	}
+	if eglCfg == nilEGLConfig {
+		supportsNoCfg := hasExtension(exts, "EGL_KHR_no_config_context")
+		if !supportsNoCfg {
+			return nil, errors.New("eglChooseConfig returned no configs")
+		}
+	}
+	var visID _EGLint
+	if eglCfg != nilEGLConfig {
+		var ok bool
+		visID, ok = eglGetConfigAttrib(disp, eglCfg, _EGL_NATIVE_VISUAL_ID)
+		if !ok {
+			return nil, errors.New("newContext: eglGetConfigAttrib for _EGL_NATIVE_VISUAL_ID failed")
+		}
+	}
+	ctxAttribs := []_EGLint{
+		_EGL_CONTEXT_CLIENT_VERSION, 3,
+		_EGL_NONE,
+	}
+	eglCtx := eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs)
+	if eglCtx == nilEGLContext {
+		// Fall back to OpenGL ES 2 and rely on extensions.
+		ctxAttribs := []_EGLint{
+			_EGL_CONTEXT_CLIENT_VERSION, 2,
+			_EGL_NONE,
+		}
+		eglCtx = eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs)
+		if eglCtx == nilEGLContext {
+			return nil, fmt.Errorf("eglCreateContext failed: 0x%x", eglGetError())
+		}
+	}
+	return &eglContext{
+		config:      _EGLConfig(eglCfg),
+		ctx:         _EGLContext(eglCtx),
+		visualID:    int(visID),
+		srgb:        srgb,
+		surfaceless: hasExtension(exts, "EGL_KHR_surfaceless_context"),
+	}, nil
+}
+
+func createSurface(disp _EGLDisplay, eglCtx *eglContext, win NativeWindowType) (_EGLSurface, error) {
+	var surfAttribs []_EGLint
+	if eglCtx.srgb {
+		surfAttribs = append(surfAttribs, _EGL_GL_COLORSPACE_KHR, _EGL_GL_COLORSPACE_SRGB_KHR)
+	}
+	surfAttribs = append(surfAttribs, _EGL_NONE)
+	eglSurf := eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs)
+	if eglSurf == nilEGLSurface && eglCtx.srgb {
+		// Try again without sRGB.
+		eglCtx.srgb = false
+		surfAttribs = []_EGLint{_EGL_NONE}
+		eglSurf = eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs)
+	}
+	if eglSurf == nilEGLSurface {
+		return nilEGLSurface, fmt.Errorf("newContext: eglCreateWindowSurface failed 0x%x (sRGB=%v)", eglGetError(), eglCtx.srgb)
+	}
+	return eglSurf, nil
+}

+ 109 - 0
vendor/gioui.org/internal/egl/egl_unix.go

@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build linux || freebsd || openbsd
+// +build linux freebsd openbsd
+
+package egl
+
+/*
+#cgo linux,!android  pkg-config: egl
+#cgo freebsd openbsd android LDFLAGS: -lEGL
+#cgo freebsd CFLAGS: -I/usr/local/include
+#cgo freebsd LDFLAGS: -L/usr/local/lib
+#cgo openbsd CFLAGS: -I/usr/X11R6/include
+#cgo openbsd LDFLAGS: -L/usr/X11R6/lib
+#cgo CFLAGS: -DEGL_NO_X11
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+*/
+import "C"
+
+type (
+	_EGLint           = C.EGLint
+	_EGLDisplay       = C.EGLDisplay
+	_EGLConfig        = C.EGLConfig
+	_EGLContext       = C.EGLContext
+	_EGLSurface       = C.EGLSurface
+	NativeDisplayType = C.EGLNativeDisplayType
+	NativeWindowType  = C.EGLNativeWindowType
+)
+
+func loadEGL() error {
+	return nil
+}
+
+func eglChooseConfig(disp _EGLDisplay, attribs []_EGLint) (_EGLConfig, bool) {
+	var cfg C.EGLConfig
+	var ncfg C.EGLint
+	if C.eglChooseConfig(disp, &attribs[0], &cfg, 1, &ncfg) != C.EGL_TRUE {
+		return nilEGLConfig, false
+	}
+	return _EGLConfig(cfg), true
+}
+
+func eglCreateContext(disp _EGLDisplay, cfg _EGLConfig, shareCtx _EGLContext, attribs []_EGLint) _EGLContext {
+	ctx := C.eglCreateContext(disp, cfg, shareCtx, &attribs[0])
+	return _EGLContext(ctx)
+}
+
+func eglDestroySurface(disp _EGLDisplay, surf _EGLSurface) bool {
+	return C.eglDestroySurface(disp, surf) == C.EGL_TRUE
+}
+
+func eglDestroyContext(disp _EGLDisplay, ctx _EGLContext) bool {
+	return C.eglDestroyContext(disp, ctx) == C.EGL_TRUE
+}
+
+func eglGetConfigAttrib(disp _EGLDisplay, cfg _EGLConfig, attr _EGLint) (_EGLint, bool) {
+	var val _EGLint
+	ret := C.eglGetConfigAttrib(disp, cfg, attr, &val)
+	return val, ret == C.EGL_TRUE
+}
+
+func eglGetError() _EGLint {
+	return C.eglGetError()
+}
+
+func eglInitialize(disp _EGLDisplay) (_EGLint, _EGLint, bool) {
+	var maj, min _EGLint
+	ret := C.eglInitialize(disp, &maj, &min)
+	return maj, min, ret == C.EGL_TRUE
+}
+
+func eglMakeCurrent(disp _EGLDisplay, draw, read _EGLSurface, ctx _EGLContext) bool {
+	return C.eglMakeCurrent(disp, draw, read, ctx) == C.EGL_TRUE
+}
+
+func eglReleaseThread() bool {
+	return C.eglReleaseThread() == C.EGL_TRUE
+}
+
+func eglSwapBuffers(disp _EGLDisplay, surf _EGLSurface) bool {
+	return C.eglSwapBuffers(disp, surf) == C.EGL_TRUE
+}
+
+func eglSwapInterval(disp _EGLDisplay, interval _EGLint) bool {
+	return C.eglSwapInterval(disp, interval) == C.EGL_TRUE
+}
+
+func eglTerminate(disp _EGLDisplay) bool {
+	return C.eglTerminate(disp) == C.EGL_TRUE
+}
+
+func eglQueryString(disp _EGLDisplay, name _EGLint) string {
+	return C.GoString(C.eglQueryString(disp, name))
+}
+
+func eglGetDisplay(disp NativeDisplayType) _EGLDisplay {
+	return C.eglGetDisplay(disp)
+}
+
+func eglCreateWindowSurface(disp _EGLDisplay, conf _EGLConfig, win NativeWindowType, attribs []_EGLint) _EGLSurface {
+	eglSurf := C.eglCreateWindowSurface(disp, conf, win, &attribs[0])
+	return eglSurf
+}
+
+func eglWaitClient() bool {
+	return C.eglWaitClient() == C.EGL_TRUE
+}

+ 191 - 0
vendor/gioui.org/internal/egl/egl_windows.go

@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package egl
+
+import (
+	"fmt"
+	"runtime"
+	"sync"
+	"unsafe"
+
+	syscall "golang.org/x/sys/windows"
+)
+
+type (
+	_EGLint           int32
+	_EGLDisplay       uintptr
+	_EGLConfig        uintptr
+	_EGLContext       uintptr
+	_EGLSurface       uintptr
+	NativeDisplayType uintptr
+	NativeWindowType  uintptr
+)
+
+var (
+	libEGL                  = syscall.DLL{}
+	_eglChooseConfig        *syscall.Proc
+	_eglCreateContext       *syscall.Proc
+	_eglCreateWindowSurface *syscall.Proc
+	_eglDestroyContext      *syscall.Proc
+	_eglDestroySurface      *syscall.Proc
+	_eglGetConfigAttrib     *syscall.Proc
+	_eglGetDisplay          *syscall.Proc
+	_eglGetError            *syscall.Proc
+	_eglInitialize          *syscall.Proc
+	_eglMakeCurrent         *syscall.Proc
+	_eglReleaseThread       *syscall.Proc
+	_eglSwapInterval        *syscall.Proc
+	_eglSwapBuffers         *syscall.Proc
+	_eglTerminate           *syscall.Proc
+	_eglQueryString         *syscall.Proc
+	_eglWaitClient          *syscall.Proc
+)
+
+var loadOnce sync.Once
+
+func loadEGL() error {
+	var err error
+	loadOnce.Do(func() {
+		err = loadDLLs()
+	})
+	return err
+}
+
+func loadDLLs() error {
+	if err := loadDLL(&libEGL, "libEGL.dll"); err != nil {
+		return err
+	}
+
+	procs := map[string]**syscall.Proc{
+		"eglChooseConfig":        &_eglChooseConfig,
+		"eglCreateContext":       &_eglCreateContext,
+		"eglCreateWindowSurface": &_eglCreateWindowSurface,
+		"eglDestroyContext":      &_eglDestroyContext,
+		"eglDestroySurface":      &_eglDestroySurface,
+		"eglGetConfigAttrib":     &_eglGetConfigAttrib,
+		"eglGetDisplay":          &_eglGetDisplay,
+		"eglGetError":            &_eglGetError,
+		"eglInitialize":          &_eglInitialize,
+		"eglMakeCurrent":         &_eglMakeCurrent,
+		"eglReleaseThread":       &_eglReleaseThread,
+		"eglSwapInterval":        &_eglSwapInterval,
+		"eglSwapBuffers":         &_eglSwapBuffers,
+		"eglTerminate":           &_eglTerminate,
+		"eglQueryString":         &_eglQueryString,
+		"eglWaitClient":          &_eglWaitClient,
+	}
+	for name, proc := range procs {
+		p, err := libEGL.FindProc(name)
+		if err != nil {
+			return fmt.Errorf("failed to locate %s in %s: %w", name, libEGL.Name, err)
+		}
+		*proc = p
+	}
+	return nil
+}
+
+func loadDLL(dll *syscall.DLL, name string) error {
+	handle, err := syscall.LoadLibraryEx(name, 0, syscall.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
+	if err != nil {
+		return fmt.Errorf("egl: failed to load %s: %v", name, err)
+	}
+	dll.Handle = handle
+	dll.Name = name
+	return nil
+}
+
+func eglChooseConfig(disp _EGLDisplay, attribs []_EGLint) (_EGLConfig, bool) {
+	var cfg _EGLConfig
+	var ncfg _EGLint
+	a := &attribs[0]
+	r, _, _ := _eglChooseConfig.Call(uintptr(disp), uintptr(unsafe.Pointer(a)), uintptr(unsafe.Pointer(&cfg)), 1, uintptr(unsafe.Pointer(&ncfg)))
+	issue34474KeepAlive(a)
+	return cfg, r != 0
+}
+
+func eglCreateContext(disp _EGLDisplay, cfg _EGLConfig, shareCtx _EGLContext, attribs []_EGLint) _EGLContext {
+	a := &attribs[0]
+	c, _, _ := _eglCreateContext.Call(uintptr(disp), uintptr(cfg), uintptr(shareCtx), uintptr(unsafe.Pointer(a)))
+	issue34474KeepAlive(a)
+	return _EGLContext(c)
+}
+
+func eglCreateWindowSurface(disp _EGLDisplay, cfg _EGLConfig, win NativeWindowType, attribs []_EGLint) _EGLSurface {
+	a := &attribs[0]
+	s, _, _ := _eglCreateWindowSurface.Call(uintptr(disp), uintptr(cfg), uintptr(win), uintptr(unsafe.Pointer(a)))
+	issue34474KeepAlive(a)
+	return _EGLSurface(s)
+}
+
+func eglDestroySurface(disp _EGLDisplay, surf _EGLSurface) bool {
+	r, _, _ := _eglDestroySurface.Call(uintptr(disp), uintptr(surf))
+	return r != 0
+}
+
+func eglDestroyContext(disp _EGLDisplay, ctx _EGLContext) bool {
+	r, _, _ := _eglDestroyContext.Call(uintptr(disp), uintptr(ctx))
+	return r != 0
+}
+
+func eglGetConfigAttrib(disp _EGLDisplay, cfg _EGLConfig, attr _EGLint) (_EGLint, bool) {
+	var val uintptr
+	r, _, _ := _eglGetConfigAttrib.Call(uintptr(disp), uintptr(cfg), uintptr(attr), uintptr(unsafe.Pointer(&val)))
+	return _EGLint(val), r != 0
+}
+
+func eglGetDisplay(disp NativeDisplayType) _EGLDisplay {
+	d, _, _ := _eglGetDisplay.Call(uintptr(disp))
+	return _EGLDisplay(d)
+}
+
+func eglGetError() _EGLint {
+	e, _, _ := _eglGetError.Call()
+	return _EGLint(e)
+}
+
+func eglInitialize(disp _EGLDisplay) (_EGLint, _EGLint, bool) {
+	var maj, min uintptr
+	r, _, _ := _eglInitialize.Call(uintptr(disp), uintptr(unsafe.Pointer(&maj)), uintptr(unsafe.Pointer(&min)))
+	return _EGLint(maj), _EGLint(min), r != 0
+}
+
+func eglMakeCurrent(disp _EGLDisplay, draw, read _EGLSurface, ctx _EGLContext) bool {
+	r, _, _ := _eglMakeCurrent.Call(uintptr(disp), uintptr(draw), uintptr(read), uintptr(ctx))
+	return r != 0
+}
+
+func eglReleaseThread() bool {
+	r, _, _ := _eglReleaseThread.Call()
+	return r != 0
+}
+
+func eglSwapInterval(disp _EGLDisplay, interval _EGLint) bool {
+	r, _, _ := _eglSwapInterval.Call(uintptr(disp), uintptr(interval))
+	return r != 0
+}
+
+func eglSwapBuffers(disp _EGLDisplay, surf _EGLSurface) bool {
+	r, _, _ := _eglSwapBuffers.Call(uintptr(disp), uintptr(surf))
+	return r != 0
+}
+
+func eglTerminate(disp _EGLDisplay) bool {
+	r, _, _ := _eglTerminate.Call(uintptr(disp))
+	return r != 0
+}
+
+func eglQueryString(disp _EGLDisplay, name _EGLint) string {
+	r, _, _ := _eglQueryString.Call(uintptr(disp), uintptr(name))
+	return syscall.BytePtrToString((*byte)(unsafe.Pointer(r)))
+}
+
+func eglWaitClient() bool {
+	r, _, _ := _eglWaitClient.Call()
+	return r != 0
+}
+
+// issue34474KeepAlive calls runtime.KeepAlive as a
+// workaround for golang.org/issue/34474.
+func issue34474KeepAlive(v interface{}) {
+	runtime.KeepAlive(v)
+}

+ 175 - 0
vendor/gioui.org/internal/f32/f32.go

@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+/*
+Package f32 is an internal version of the public package f32 with
+extra types for internal use.
+*/
+package f32
+
+import (
+	"image"
+	"math"
+
+	"gioui.org/f32"
+)
+
+type Point = f32.Point
+
+type Affine2D = f32.Affine2D
+
+var NewAffine2D = f32.NewAffine2D
+
+// 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}.
+var Pt = f32.Pt
+
+// 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},
+	}
+}
+
+// Round returns the smallest integer rectangle that
+// contains r.
+func (r Rectangle) Round() 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)),
+		},
+	}
+}
+
+// fRect converts a rectangle to a f32internal.Rectangle.
+func FRect(r image.Rectangle) Rectangle {
+	return Rectangle{
+		Min: FPt(r.Min), Max: FPt(r.Max),
+	}
+}
+
+// Fpt converts an point to a f32.Point.
+func FPt(p image.Point) Point {
+	return Point{
+		X: float32(p.X), Y: float32(p.Y),
+	}
+}
+
+func ceil(v float32) int {
+	return int(math.Ceil(float64(v)))
+}
+
+func floor(v float32) int {
+	return int(math.Floor(float64(v)))
+}

+ 191 - 0
vendor/gioui.org/internal/f32color/rgba.go

@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package f32color
+
+import (
+	"image/color"
+	"math"
+)
+
+//go:generate go run ./f32colorgen -out tables.go
+
+// RGBA is a 32 bit floating point linear premultiplied color space.
+type RGBA struct {
+	R, G, B, A float32
+}
+
+// Array returns rgba values in a [4]float32 array.
+func (rgba RGBA) Array() [4]float32 {
+	return [4]float32{rgba.R, rgba.G, rgba.B, rgba.A}
+}
+
+// Float32 returns r, g, b, a values.
+func (col RGBA) Float32() (r, g, b, a float32) {
+	return col.R, col.G, col.B, col.A
+}
+
+// SRGBA converts from linear to sRGB color space.
+func (col RGBA) SRGB() color.NRGBA {
+	if col.A == 0 {
+		return color.NRGBA{}
+	}
+	return color.NRGBA{
+		R: uint8(linearTosRGB(col.R/col.A)*255 + .5),
+		G: uint8(linearTosRGB(col.G/col.A)*255 + .5),
+		B: uint8(linearTosRGB(col.B/col.A)*255 + .5),
+		A: uint8(col.A*255 + .5),
+	}
+}
+
+// Luminance calculates the relative luminance of a linear RGBA color.
+// Normalized to 0 for black and 1 for white.
+//
+// See https://www.w3.org/TR/WCAG20/#relativeluminancedef for more details
+func (col RGBA) Luminance() float32 {
+	return 0.2126*col.R + 0.7152*col.G + 0.0722*col.B
+}
+
+// Opaque returns the color without alpha component.
+func (col RGBA) Opaque() RGBA {
+	col.A = 1.0
+	return col
+}
+
+// LinearFromSRGB converts from col in the sRGB colorspace to RGBA.
+func LinearFromSRGB(col color.NRGBA) RGBA {
+	af := float32(col.A) / 0xFF
+	return RGBA{
+		R: srgb8ToLinear[col.R] * af, // sRGBToLinear(float32(col.R)/0xff) * af,
+		G: srgb8ToLinear[col.G] * af, // sRGBToLinear(float32(col.G)/0xff) * af,
+		B: srgb8ToLinear[col.B] * af, // sRGBToLinear(float32(col.B)/0xff) * af,
+		A: af,
+	}
+}
+
+// NRGBAToRGBA converts from non-premultiplied sRGB color to premultiplied sRGB color.
+//
+// Each component in the result is `sRGBToLinear(c * alpha)`, where `c`
+// is the linear color.
+func NRGBAToRGBA(col color.NRGBA) color.RGBA {
+	if col.A == 0xFF {
+		return color.RGBA(col)
+	}
+	c := LinearFromSRGB(col)
+	return color.RGBA{
+		R: uint8(linearTosRGB(c.R)*255 + .5),
+		G: uint8(linearTosRGB(c.G)*255 + .5),
+		B: uint8(linearTosRGB(c.B)*255 + .5),
+		A: col.A,
+	}
+}
+
+// NRGBAToLinearRGBA converts from non-premultiplied sRGB color to premultiplied linear RGBA color.
+//
+// Each component in the result is `c * alpha`, where `c` is the linear color.
+func NRGBAToLinearRGBA(col color.NRGBA) color.RGBA {
+	if col.A == 0xFF {
+		return color.RGBA(col)
+	}
+	c := LinearFromSRGB(col)
+	return color.RGBA{
+		R: uint8(c.R*255 + .5),
+		G: uint8(c.G*255 + .5),
+		B: uint8(c.B*255 + .5),
+		A: col.A,
+	}
+}
+
+// RGBAToNRGBA converts from premultiplied sRGB color to non-premultiplied sRGB color.
+func RGBAToNRGBA(col color.RGBA) color.NRGBA {
+	if col.A == 0xFF {
+		return color.NRGBA(col)
+	}
+
+	linear := RGBA{
+		R: sRGBToLinear(float32(col.R) / 0xff),
+		G: sRGBToLinear(float32(col.G) / 0xff),
+		B: sRGBToLinear(float32(col.B) / 0xff),
+		A: float32(col.A) / 0xff,
+	}
+
+	return linear.SRGB()
+}
+
+// linearTosRGB transforms color value from linear to sRGB.
+func linearTosRGB(c float32) float32 {
+	// Formula from EXT_sRGB.
+	switch {
+	case c <= 0:
+		return 0
+	case 0 < c && c < 0.0031308:
+		return 12.92 * c
+	case 0.0031308 <= c && c < 1:
+		return 1.055*float32(math.Pow(float64(c), 0.41666)) - 0.055
+	}
+
+	return 1
+}
+
+// sRGBToLinear transforms color value from sRGB to linear.
+func sRGBToLinear(c float32) float32 {
+	// Formula from EXT_sRGB.
+	if c <= 0.04045 {
+		return c / 12.92
+	} else {
+		return float32(math.Pow(float64((c+0.055)/1.055), 2.4))
+	}
+}
+
+// MulAlpha applies the alpha to the color.
+func MulAlpha(c color.NRGBA, alpha uint8) color.NRGBA {
+	c.A = uint8(uint32(c.A) * uint32(alpha) / 0xFF)
+	return c
+}
+
+// Disabled blends color towards the luminance and multiplies alpha.
+// Blending towards luminance will desaturate the color.
+// Multiplying alpha blends the color together more with the background.
+func Disabled(c color.NRGBA) (d color.NRGBA) {
+	const r = 80 // blend ratio
+	lum := approxLuminance(c)
+	d = mix(c, color.NRGBA{A: c.A, R: lum, G: lum, B: lum}, r)
+	d = MulAlpha(d, 128+32)
+	return
+}
+
+// Hovered blends dark colors towards white, and light colors towards
+// black. It is approximate because it operates in non-linear sRGB space.
+func Hovered(c color.NRGBA) (h color.NRGBA) {
+	if c.A == 0 {
+		// Provide a reasonable default for transparent widgets.
+		return color.NRGBA{A: 0x44, R: 0x88, G: 0x88, B: 0x88}
+	}
+	const ratio = 0x20
+	m := color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: c.A}
+	if approxLuminance(c) > 128 {
+		m = color.NRGBA{A: c.A}
+	}
+	return mix(m, c, ratio)
+}
+
+// mix mixes c1 and c2 weighted by (1 - a/256) and a/256 respectively.
+func mix(c1, c2 color.NRGBA, a uint8) color.NRGBA {
+	ai := int(a)
+	return color.NRGBA{
+		R: byte((int(c1.R)*ai + int(c2.R)*(256-ai)) / 256),
+		G: byte((int(c1.G)*ai + int(c2.G)*(256-ai)) / 256),
+		B: byte((int(c1.B)*ai + int(c2.B)*(256-ai)) / 256),
+		A: byte((int(c1.A)*ai + int(c2.A)*(256-ai)) / 256),
+	}
+}
+
+// approxLuminance is a fast approximate version of RGBA.Luminance.
+func approxLuminance(c color.NRGBA) byte {
+	const (
+		r = 13933 // 0.2126 * 256 * 256
+		g = 46871 // 0.7152 * 256 * 256
+		b = 4732  // 0.0722 * 256 * 256
+		t = r + g + b
+	)
+	return byte((r*int(c.R) + g*int(c.G) + b*int(c.B)) / t)
+}

+ 25 - 0
vendor/gioui.org/internal/f32color/tables.go

@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// Code generated by f32colorgen. DO NOT EDIT.
+
+package f32color
+
+// table corresponds to sRGBToLinear(float32(index)/0xff)
+var srgb8ToLinear = [...]float32{
+	0, 0.000303527, 0.000607054, 0.000910581, 0.001214108, 0.001517635, 0.001821162, 0.0021246888, 0.002428216, 0.002731743, 0.00303527, 0.0033465363, 0.0036765079, 0.004024718, 0.004391443, 0.004776954,
+	0.005181518, 0.0056053926, 0.006048834, 0.0065120924, 0.0069954116, 0.007499033, 0.008023194, 0.008568126, 0.009134059, 0.00972122, 0.010329825, 0.010960096, 0.011612247, 0.012286489, 0.0129830325, 0.013702083,
+	0.014443846, 0.015208517, 0.015996296, 0.016807377, 0.017641956, 0.01850022, 0.019382365, 0.020288566, 0.021219013, 0.022173887, 0.023153368, 0.024157634, 0.025186861, 0.026241226, 0.027320895, 0.028426042,
+	0.029556837, 0.030713446, 0.031896036, 0.033104766, 0.03433981, 0.035601318, 0.03688945, 0.03820437, 0.039546244, 0.040915202, 0.042311415, 0.043735035, 0.04518621, 0.04666509, 0.048171826, 0.049706567,
+	0.05126947, 0.05286066, 0.05448029, 0.056128502, 0.05780544, 0.059511248, 0.06124608, 0.06301004, 0.06480329, 0.06662596, 0.06847819, 0.07036012, 0.07227187, 0.07421359, 0.0761854, 0.078187436,
+	0.080219835, 0.08228272, 0.08437622, 0.08650047, 0.08865561, 0.09084174, 0.09305899, 0.09530749, 0.09758737, 0.09989875, 0.102241755, 0.10461651, 0.10702312, 0.10946173, 0.11193245, 0.11443539,
+	0.11697068, 0.11953844, 0.122138806, 0.12477185, 0.12743771, 0.1301365, 0.13286835, 0.13563335, 0.13843164, 0.14126332, 0.14412849, 0.14702728, 0.1499598, 0.15292618, 0.15592648, 0.15896088,
+	0.16202942, 0.16513222, 0.16826941, 0.17144111, 0.1746474, 0.17788842, 0.18116425, 0.18447499, 0.18782078, 0.19120169, 0.19461782, 0.19806932, 0.20155625, 0.20507872, 0.20863685, 0.21223074,
+	0.21586055, 0.21952623, 0.223228, 0.2269659, 0.23074009, 0.23455067, 0.23839766, 0.24228121, 0.24620141, 0.25015837, 0.25415218, 0.25818294, 0.26225075, 0.2663557, 0.27049786, 0.2746774,
+	0.27889434, 0.28314883, 0.2874409, 0.29177073, 0.29613835, 0.30054384, 0.30498737, 0.30946898, 0.31398878, 0.31854683, 0.32314327, 0.32777816, 0.33245158, 0.33716366, 0.34191447, 0.3467041,
+	0.35153273, 0.35640025, 0.3613069, 0.36625272, 0.37123778, 0.37626222, 0.3813261, 0.38642955, 0.39157256, 0.39675534, 0.40197787, 0.4072403, 0.4125427, 0.41788515, 0.42326775, 0.42869058,
+	0.4341537, 0.43965724, 0.44520128, 0.45078585, 0.4564111, 0.46207705, 0.46778387, 0.47353154, 0.47932023, 0.48515, 0.4910209, 0.49693304, 0.5028866, 0.50888145, 0.5149178, 0.5209957,
+	0.5271153, 0.53327656, 0.5394796, 0.5457246, 0.55201155, 0.5583405, 0.56471163, 0.5711249, 0.5775806, 0.58407855, 0.59061897, 0.5972019, 0.6038274, 0.6104957, 0.61720663, 0.6239605,
+	0.6307572, 0.63759696, 0.64447975, 0.6514057, 0.6583749, 0.66538733, 0.6724432, 0.67954254, 0.6866855, 0.6938719, 0.7011021, 0.70837593, 0.71569365, 0.7230553, 0.7304609, 0.73791057,
+	0.74540436, 0.7529423, 0.76052463, 0.7681513, 0.77582234, 0.7835379, 0.79129803, 0.79910284, 0.80695236, 0.8148467, 0.82278585, 0.83076996, 0.8387991, 0.84687334, 0.8549927, 0.8631573,
+	0.8713672, 0.87962234, 0.8879232, 0.89626944, 0.90466136, 0.9130987, 0.92158204, 0.9301109, 0.9386859, 0.9473066, 0.9559735, 0.9646863, 0.9734455, 0.9822506, 0.9911022, 1,
+}

+ 95 - 0
vendor/gioui.org/internal/fling/animation.go

@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package fling
+
+import (
+	"math"
+	"runtime"
+	"time"
+
+	"gioui.org/unit"
+)
+
+type Animation struct {
+	// Current offset in pixels.
+	x float32
+	// Initial time.
+	t0 time.Time
+	// Initial velocity in pixels pr second.
+	v0 float32
+}
+
+const (
+	// dp/second.
+	minFlingVelocity  = unit.Dp(50)
+	maxFlingVelocity  = unit.Dp(8000)
+	thresholdVelocity = 1
+)
+
+// Start a fling given a starting velocity. Returns whether a
+// fling was started.
+func (f *Animation) Start(c unit.Metric, now time.Time, velocity float32) bool {
+	min := float32(c.Dp(minFlingVelocity))
+	v := velocity
+	if -min <= v && v <= min {
+		return false
+	}
+	max := float32(c.Dp(maxFlingVelocity))
+	if v > max {
+		v = max
+	} else if v < -max {
+		v = -max
+	}
+	f.init(now, v)
+	return true
+}
+
+func (f *Animation) init(now time.Time, v0 float32) {
+	f.t0 = now
+	f.v0 = v0
+	f.x = 0
+}
+
+func (f *Animation) Active() bool {
+	return f.v0 != 0
+}
+
+// Tick computes and returns a fling distance since
+// the last time Tick was called.
+func (f *Animation) Tick(now time.Time) int {
+	if !f.Active() {
+		return 0
+	}
+	var k float32
+	if runtime.GOOS == "darwin" {
+		k = -2 // iOS
+	} else {
+		k = -4.2 // Android and default
+	}
+	t := now.Sub(f.t0)
+	// The acceleration x''(t) of a point mass with a drag
+	// force, f, proportional with velocity, x'(t), is
+	// governed by the equation
+	//
+	// x''(t) = kx'(t)
+	//
+	// Given the starting position x(0) = 0, the starting
+	// velocity x'(0) = v0, the position is then
+	// given by
+	//
+	// x(t) = v0*e^(k*t)/k - v0/k
+	//
+	ekt := float32(math.Exp(float64(k) * t.Seconds()))
+	x := f.v0*ekt/k - f.v0/k
+	dist := x - f.x
+	idist := int(dist)
+	f.x += float32(idist)
+	// Solving for the velocity x'(t) gives us
+	//
+	// x'(t) = v0*e^(k*t)
+	v := f.v0 * ekt
+	if -thresholdVelocity < v && v < thresholdVelocity {
+		f.v0 = 0
+	}
+	return idist
+}

+ 332 - 0
vendor/gioui.org/internal/fling/extrapolation.go

@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package fling
+
+import (
+	"math"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Extrapolation computes a 1-dimensional velocity estimate
+// for a set of timestamped points using the least squares
+// fit of a 2nd order polynomial. The same method is used
+// by Android.
+type Extrapolation struct {
+	// Index into points.
+	idx int
+	// Circular buffer of samples.
+	samples   []sample
+	lastValue float32
+	// Pre-allocated cache for samples.
+	cache [historySize]sample
+
+	// Filtered values and times
+	values [historySize]float32
+	times  [historySize]float32
+}
+
+type sample struct {
+	t time.Duration
+	v float32
+}
+
+type matrix struct {
+	rows, cols int
+	data       []float32
+}
+
+type Estimate struct {
+	Velocity float32
+	Distance float32
+}
+
+type coefficients [degree + 1]float32
+
+const (
+	degree       = 2
+	historySize  = 20
+	maxAge       = 100 * time.Millisecond
+	maxSampleGap = 40 * time.Millisecond
+)
+
+// SampleDelta adds a relative sample to the estimation.
+func (e *Extrapolation) SampleDelta(t time.Duration, delta float32) {
+	val := delta + e.lastValue
+	e.Sample(t, val)
+}
+
+// Sample adds an absolute sample to the estimation.
+func (e *Extrapolation) Sample(t time.Duration, val float32) {
+	e.lastValue = val
+	if e.samples == nil {
+		e.samples = e.cache[:0]
+	}
+	s := sample{
+		t: t,
+		v: val,
+	}
+	if e.idx == len(e.samples) && e.idx < cap(e.samples) {
+		e.samples = append(e.samples, s)
+	} else {
+		e.samples[e.idx] = s
+	}
+	e.idx++
+	if e.idx == cap(e.samples) {
+		e.idx = 0
+	}
+}
+
+// Velocity returns an estimate of the implied velocity and
+// distance for the points sampled, or zero if the estimation method
+// failed.
+func (e *Extrapolation) Estimate() Estimate {
+	if len(e.samples) == 0 {
+		return Estimate{}
+	}
+	values := e.values[:0]
+	times := e.times[:0]
+	first := e.get(0)
+	t := first.t
+	// Walk backwards collecting samples.
+	for i := 0; i < len(e.samples); i++ {
+		p := e.get(-i)
+		age := first.t - p.t
+		if age >= maxAge || t-p.t >= maxSampleGap {
+			// If the samples are too old or
+			// too much time passed between samples
+			// assume they're not part of the fling.
+			break
+		}
+		t = p.t
+		values = append(values, first.v-p.v)
+		times = append(times, float32((-age).Seconds()))
+	}
+	coef, ok := polyFit(times, values)
+	if !ok {
+		return Estimate{}
+	}
+	dist := values[len(values)-1] - values[0]
+	return Estimate{
+		Velocity: coef[1],
+		Distance: dist,
+	}
+}
+
+func (e *Extrapolation) get(i int) sample {
+	idx := (e.idx + i - 1 + len(e.samples)) % len(e.samples)
+	return e.samples[idx]
+}
+
+// fit computes the least squares polynomial fit for
+// the set of points in X, Y. If the fitting fails
+// because of contradicting or insufficient data,
+// fit returns false.
+func polyFit(X, Y []float32) (coefficients, bool) {
+	if len(X) != len(Y) {
+		panic("X and Y lengths differ")
+	}
+	if len(X) <= degree {
+		// Not enough points to fit a curve.
+		return coefficients{}, false
+	}
+
+	// Use a method similar to Android's VelocityTracker.cpp:
+	// https://android.googlesource.com/platform/frameworks/base/+/56a2301/libs/androidfw/VelocityTracker.cpp
+	// where all weights are 1.
+
+	// First, expand the X vector to the matrix A in column-major order.
+	A := newMatrix(degree+1, len(X))
+	for i, x := range X {
+		A.set(0, i, 1)
+		for j := 1; j < A.rows; j++ {
+			A.set(j, i, A.get(j-1, i)*x)
+		}
+	}
+
+	Q, Rt, ok := decomposeQR(A)
+	if !ok {
+		return coefficients{}, false
+	}
+	// Solve R*B = Qt*Y for B, which is then the polynomial coefficients.
+	// Since R is upper triangular, we can proceed from bottom right to
+	// upper left.
+	// https://en.wikipedia.org/wiki/Non-linear_least_squares
+	var B coefficients
+	for i := Q.rows - 1; i >= 0; i-- {
+		B[i] = dot(Q.col(i), Y)
+		for j := Q.rows - 1; j > i; j-- {
+			B[i] -= Rt.get(i, j) * B[j]
+		}
+		B[i] /= Rt.get(i, i)
+	}
+	return B, true
+}
+
+// decomposeQR computes and returns Q, Rt where Q*transpose(Rt) = A, if
+// possible. R is guaranteed to be upper triangular and only the square
+// part of Rt is returned.
+func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
+	// Gram-Schmidt QR decompose A where Q*R = A.
+	// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
+	Q := newMatrix(A.rows, A.cols)  // Column-major.
+	Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
+	for i := 0; i < Q.rows; i++ {
+		// Copy A column.
+		for j := 0; j < Q.cols; j++ {
+			Q.set(i, j, A.get(i, j))
+		}
+		// Subtract projections. Note that int the projection
+		//
+		// proju a = <u, a>/<u, u> u
+		//
+		// the normalized column e replaces u, where <e, e> = 1:
+		//
+		// proje a = <e, a>/<e, e> e = <e, a> e
+		for j := 0; j < i; j++ {
+			d := dot(Q.col(j), Q.col(i))
+			for k := 0; k < Q.cols; k++ {
+				Q.set(i, k, Q.get(i, k)-d*Q.get(j, k))
+			}
+		}
+		// Normalize Q columns.
+		n := norm(Q.col(i))
+		if n < 0.000001 {
+			// Degenerate data, no solution.
+			return nil, nil, false
+		}
+		invNorm := 1 / n
+		for j := 0; j < Q.cols; j++ {
+			Q.set(i, j, Q.get(i, j)*invNorm)
+		}
+		// Update Rt.
+		for j := i; j < Rt.cols; j++ {
+			Rt.set(i, j, dot(Q.col(i), A.col(j)))
+		}
+	}
+	return Q, Rt, true
+}
+
+func norm(V []float32) float32 {
+	var n float32
+	for _, v := range V {
+		n += v * v
+	}
+	return float32(math.Sqrt(float64(n)))
+}
+
+func dot(V1, V2 []float32) float32 {
+	var d float32
+	for i, v1 := range V1 {
+		d += v1 * V2[i]
+	}
+	return d
+}
+
+func newMatrix(rows, cols int) *matrix {
+	return &matrix{
+		rows: rows,
+		cols: cols,
+		data: make([]float32, rows*cols),
+	}
+}
+
+func (m *matrix) set(row, col int, v float32) {
+	if row < 0 || row >= m.rows {
+		panic("row out of range")
+	}
+	if col < 0 || col >= m.cols {
+		panic("col out of range")
+	}
+	m.data[row*m.cols+col] = v
+}
+
+func (m *matrix) get(row, col int) float32 {
+	if row < 0 || row >= m.rows {
+		panic("row out of range")
+	}
+	if col < 0 || col >= m.cols {
+		panic("col out of range")
+	}
+	return m.data[row*m.cols+col]
+}
+
+func (m *matrix) col(c int) []float32 {
+	return m.data[c*m.cols : (c+1)*m.cols]
+}
+
+func (m *matrix) approxEqual(m2 *matrix) bool {
+	if m.rows != m2.rows || m.cols != m2.cols {
+		return false
+	}
+	const epsilon = 0.00001
+	for row := 0; row < m.rows; row++ {
+		for col := 0; col < m.cols; col++ {
+			d := m2.get(row, col) - m.get(row, col)
+			if d < -epsilon || d > epsilon {
+				return false
+			}
+		}
+	}
+	return true
+}
+
+func (m *matrix) transpose() *matrix {
+	t := &matrix{
+		rows: m.cols,
+		cols: m.rows,
+		data: make([]float32, len(m.data)),
+	}
+	for i := 0; i < m.rows; i++ {
+		for j := 0; j < m.cols; j++ {
+			t.set(j, i, m.get(i, j))
+		}
+	}
+	return t
+}
+
+func (m *matrix) mul(m2 *matrix) *matrix {
+	if m.rows != m2.cols {
+		panic("mismatched matrices")
+	}
+	mm := &matrix{
+		rows: m.rows,
+		cols: m2.cols,
+		data: make([]float32, m.rows*m2.cols),
+	}
+	for i := 0; i < mm.rows; i++ {
+		for j := 0; j < mm.cols; j++ {
+			var v float32
+			for k := 0; k < m.rows; k++ {
+				v += m.get(k, j) * m2.get(i, k)
+			}
+			mm.set(i, j, v)
+		}
+	}
+	return mm
+}
+
+func (m *matrix) String() string {
+	var b strings.Builder
+	for i := 0; i < m.rows; i++ {
+		for j := 0; j < m.cols; j++ {
+			v := m.get(i, j)
+			b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
+			b.WriteString(", ")
+		}
+		b.WriteString("\n")
+	}
+	return b.String()
+}
+
+func (c coefficients) approxEqual(c2 coefficients) bool {
+	const epsilon = 0.00001
+	for i, v := range c {
+		d := v - c2[i]
+		if d < -epsilon || d > epsilon {
+			return false
+		}
+	}
+	return true
+}

+ 131 - 0
vendor/gioui.org/internal/gl/gl.go

@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package gl
+
+type (
+	Attrib uint
+	Enum   uint
+)
+
+const (
+	ACTIVE_TEXTURE                        = 0x84E0
+	ALL_BARRIER_BITS                      = 0xffffffff
+	ARRAY_BUFFER                          = 0x8892
+	ARRAY_BUFFER_BINDING                  = 0x8894
+	BACK                                  = 0x0405
+	BLEND                                 = 0xbe2
+	BLEND_DST_RGB                         = 0x80C8
+	BLEND_SRC_RGB                         = 0x80C9
+	BLEND_DST_ALPHA                       = 0x80CA
+	BLEND_SRC_ALPHA                       = 0x80CB
+	CLAMP_TO_EDGE                         = 0x812f
+	COLOR_ATTACHMENT0                     = 0x8ce0
+	COLOR_BUFFER_BIT                      = 0x4000
+	COLOR_CLEAR_VALUE                     = 0x0C22
+	COMPILE_STATUS                        = 0x8b81
+	COMPUTE_SHADER                        = 0x91B9
+	CURRENT_PROGRAM                       = 0x8B8D
+	DEPTH_ATTACHMENT                      = 0x8d00
+	DEPTH_BUFFER_BIT                      = 0x100
+	DEPTH_CLEAR_VALUE                     = 0x0B73
+	DEPTH_COMPONENT16                     = 0x81a5
+	DEPTH_COMPONENT24                     = 0x81A6
+	DEPTH_COMPONENT32F                    = 0x8CAC
+	DEPTH_FUNC                            = 0x0B74
+	DEPTH_TEST                            = 0xb71
+	DEPTH_WRITEMASK                       = 0x0B72
+	DRAW_FRAMEBUFFER                      = 0x8CA9
+	DST_COLOR                             = 0x306
+	DYNAMIC_DRAW                          = 0x88E8
+	DYNAMIC_READ                          = 0x88E9
+	ELEMENT_ARRAY_BUFFER                  = 0x8893
+	ELEMENT_ARRAY_BUFFER_BINDING          = 0x8895
+	EXTENSIONS                            = 0x1f03
+	FALSE                                 = 0
+	FLOAT                                 = 0x1406
+	FRAGMENT_SHADER                       = 0x8b30
+	FRAMEBUFFER                           = 0x8d40
+	FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210
+	FRAMEBUFFER_BINDING                   = 0x8ca6
+	FRAMEBUFFER_COMPLETE                  = 0x8cd5
+	FRAMEBUFFER_SRGB                      = 0x8db9
+	HALF_FLOAT                            = 0x140b
+	HALF_FLOAT_OES                        = 0x8d61
+	INFO_LOG_LENGTH                       = 0x8B84
+	INVALID_INDEX                         = ^uint(0)
+	GREATER                               = 0x204
+	GEQUAL                                = 0x206
+	LINEAR                                = 0x2601
+	LINEAR_MIPMAP_LINEAR                  = 0x2703
+	LINK_STATUS                           = 0x8b82
+	LUMINANCE                             = 0x1909
+	MAP_READ_BIT                          = 0x0001
+	MAX_TEXTURE_SIZE                      = 0xd33
+	NEAREST                               = 0x2600
+	NO_ERROR                              = 0x0
+	NUM_EXTENSIONS                        = 0x821D
+	ONE                                   = 0x1
+	ONE_MINUS_SRC_ALPHA                   = 0x303
+	PACK_ROW_LENGTH                       = 0x0D02
+	PROGRAM_BINARY_LENGTH                 = 0x8741
+	QUERY_RESULT                          = 0x8866
+	QUERY_RESULT_AVAILABLE                = 0x8867
+	R16F                                  = 0x822d
+	R8                                    = 0x8229
+	READ_FRAMEBUFFER                      = 0x8ca8
+	READ_FRAMEBUFFER_BINDING              = 0x8CAA
+	READ_ONLY                             = 0x88B8
+	READ_WRITE                            = 0x88BA
+	RED                                   = 0x1903
+	RENDERER                              = 0x1F01
+	RENDERBUFFER                          = 0x8d41
+	RENDERBUFFER_BINDING                  = 0x8ca7
+	RENDERBUFFER_HEIGHT                   = 0x8d43
+	RENDERBUFFER_WIDTH                    = 0x8d42
+	RGB                                   = 0x1907
+	RGBA                                  = 0x1908
+	RGBA8                                 = 0x8058
+	SHADER_STORAGE_BUFFER                 = 0x90D2
+	SHADER_STORAGE_BUFFER_BINDING         = 0x90D3
+	SHORT                                 = 0x1402
+	SRGB                                  = 0x8c40
+	SRGB_ALPHA_EXT                        = 0x8c42
+	SRGB8                                 = 0x8c41
+	SRGB8_ALPHA8                          = 0x8c43
+	STATIC_DRAW                           = 0x88e4
+	STENCIL_BUFFER_BIT                    = 0x00000400
+	TEXTURE_2D                            = 0xde1
+	TEXTURE_BINDING_2D                    = 0x8069
+	TEXTURE_MAG_FILTER                    = 0x2800
+	TEXTURE_MIN_FILTER                    = 0x2801
+	TEXTURE_WRAP_S                        = 0x2802
+	TEXTURE_WRAP_T                        = 0x2803
+	TEXTURE0                              = 0x84c0
+	TEXTURE1                              = 0x84c1
+	TRIANGLE_STRIP                        = 0x5
+	TRIANGLES                             = 0x4
+	TRUE                                  = 1
+	UNIFORM_BUFFER                        = 0x8A11
+	UNIFORM_BUFFER_BINDING                = 0x8A28
+	UNPACK_ALIGNMENT                      = 0xcf5
+	UNPACK_ROW_LENGTH                     = 0x0CF2
+	UNSIGNED_BYTE                         = 0x1401
+	UNSIGNED_SHORT                        = 0x1403
+	VIEWPORT                              = 0x0BA2
+	VERSION                               = 0x1f02
+	VERTEX_ARRAY_BINDING                  = 0x85B5
+	VERTEX_SHADER                         = 0x8b31
+	VERTEX_ATTRIB_ARRAY_BUFFER_BINDING    = 0x889F
+	VERTEX_ATTRIB_ARRAY_ENABLED           = 0x8622
+	VERTEX_ATTRIB_ARRAY_POINTER           = 0x8645
+	VERTEX_ATTRIB_ARRAY_NORMALIZED        = 0x886A
+	VERTEX_ATTRIB_ARRAY_SIZE              = 0x8623
+	VERTEX_ATTRIB_ARRAY_STRIDE            = 0x8624
+	VERTEX_ATTRIB_ARRAY_TYPE              = 0x8625
+	WRITE_ONLY                            = 0x88B9
+	ZERO                                  = 0x0
+
+	// EXT_disjoint_timer_query
+	TIME_ELAPSED_EXT = 0x88BF
+	GPU_DISJOINT_EXT = 0x8FBB
+)

+ 653 - 0
vendor/gioui.org/internal/gl/gl_js.go

@@ -0,0 +1,653 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package gl
+
+import (
+	"errors"
+	"strings"
+	"syscall/js"
+)
+
+type Functions struct {
+	Ctx                             js.Value
+	EXT_disjoint_timer_query        js.Value
+	EXT_disjoint_timer_query_webgl2 js.Value
+
+	// Cached reference to the Uint8Array JS type.
+	uint8Array js.Value
+
+	// Cached JS arrays.
+	arrayBuf js.Value
+	int32Buf js.Value
+
+	isWebGL2 bool
+
+	_getExtension                      js.Value
+	_activeTexture                     js.Value
+	_attachShader                      js.Value
+	_beginQuery                        js.Value
+	_beginQueryEXT                     js.Value
+	_bindAttribLocation                js.Value
+	_bindBuffer                        js.Value
+	_bindBufferBase                    js.Value
+	_bindFramebuffer                   js.Value
+	_bindRenderbuffer                  js.Value
+	_bindTexture                       js.Value
+	_blendEquation                     js.Value
+	_blendFunc                         js.Value
+	_bufferData                        js.Value
+	_bufferSubData                     js.Value
+	_checkFramebufferStatus            js.Value
+	_clear                             js.Value
+	_clearColor                        js.Value
+	_clearDepth                        js.Value
+	_compileShader                     js.Value
+	_copyTexSubImage2D                 js.Value
+	_createBuffer                      js.Value
+	_createFramebuffer                 js.Value
+	_createProgram                     js.Value
+	_createQuery                       js.Value
+	_createRenderbuffer                js.Value
+	_createShader                      js.Value
+	_createTexture                     js.Value
+	_deleteBuffer                      js.Value
+	_deleteFramebuffer                 js.Value
+	_deleteProgram                     js.Value
+	_deleteQuery                       js.Value
+	_deleteQueryEXT                    js.Value
+	_deleteShader                      js.Value
+	_deleteRenderbuffer                js.Value
+	_deleteTexture                     js.Value
+	_depthFunc                         js.Value
+	_depthMask                         js.Value
+	_disableVertexAttribArray          js.Value
+	_disable                           js.Value
+	_drawArrays                        js.Value
+	_drawElements                      js.Value
+	_enable                            js.Value
+	_enableVertexAttribArray           js.Value
+	_endQuery                          js.Value
+	_endQueryEXT                       js.Value
+	_finish                            js.Value
+	_flush                             js.Value
+	_framebufferRenderbuffer           js.Value
+	_framebufferTexture2D              js.Value
+	_generateMipmap                    js.Value
+	_getRenderbufferParameteri         js.Value
+	_getFramebufferAttachmentParameter js.Value
+	_getParameter                      js.Value
+	_getIndexedParameter               js.Value
+	_getProgramParameter               js.Value
+	_getProgramInfoLog                 js.Value
+	_getQueryParameter                 js.Value
+	_getQueryObjectEXT                 js.Value
+	_getShaderParameter                js.Value
+	_getShaderInfoLog                  js.Value
+	_getSupportedExtensions            js.Value
+	_getUniformBlockIndex              js.Value
+	_getUniformLocation                js.Value
+	_getVertexAttrib                   js.Value
+	_getVertexAttribOffset             js.Value
+	_invalidateFramebuffer             js.Value
+	_isEnabled                         js.Value
+	_linkProgram                       js.Value
+	_pixelStorei                       js.Value
+	_renderbufferStorage               js.Value
+	_readPixels                        js.Value
+	_scissor                           js.Value
+	_shaderSource                      js.Value
+	_texImage2D                        js.Value
+	_texStorage2D                      js.Value
+	_texSubImage2D                     js.Value
+	_texParameteri                     js.Value
+	_uniformBlockBinding               js.Value
+	_uniform1f                         js.Value
+	_uniform1i                         js.Value
+	_uniform2f                         js.Value
+	_uniform3f                         js.Value
+	_uniform4f                         js.Value
+	_useProgram                        js.Value
+	_vertexAttribPointer               js.Value
+	_viewport                          js.Value
+}
+
+type Context js.Value
+
+func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
+	webgl := js.Value(ctx)
+	f := &Functions{
+		Ctx:                                webgl,
+		uint8Array:                         js.Global().Get("Uint8Array"),
+		_getExtension:                      _bind(webgl, `getExtension`),
+		_activeTexture:                     _bind(webgl, `activeTexture`),
+		_attachShader:                      _bind(webgl, `attachShader`),
+		_beginQuery:                        _bind(webgl, `beginQuery`),
+		_beginQueryEXT:                     _bind(webgl, `beginQueryEXT`),
+		_bindAttribLocation:                _bind(webgl, `bindAttribLocation`),
+		_bindBuffer:                        _bind(webgl, `bindBuffer`),
+		_bindBufferBase:                    _bind(webgl, `bindBufferBase`),
+		_bindFramebuffer:                   _bind(webgl, `bindFramebuffer`),
+		_bindRenderbuffer:                  _bind(webgl, `bindRenderbuffer`),
+		_bindTexture:                       _bind(webgl, `bindTexture`),
+		_blendEquation:                     _bind(webgl, `blendEquation`),
+		_blendFunc:                         _bind(webgl, `blendFunc`),
+		_bufferData:                        _bind(webgl, `bufferData`),
+		_bufferSubData:                     _bind(webgl, `bufferSubData`),
+		_checkFramebufferStatus:            _bind(webgl, `checkFramebufferStatus`),
+		_clear:                             _bind(webgl, `clear`),
+		_clearColor:                        _bind(webgl, `clearColor`),
+		_clearDepth:                        _bind(webgl, `clearDepth`),
+		_compileShader:                     _bind(webgl, `compileShader`),
+		_copyTexSubImage2D:                 _bind(webgl, `copyTexSubImage2D`),
+		_createBuffer:                      _bind(webgl, `createBuffer`),
+		_createFramebuffer:                 _bind(webgl, `createFramebuffer`),
+		_createProgram:                     _bind(webgl, `createProgram`),
+		_createQuery:                       _bind(webgl, `createQuery`),
+		_createRenderbuffer:                _bind(webgl, `createRenderbuffer`),
+		_createShader:                      _bind(webgl, `createShader`),
+		_createTexture:                     _bind(webgl, `createTexture`),
+		_deleteBuffer:                      _bind(webgl, `deleteBuffer`),
+		_deleteFramebuffer:                 _bind(webgl, `deleteFramebuffer`),
+		_deleteProgram:                     _bind(webgl, `deleteProgram`),
+		_deleteQuery:                       _bind(webgl, `deleteQuery`),
+		_deleteQueryEXT:                    _bind(webgl, `deleteQueryEXT`),
+		_deleteShader:                      _bind(webgl, `deleteShader`),
+		_deleteRenderbuffer:                _bind(webgl, `deleteRenderbuffer`),
+		_deleteTexture:                     _bind(webgl, `deleteTexture`),
+		_depthFunc:                         _bind(webgl, `depthFunc`),
+		_depthMask:                         _bind(webgl, `depthMask`),
+		_disableVertexAttribArray:          _bind(webgl, `disableVertexAttribArray`),
+		_disable:                           _bind(webgl, `disable`),
+		_drawArrays:                        _bind(webgl, `drawArrays`),
+		_drawElements:                      _bind(webgl, `drawElements`),
+		_enable:                            _bind(webgl, `enable`),
+		_enableVertexAttribArray:           _bind(webgl, `enableVertexAttribArray`),
+		_endQuery:                          _bind(webgl, `endQuery`),
+		_endQueryEXT:                       _bind(webgl, `endQueryEXT`),
+		_finish:                            _bind(webgl, `finish`),
+		_flush:                             _bind(webgl, `flush`),
+		_framebufferRenderbuffer:           _bind(webgl, `framebufferRenderbuffer`),
+		_framebufferTexture2D:              _bind(webgl, `framebufferTexture2D`),
+		_generateMipmap:                    _bind(webgl, `generateMipmap`),
+		_getRenderbufferParameteri:         _bind(webgl, `getRenderbufferParameteri`),
+		_getFramebufferAttachmentParameter: _bind(webgl, `getFramebufferAttachmentParameter`),
+		_getParameter:                      _bind(webgl, `getParameter`),
+		_getIndexedParameter:               _bind(webgl, `getIndexedParameter`),
+		_getProgramParameter:               _bind(webgl, `getProgramParameter`),
+		_getProgramInfoLog:                 _bind(webgl, `getProgramInfoLog`),
+		_getQueryParameter:                 _bind(webgl, `getQueryParameter`),
+		_getQueryObjectEXT:                 _bind(webgl, `getQueryObjectEXT`),
+		_getShaderParameter:                _bind(webgl, `getShaderParameter`),
+		_getShaderInfoLog:                  _bind(webgl, `getShaderInfoLog`),
+		_getSupportedExtensions:            _bind(webgl, `getSupportedExtensions`),
+		_getUniformBlockIndex:              _bind(webgl, `getUniformBlockIndex`),
+		_getUniformLocation:                _bind(webgl, `getUniformLocation`),
+		_getVertexAttrib:                   _bind(webgl, `getVertexAttrib`),
+		_getVertexAttribOffset:             _bind(webgl, `getVertexAttribOffset`),
+		_invalidateFramebuffer:             _bind(webgl, `invalidateFramebuffer`),
+		_isEnabled:                         _bind(webgl, `isEnabled`),
+		_linkProgram:                       _bind(webgl, `linkProgram`),
+		_pixelStorei:                       _bind(webgl, `pixelStorei`),
+		_renderbufferStorage:               _bind(webgl, `renderbufferStorage`),
+		_readPixels:                        _bind(webgl, `readPixels`),
+		_scissor:                           _bind(webgl, `scissor`),
+		_shaderSource:                      _bind(webgl, `shaderSource`),
+		_texImage2D:                        _bind(webgl, `texImage2D`),
+		_texStorage2D:                      _bind(webgl, `texStorage2D`),
+		_texSubImage2D:                     _bind(webgl, `texSubImage2D`),
+		_texParameteri:                     _bind(webgl, `texParameteri`),
+		_uniformBlockBinding:               _bind(webgl, `uniformBlockBinding`),
+		_uniform1f:                         _bind(webgl, `uniform1f`),
+		_uniform1i:                         _bind(webgl, `uniform1i`),
+		_uniform2f:                         _bind(webgl, `uniform2f`),
+		_uniform3f:                         _bind(webgl, `uniform3f`),
+		_uniform4f:                         _bind(webgl, `uniform4f`),
+		_useProgram:                        _bind(webgl, `useProgram`),
+		_vertexAttribPointer:               _bind(webgl, `vertexAttribPointer`),
+		_viewport:                          _bind(webgl, `viewport`),
+	}
+	if err := f.Init(); err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+func _bind(ctx js.Value, p string) js.Value {
+	if o := ctx.Get(p); o.Truthy() {
+		return o.Call("bind", ctx)
+	}
+	return js.Undefined()
+}
+
+func (f *Functions) Init() error {
+	webgl2Class := js.Global().Get("WebGL2RenderingContext")
+	f.isWebGL2 = !webgl2Class.IsUndefined() && f.Ctx.InstanceOf(webgl2Class)
+	if !f.isWebGL2 {
+		f.EXT_disjoint_timer_query = f.getExtension("EXT_disjoint_timer_query")
+		if f.getExtension("OES_texture_half_float").IsNull() && f.getExtension("OES_texture_float").IsNull() {
+			return errors.New("gl: no support for neither OES_texture_half_float nor OES_texture_float")
+		}
+		if f.getExtension("EXT_sRGB").IsNull() {
+			return errors.New("gl: EXT_sRGB not supported")
+		}
+	} else {
+		// WebGL2 extensions.
+		f.EXT_disjoint_timer_query_webgl2 = f.getExtension("EXT_disjoint_timer_query_webgl2")
+		if f.getExtension("EXT_color_buffer_half_float").IsNull() && f.getExtension("EXT_color_buffer_float").IsNull() {
+			return errors.New("gl: no support for neither EXT_color_buffer_half_float nor EXT_color_buffer_float")
+		}
+	}
+	return nil
+}
+
+func (f *Functions) getExtension(name string) js.Value {
+	return f._getExtension.Invoke(name)
+}
+
+func (f *Functions) ActiveTexture(t Enum) {
+	f._activeTexture.Invoke(int(t))
+}
+func (f *Functions) AttachShader(p Program, s Shader) {
+	f._attachShader.Invoke(js.Value(p), js.Value(s))
+}
+func (f *Functions) BeginQuery(target Enum, query Query) {
+	if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
+		f._beginQuery.Invoke(int(target), js.Value(query))
+	} else {
+		f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
+	}
+}
+func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
+	f._bindAttribLocation.Invoke(js.Value(p), int(a), name)
+}
+func (f *Functions) BindBuffer(target Enum, b Buffer) {
+	f._bindBuffer.Invoke(int(target), js.Value(b))
+}
+func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
+	f._bindBufferBase.Invoke(int(target), index, js.Value(b))
+}
+func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
+	f._bindFramebuffer.Invoke(int(target), js.Value(fb))
+}
+func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
+	f._bindRenderbuffer.Invoke(int(target), js.Value(rb))
+}
+func (f *Functions) BindTexture(target Enum, t Texture) {
+	f._bindTexture.Invoke(int(target), js.Value(t))
+}
+func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
+	panic("not implemented")
+}
+func (f *Functions) BindVertexArray(a VertexArray) {
+	panic("not supported")
+}
+func (f *Functions) BlendEquation(mode Enum) {
+	f._blendEquation.Invoke(int(mode))
+}
+func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
+	f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA))
+}
+func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
+	if data == nil {
+		f._bufferData.Invoke(int(target), size, int(usage))
+	} else {
+		if len(data) != size {
+			panic("size mismatch")
+		}
+		f._bufferData.Invoke(int(target), f.byteArrayOf(data), int(usage))
+	}
+}
+func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
+	f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
+}
+func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
+	status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
+	if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
+		// If the context is lost, we say that everything is fine. That saves internal/opengl/opengl.go from panic.
+		return FRAMEBUFFER_COMPLETE
+	}
+	return status
+}
+func (f *Functions) Clear(mask Enum) {
+	f._clear.Invoke(int(mask))
+}
+func (f *Functions) ClearColor(red, green, blue, alpha float32) {
+	f._clearColor.Invoke(red, green, blue, alpha)
+}
+func (f *Functions) ClearDepthf(d float32) {
+	f._clearDepth.Invoke(d)
+}
+func (f *Functions) CompileShader(s Shader) {
+	f._compileShader.Invoke(js.Value(s))
+}
+func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
+	f._copyTexSubImage2D.Invoke(int(target), level, xoffset, yoffset, x, y, width, height)
+}
+func (f *Functions) CreateBuffer() Buffer {
+	return Buffer(f._createBuffer.Invoke())
+}
+func (f *Functions) CreateFramebuffer() Framebuffer {
+	return Framebuffer(f._createFramebuffer.Invoke())
+}
+func (f *Functions) CreateProgram() Program {
+	return Program(f._createProgram.Invoke())
+}
+func (f *Functions) CreateQuery() Query {
+	return Query(f._createQuery.Invoke())
+}
+func (f *Functions) CreateRenderbuffer() Renderbuffer {
+	return Renderbuffer(f._createRenderbuffer.Invoke())
+}
+func (f *Functions) CreateShader(ty Enum) Shader {
+	return Shader(f._createShader.Invoke(int(ty)))
+}
+func (f *Functions) CreateTexture() Texture {
+	return Texture(f._createTexture.Invoke())
+}
+func (f *Functions) CreateVertexArray() VertexArray {
+	panic("not supported")
+}
+func (f *Functions) DeleteBuffer(v Buffer) {
+	f._deleteBuffer.Invoke(js.Value(v))
+}
+func (f *Functions) DeleteFramebuffer(v Framebuffer) {
+	f._deleteFramebuffer.Invoke(js.Value(v))
+}
+func (f *Functions) DeleteProgram(p Program) {
+	f._deleteProgram.Invoke(js.Value(p))
+}
+func (f *Functions) DeleteQuery(query Query) {
+	if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
+		f._deleteQuery.Invoke(js.Value(query))
+	} else {
+		f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
+	}
+}
+func (f *Functions) DeleteShader(s Shader) {
+	f._deleteShader.Invoke(js.Value(s))
+}
+func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
+	f._deleteRenderbuffer.Invoke(js.Value(v))
+}
+func (f *Functions) DeleteTexture(v Texture) {
+	f._deleteTexture.Invoke(js.Value(v))
+}
+func (f *Functions) DeleteVertexArray(a VertexArray) {
+	panic("not implemented")
+}
+func (f *Functions) DepthFunc(fn Enum) {
+	f._depthFunc.Invoke(int(fn))
+}
+func (f *Functions) DepthMask(mask bool) {
+	f._depthMask.Invoke(mask)
+}
+func (f *Functions) DisableVertexAttribArray(a Attrib) {
+	f._disableVertexAttribArray.Invoke(int(a))
+}
+func (f *Functions) Disable(cap Enum) {
+	f._disable.Invoke(int(cap))
+}
+func (f *Functions) DrawArrays(mode Enum, first, count int) {
+	f._drawArrays.Invoke(int(mode), first, count)
+}
+func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
+	f._drawElements.Invoke(int(mode), count, int(ty), offset)
+}
+func (f *Functions) DispatchCompute(x, y, z int) {
+	panic("not implemented")
+}
+func (f *Functions) Enable(cap Enum) {
+	f._enable.Invoke(int(cap))
+}
+func (f *Functions) EnableVertexAttribArray(a Attrib) {
+	f._enableVertexAttribArray.Invoke(int(a))
+}
+func (f *Functions) EndQuery(target Enum) {
+	if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
+		f._endQuery.Invoke(int(target))
+	} else {
+		f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
+	}
+}
+func (f *Functions) Finish() {
+	f._finish.Invoke()
+}
+func (f *Functions) Flush() {
+	f._flush.Invoke()
+}
+func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
+	f._framebufferRenderbuffer.Invoke(int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
+}
+func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
+	f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level)
+}
+func (f *Functions) GenerateMipmap(target Enum) {
+	f._generateMipmap.Invoke(int(target))
+}
+func (f *Functions) GetError() Enum {
+	// Avoid slow getError calls. See gio#179.
+	return 0
+}
+func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
+	return paramVal(f._getRenderbufferParameteri.Invoke(int(pname)))
+}
+func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
+	if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING {
+		// FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2
+		return LINEAR
+	}
+	return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname)))
+}
+func (f *Functions) GetBinding(pname Enum) Object {
+	obj := f._getParameter.Invoke(int(pname))
+	if !obj.Truthy() {
+		return Object{}
+	}
+	return Object(obj)
+}
+func (f *Functions) GetBindingi(pname Enum, idx int) Object {
+	obj := f._getIndexedParameter.Invoke(int(pname), idx)
+	if !obj.Truthy() {
+		return Object{}
+	}
+	return Object(obj)
+}
+func (f *Functions) GetInteger(pname Enum) int {
+	if !f.isWebGL2 {
+		switch pname {
+		case PACK_ROW_LENGTH, UNPACK_ROW_LENGTH:
+			return 0 // PACK_ROW_LENGTH and UNPACK_ROW_LENGTH is only available on WebGL 2
+		}
+	}
+	return paramVal(f._getParameter.Invoke(int(pname)))
+}
+func (f *Functions) GetFloat(pname Enum) float32 {
+	return float32(f._getParameter.Invoke(int(pname)).Float())
+}
+func (f *Functions) GetInteger4(pname Enum) [4]int {
+	arr := f._getParameter.Invoke(int(pname))
+	var res [4]int
+	for i := range res {
+		res[i] = arr.Index(i).Int()
+	}
+	return res
+}
+func (f *Functions) GetFloat4(pname Enum) [4]float32 {
+	arr := f._getParameter.Invoke(int(pname))
+	var res [4]float32
+	for i := range res {
+		res[i] = float32(arr.Index(i).Float())
+	}
+	return res
+}
+func (f *Functions) GetProgrami(p Program, pname Enum) int {
+	return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname)))
+}
+func (f *Functions) GetProgramInfoLog(p Program) string {
+	return f._getProgramInfoLog.Invoke(js.Value(p)).String()
+}
+func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
+	if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
+		return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname))))
+	} else {
+		return uint(paramVal(f.EXT_disjoint_timer_query.Call("getQueryObjectEXT", js.Value(query), int(pname))))
+	}
+}
+func (f *Functions) GetShaderi(s Shader, pname Enum) int {
+	return paramVal(f._getShaderParameter.Invoke(js.Value(s), int(pname)))
+}
+func (f *Functions) GetShaderInfoLog(s Shader) string {
+	return f._getShaderInfoLog.Invoke(js.Value(s)).String()
+}
+func (f *Functions) GetString(pname Enum) string {
+	switch pname {
+	case EXTENSIONS:
+		extsjs := f._getSupportedExtensions.Invoke()
+		var exts []string
+		for i := 0; i < extsjs.Length(); i++ {
+			exts = append(exts, "GL_"+extsjs.Index(i).String())
+		}
+		return strings.Join(exts, " ")
+	default:
+		return f._getParameter.Invoke(int(pname)).String()
+	}
+}
+func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
+	return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name)))
+}
+func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
+	return Uniform(f._getUniformLocation.Invoke(js.Value(p), name))
+}
+func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
+	return paramVal(f._getVertexAttrib.Invoke(index, int(pname)))
+}
+func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
+	obj := f._getVertexAttrib.Invoke(index, int(pname))
+	if !obj.Truthy() {
+		return Object{}
+	}
+	return Object(obj)
+}
+func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
+	return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int())
+}
+func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
+	fn := f.Ctx.Get("invalidateFramebuffer")
+	if !fn.IsUndefined() {
+		if f.int32Buf.IsUndefined() {
+			f.int32Buf = js.Global().Get("Int32Array").New(1)
+		}
+		f.int32Buf.SetIndex(0, int32(attachment))
+		f._invalidateFramebuffer.Invoke(int(target), f.int32Buf)
+	}
+}
+func (f *Functions) IsEnabled(cap Enum) bool {
+	return f._isEnabled.Invoke(int(cap)).Truthy()
+}
+func (f *Functions) LinkProgram(p Program) {
+	f._linkProgram.Invoke(js.Value(p))
+}
+func (f *Functions) PixelStorei(pname Enum, param int) {
+	f._pixelStorei.Invoke(int(pname), param)
+}
+func (f *Functions) MemoryBarrier(barriers Enum) {
+	panic("not implemented")
+}
+func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
+	panic("not implemented")
+}
+func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
+	f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height)
+}
+func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
+	ba := f.byteArrayOf(data)
+	f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba)
+	js.CopyBytesToGo(data, ba)
+}
+func (f *Functions) Scissor(x, y, width, height int32) {
+	f._scissor.Invoke(x, y, width, height)
+}
+func (f *Functions) ShaderSource(s Shader, src string) {
+	f._shaderSource.Invoke(js.Value(s), src)
+}
+func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) {
+	f._texImage2D.Invoke(int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), nil)
+}
+func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
+	f._texStorage2D.Invoke(int(target), levels, int(internalFormat), width, height)
+}
+func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
+	f._texSubImage2D.Invoke(int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
+}
+func (f *Functions) TexParameteri(target, pname Enum, param int) {
+	f._texParameteri.Invoke(int(target), int(pname), int(param))
+}
+func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
+	f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
+}
+func (f *Functions) Uniform1f(dst Uniform, v float32) {
+	f._uniform1f.Invoke(js.Value(dst), v)
+}
+func (f *Functions) Uniform1i(dst Uniform, v int) {
+	f._uniform1i.Invoke(js.Value(dst), v)
+}
+func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
+	f._uniform2f.Invoke(js.Value(dst), v0, v1)
+}
+func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
+	f._uniform3f.Invoke(js.Value(dst), v0, v1, v2)
+}
+func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
+	f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3)
+}
+func (f *Functions) UseProgram(p Program) {
+	f._useProgram.Invoke(js.Value(p))
+}
+func (f *Functions) UnmapBuffer(target Enum) bool {
+	panic("not implemented")
+}
+func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
+	f._vertexAttribPointer.Invoke(int(dst), size, int(ty), normalized, stride, offset)
+}
+func (f *Functions) Viewport(x, y, width, height int) {
+	f._viewport.Invoke(x, y, width, height)
+}
+
+func (f *Functions) byteArrayOf(data []byte) js.Value {
+	if len(data) == 0 {
+		return js.Null()
+	}
+	f.resizeByteBuffer(len(data))
+	ba := f.uint8Array.New(f.arrayBuf, int(0), int(len(data)))
+	js.CopyBytesToJS(ba, data)
+	return ba
+}
+
+func (f *Functions) resizeByteBuffer(n int) {
+	if n == 0 {
+		return
+	}
+	if !f.arrayBuf.IsUndefined() && f.arrayBuf.Length() >= n {
+		return
+	}
+	f.arrayBuf = js.Global().Get("ArrayBuffer").New(n)
+}
+
+func paramVal(v js.Value) int {
+	switch v.Type() {
+	case js.TypeBoolean:
+		if b := v.Bool(); b {
+			return 1
+		} else {
+			return 0
+		}
+	case js.TypeNumber:
+		return v.Int()
+	case js.TypeUndefined:
+		return 0
+	case js.TypeNull:
+		return 0
+	default:
+		panic("unknown parameter type")
+	}
+}

+ 1323 - 0
vendor/gioui.org/internal/gl/gl_unix.go

@@ -0,0 +1,1323 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build darwin || linux || freebsd || openbsd
+// +build darwin linux freebsd openbsd
+
+package gl
+
+import (
+	"fmt"
+	"runtime"
+	"strings"
+	"unsafe"
+)
+
+/*
+#cgo CFLAGS: -Werror
+#cgo linux freebsd LDFLAGS: -ldl
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#define __USE_GNU
+#include <dlfcn.h>
+
+typedef unsigned int GLenum;
+typedef unsigned int GLuint;
+typedef char GLchar;
+typedef float GLfloat;
+typedef ssize_t GLsizeiptr;
+typedef intptr_t GLintptr;
+typedef unsigned int GLbitfield;
+typedef int GLint;
+typedef unsigned char GLboolean;
+typedef int GLsizei;
+typedef uint8_t GLubyte;
+
+typedef void (*_glActiveTexture)(GLenum texture);
+typedef void (*_glAttachShader)(GLuint program, GLuint shader);
+typedef void (*_glBindAttribLocation)(GLuint program, GLuint index, const GLchar *name);
+typedef void (*_glBindBuffer)(GLenum target, GLuint buffer);
+typedef void (*_glBindFramebuffer)(GLenum target, GLuint framebuffer);
+typedef void (*_glBindRenderbuffer)(GLenum target, GLuint renderbuffer);
+typedef void (*_glBindTexture)(GLenum target, GLuint texture);
+typedef void (*_glBlendEquation)(GLenum mode);
+typedef void (*_glBlendFuncSeparate)(GLenum srcRGB, GLenum dstRGB, GLenum srcA, GLenum dstA);
+typedef void (*_glBufferData)(GLenum target, GLsizeiptr size, const void *data, GLenum usage);
+typedef void (*_glBufferSubData)(GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
+typedef GLenum (*_glCheckFramebufferStatus)(GLenum target);
+typedef void (*_glClear)(GLbitfield mask);
+typedef void (*_glClearColor)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
+typedef void (*_glClearDepthf)(GLfloat d);
+typedef void (*_glCompileShader)(GLuint shader);
+typedef void (*_glCopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);
+typedef GLuint (*_glCreateProgram)(void);
+typedef GLuint (*_glCreateShader)(GLenum type);
+typedef void (*_glDeleteBuffers)(GLsizei n, const GLuint *buffers);
+typedef void (*_glDeleteFramebuffers)(GLsizei n, const GLuint *framebuffers);
+typedef void (*_glDeleteProgram)(GLuint program);
+typedef void (*_glDeleteRenderbuffers)(GLsizei n, const GLuint *renderbuffers);
+typedef void (*_glDeleteShader)(GLuint shader);
+typedef void (*_glDeleteTextures)(GLsizei n, const GLuint *textures);
+typedef void (*_glDepthFunc)(GLenum func);
+typedef void (*_glDepthMask)(GLboolean flag);
+typedef void (*_glDisable)(GLenum cap);
+typedef void (*_glDisableVertexAttribArray)(GLuint index);
+typedef void (*_glDrawArrays)(GLenum mode, GLint first, GLsizei count);
+typedef void (*_glDrawElements)(GLenum mode, GLsizei count, GLenum type, const void *indices);
+typedef void (*_glEnable)(GLenum cap);
+typedef void (*_glEnableVertexAttribArray)(GLuint index);
+typedef void (*_glFinish)(void);
+typedef void (*_glFlush)(void);
+typedef void (*_glFramebufferRenderbuffer)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
+typedef void (*_glFramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
+typedef void (*_glGenBuffers)(GLsizei n, GLuint *buffers);
+typedef void (*_glGenerateMipmap)(GLenum target);
+typedef void (*_glGenFramebuffers)(GLsizei n, GLuint *framebuffers);
+typedef void (*_glGenRenderbuffers)(GLsizei n, GLuint *renderbuffers);
+typedef void (*_glGenTextures)(GLsizei n, GLuint *textures);
+typedef GLenum (*_glGetError)(void);
+typedef void (*_glGetFramebufferAttachmentParameteriv)(GLenum target, GLenum attachment, GLenum pname, GLint *params);
+typedef void (*_glGetFloatv)(GLenum pname, GLfloat *data);
+typedef void (*_glGetIntegerv)(GLenum pname, GLint *data);
+typedef void (*_glGetIntegeri_v)(GLenum pname, GLuint idx, GLint *data);
+typedef void (*_glGetProgramiv)(GLuint program, GLenum pname, GLint *params);
+typedef void (*_glGetProgramInfoLog)(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
+typedef void (*_glGetRenderbufferParameteriv)(GLenum target, GLenum pname, GLint *params);
+typedef void (*_glGetShaderiv)(GLuint shader, GLenum pname, GLint *params);
+typedef void (*_glGetShaderInfoLog)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
+typedef const GLubyte *(*_glGetString)(GLenum name);
+typedef GLint (*_glGetUniformLocation)(GLuint program, const GLchar *name);
+typedef void (*_glGetVertexAttribiv)(GLuint index, GLenum pname, GLint *params);
+typedef void (*_glGetVertexAttribPointerv)(GLuint index, GLenum pname, void **params);
+typedef GLboolean (*_glIsEnabled)(GLenum cap);
+typedef void (*_glLinkProgram)(GLuint program);
+typedef void (*_glPixelStorei)(GLenum pname, GLint param);
+typedef void (*_glReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels);
+typedef void (*_glRenderbufferStorage)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
+typedef void (*_glScissor)(GLint x, GLint y, GLsizei width, GLsizei height);
+typedef void (*_glShaderSource)(GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);
+typedef void (*_glTexImage2D)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels);
+typedef void (*_glTexParameteri)(GLenum target, GLenum pname, GLint param);
+typedef void (*_glTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels);
+typedef void (*_glUniform1f)(GLint location, GLfloat v0);
+typedef void (*_glUniform1i)(GLint location, GLint v0);
+typedef void (*_glUniform2f)(GLint location, GLfloat v0, GLfloat v1);
+typedef void (*_glUniform3f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
+typedef void (*_glUniform4f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
+typedef void (*_glUseProgram)(GLuint program);
+typedef void (*_glVertexAttribPointer)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
+typedef void (*_glViewport)(GLint x, GLint y, GLsizei width, GLsizei height);
+typedef void (*_glBindVertexArray)(GLuint array);
+typedef void (*_glBindBufferBase)(GLenum target, GLuint index, GLuint buffer);
+typedef GLuint (*_glGetUniformBlockIndex)(GLuint program, const GLchar *uniformBlockName);
+typedef void (*_glUniformBlockBinding)(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
+typedef void (*_glInvalidateFramebuffer)(GLenum target, GLsizei numAttachments, const GLenum *attachments);
+typedef void (*_glBeginQuery)(GLenum target, GLuint id);
+typedef void (*_glDeleteQueries)(GLsizei n, const GLuint *ids);
+typedef void (*_glDeleteVertexArrays)(GLsizei n, const GLuint *ids);
+typedef void (*_glEndQuery)(GLenum target);
+typedef void (*_glGenQueries)(GLsizei n, GLuint *ids);
+typedef void (*_glGenVertexArrays)(GLsizei n, GLuint *ids);
+typedef void (*_glGetProgramBinary)(GLuint program, GLsizei bufsize, GLsizei *length, GLenum *binaryFormat, void *binary);
+typedef void (*_glGetQueryObjectuiv)(GLuint id, GLenum pname, GLuint *params);
+typedef const GLubyte* (*_glGetStringi)(GLenum name, GLuint index);
+typedef void (*_glDispatchCompute)(GLuint x, GLuint y, GLuint z);
+typedef void (*_glMemoryBarrier)(GLbitfield barriers);
+typedef void* (*_glMapBufferRange)(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
+typedef GLboolean (*_glUnmapBuffer)(GLenum target);
+typedef void (*_glBindImageTexture)(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format);
+typedef void (*_glTexStorage2D)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
+typedef void (*_glBlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
+
+static void glActiveTexture(_glActiveTexture f, GLenum texture) {
+	f(texture);
+}
+
+static void glAttachShader(_glAttachShader f, GLuint program, GLuint shader) {
+	f(program, shader);
+}
+
+static void glBindAttribLocation(_glBindAttribLocation f, GLuint program, GLuint index, const GLchar *name) {
+	f(program, index, name);
+}
+
+static void glBindBuffer(_glBindBuffer f, GLenum target, GLuint buffer) {
+	f(target, buffer);
+}
+
+static void glBindFramebuffer(_glBindFramebuffer f, GLenum target, GLuint framebuffer) {
+	f(target, framebuffer);
+}
+
+static void glBindRenderbuffer(_glBindRenderbuffer f, GLenum target, GLuint renderbuffer) {
+	f(target, renderbuffer);
+}
+
+static void glBindTexture(_glBindTexture f, GLenum target, GLuint texture) {
+	f(target, texture);
+}
+
+static void glBindVertexArray(_glBindVertexArray f, GLuint array) {
+	f(array);
+}
+
+static void glBlendEquation(_glBlendEquation f, GLenum mode) {
+	f(mode);
+}
+
+static void glBlendFuncSeparate(_glBlendFuncSeparate f, GLenum srcRGB, GLenum dstRGB, GLenum srcA, GLenum dstA) {
+	f(srcRGB, dstRGB, srcA, dstA);
+}
+
+static void glBufferData(_glBufferData f, GLenum target, GLsizeiptr size, const void *data, GLenum usage) {
+	f(target, size, data, usage);
+}
+
+static void glBufferSubData(_glBufferSubData f, GLenum target, GLintptr offset, GLsizeiptr size, const void *data) {
+	f(target, offset, size, data);
+}
+
+static GLenum glCheckFramebufferStatus(_glCheckFramebufferStatus f, GLenum target) {
+	return f(target);
+}
+
+static void glClear(_glClear f, GLbitfield mask) {
+	f(mask);
+}
+
+static void glClearColor(_glClearColor f, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
+	f(red, green, blue, alpha);
+}
+
+static void glClearDepthf(_glClearDepthf f, GLfloat d) {
+	f(d);
+}
+
+static void glCompileShader(_glCompileShader f, GLuint shader) {
+	f(shader);
+}
+
+static void glCopyTexSubImage2D(_glCopyTexSubImage2D f, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) {
+	f(target, level, xoffset, yoffset, x, y, width, height);
+}
+
+static GLuint glCreateProgram(_glCreateProgram f) {
+	return f();
+}
+
+static GLuint glCreateShader(_glCreateShader f, GLenum type) {
+	return f(type);
+}
+
+static void glDeleteBuffers(_glDeleteBuffers f, GLsizei n, const GLuint *buffers) {
+	f(n, buffers);
+}
+
+static void glDeleteFramebuffers(_glDeleteFramebuffers f, GLsizei n, const GLuint *framebuffers) {
+	f(n, framebuffers);
+}
+
+static void glDeleteProgram(_glDeleteProgram f, GLuint program) {
+	f(program);
+}
+
+static void glDeleteRenderbuffers(_glDeleteRenderbuffers f, GLsizei n, const GLuint *renderbuffers) {
+	f(n, renderbuffers);
+}
+
+static void glDeleteShader(_glDeleteShader f, GLuint shader) {
+	f(shader);
+}
+
+static void glDeleteTextures(_glDeleteTextures f, GLsizei n, const GLuint *textures) {
+	f(n, textures);
+}
+
+static void glDepthFunc(_glDepthFunc f, GLenum func) {
+	f(func);
+}
+
+static void glDepthMask(_glDepthMask f, GLboolean flag) {
+	f(flag);
+}
+
+static void glDisable(_glDisable f, GLenum cap) {
+	f(cap);
+}
+
+static void glDisableVertexAttribArray(_glDisableVertexAttribArray f, GLuint index) {
+	f(index);
+}
+
+static void glDrawArrays(_glDrawArrays f, GLenum mode, GLint first, GLsizei count) {
+	f(mode, first, count);
+}
+
+// offset is defined as an uintptr_t to omit Cgo pointer checks.
+static void glDrawElements(_glDrawElements f, GLenum mode, GLsizei count, GLenum type, const uintptr_t offset) {
+	f(mode, count, type, (const void *)offset);
+}
+
+static void glEnable(_glEnable f, GLenum cap) {
+	f(cap);
+}
+
+static void glEnableVertexAttribArray(_glEnableVertexAttribArray f, GLuint index) {
+	f(index);
+}
+
+static void glFinish(_glFinish f) {
+	f();
+}
+
+static void glFlush(_glFlush f) {
+	f();
+}
+
+static void glFramebufferRenderbuffer(_glFramebufferRenderbuffer f, GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) {
+	f(target, attachment, renderbuffertarget, renderbuffer);
+}
+
+static void glFramebufferTexture2D(_glFramebufferTexture2D f, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) {
+	f(target, attachment, textarget, texture, level);
+}
+
+static void glGenBuffers(_glGenBuffers f, GLsizei n, GLuint *buffers) {
+	f(n, buffers);
+}
+
+static void glGenerateMipmap(_glGenerateMipmap f, GLenum target) {
+	f(target);
+}
+
+static void glGenFramebuffers(_glGenFramebuffers f, GLsizei n, GLuint *framebuffers) {
+	f(n, framebuffers);
+}
+
+static void glGenRenderbuffers(_glGenRenderbuffers f, GLsizei n, GLuint *renderbuffers) {
+	f(n, renderbuffers);
+}
+
+static void glGenTextures(_glGenTextures f, GLsizei n, GLuint *textures) {
+	f(n, textures);
+}
+
+static GLenum glGetError(_glGetError f) {
+	return f();
+}
+
+static void glGetFramebufferAttachmentParameteriv(_glGetFramebufferAttachmentParameteriv f, GLenum target, GLenum attachment, GLenum pname, GLint *params) {
+	f(target, attachment, pname, params);
+}
+
+static void glGetIntegerv(_glGetIntegerv f, GLenum pname, GLint *data) {
+	f(pname, data);
+}
+
+static void glGetFloatv(_glGetFloatv f, GLenum pname, GLfloat *data) {
+	f(pname, data);
+}
+
+static void glGetIntegeri_v(_glGetIntegeri_v f, GLenum pname, GLuint idx, GLint *data) {
+	f(pname, idx, data);
+}
+
+static void glGetProgramiv(_glGetProgramiv f, GLuint program, GLenum pname, GLint *params) {
+	f(program, pname, params);
+}
+
+static void glGetProgramInfoLog(_glGetProgramInfoLog f, GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog) {
+	f(program, bufSize, length, infoLog);
+}
+
+static void glGetRenderbufferParameteriv(_glGetRenderbufferParameteriv f, GLenum target, GLenum pname, GLint *params) {
+	f(target, pname, params);
+}
+
+static void glGetShaderiv(_glGetShaderiv f, GLuint shader, GLenum pname, GLint *params) {
+	f(shader, pname, params);
+}
+
+static void glGetShaderInfoLog(_glGetShaderInfoLog f, GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog) {
+	f(shader, bufSize, length, infoLog);
+}
+
+static const GLubyte *glGetString(_glGetString f, GLenum name) {
+	return f(name);
+}
+
+static GLint glGetUniformLocation(_glGetUniformLocation f, GLuint program, const GLchar *name) {
+	return f(program, name);
+}
+
+static void glGetVertexAttribiv(_glGetVertexAttribiv f, GLuint index, GLenum pname, GLint *data) {
+	f(index, pname, data);
+}
+
+// Return uintptr_t to avoid Cgo pointer check.
+static uintptr_t glGetVertexAttribPointerv(_glGetVertexAttribPointerv f, GLuint index, GLenum pname) {
+	void *ptrs;
+	f(index, pname, &ptrs);
+	return (uintptr_t)ptrs;
+}
+
+static GLboolean glIsEnabled(_glIsEnabled f, GLenum cap) {
+	return f(cap);
+}
+
+static void glLinkProgram(_glLinkProgram f, GLuint program) {
+	f(program);
+}
+
+static void glPixelStorei(_glPixelStorei f, GLenum pname, GLint param) {
+	f(pname, param);
+}
+
+static void glReadPixels(_glReadPixels f, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels) {
+	f(x, y, width, height, format, type, pixels);
+}
+
+static void glRenderbufferStorage(_glRenderbufferStorage f, GLenum target, GLenum internalformat, GLsizei width, GLsizei height) {
+	f(target, internalformat, width, height);
+}
+
+static void glScissor(_glScissor f, GLint x, GLint y, GLsizei width, GLsizei height) {
+	f(x, y, width, height);
+}
+
+static void glShaderSource(_glShaderSource f, GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length) {
+	f(shader, count, string, length);
+}
+
+static void glTexImage2D(_glTexImage2D f, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) {
+	f(target, level, internalformat, width, height, border, format, type, pixels);
+}
+
+static void glTexParameteri(_glTexParameteri f, GLenum target, GLenum pname, GLint param) {
+	f(target, pname, param);
+}
+
+static void glTexSubImage2D(_glTexSubImage2D f, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) {
+	f(target, level, xoffset, yoffset, width, height, format, type, pixels);
+}
+
+static void glUniform1f(_glUniform1f f, GLint location, GLfloat v0) {
+	f(location, v0);
+}
+
+static void glUniform1i(_glUniform1i f, GLint location, GLint v0) {
+	f(location, v0);
+}
+
+static void glUniform2f(_glUniform2f f, GLint location, GLfloat v0, GLfloat v1) {
+	f(location, v0, v1);
+}
+
+static void glUniform3f(_glUniform3f f, GLint location, GLfloat v0, GLfloat v1, GLfloat v2) {
+	f(location, v0, v1, v2);
+}
+
+static void glUniform4f(_glUniform4f f, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {
+	f(location, v0, v1, v2, v3);
+}
+
+static void glUseProgram(_glUseProgram f, GLuint program) {
+	f(program);
+}
+
+// offset is defined as an uintptr_t to omit Cgo pointer checks.
+static void glVertexAttribPointer(_glVertexAttribPointer f, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, uintptr_t offset) {
+	f(index, size, type, normalized, stride, (const void *)offset);
+}
+
+static void glViewport(_glViewport f, GLint x, GLint y, GLsizei width, GLsizei height) {
+	f(x, y, width, height);
+}
+
+static void glBindBufferBase(_glBindBufferBase f, GLenum target, GLuint index, GLuint buffer) {
+	f(target, index, buffer);
+}
+
+static void glUniformBlockBinding(_glUniformBlockBinding f, GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding) {
+	f(program, uniformBlockIndex, uniformBlockBinding);
+}
+
+static GLuint glGetUniformBlockIndex(_glGetUniformBlockIndex f, GLuint program, const GLchar *uniformBlockName) {
+	return f(program, uniformBlockName);
+}
+
+static void glInvalidateFramebuffer(_glInvalidateFramebuffer f, GLenum target, GLenum attachment) {
+	// Framebuffer invalidation is just a hint and can safely be ignored.
+	if (f != NULL) {
+		f(target, 1, &attachment);
+	}
+}
+
+static void glBeginQuery(_glBeginQuery f, GLenum target, GLenum attachment) {
+	f(target, attachment);
+}
+
+static void glDeleteQueries(_glDeleteQueries f, GLsizei n, const GLuint *ids) {
+	f(n, ids);
+}
+
+static void glDeleteVertexArrays(_glDeleteVertexArrays f, GLsizei n, const GLuint *ids) {
+	f(n, ids);
+}
+
+static void glEndQuery(_glEndQuery f, GLenum target) {
+	f(target);
+}
+
+static const GLubyte* glGetStringi(_glGetStringi f, GLenum name, GLuint index) {
+	return f(name, index);
+}
+
+static void glGenQueries(_glGenQueries f, GLsizei n, GLuint *ids) {
+	f(n, ids);
+}
+
+static void glGenVertexArrays(_glGenVertexArrays f, GLsizei n, GLuint *ids) {
+	f(n, ids);
+}
+
+static void glGetProgramBinary(_glGetProgramBinary f, GLuint program, GLsizei bufsize, GLsizei *length, GLenum *binaryFormat, void *binary) {
+	f(program, bufsize, length, binaryFormat, binary);
+}
+
+static void glGetQueryObjectuiv(_glGetQueryObjectuiv f, GLuint id, GLenum pname, GLuint *params) {
+	f(id, pname, params);
+}
+
+static void glMemoryBarrier(_glMemoryBarrier f, GLbitfield barriers) {
+	f(barriers);
+}
+
+static void glDispatchCompute(_glDispatchCompute f, GLuint x, GLuint y, GLuint z) {
+	f(x, y, z);
+}
+
+static void *glMapBufferRange(_glMapBufferRange f, GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access) {
+	return f(target, offset, length, access);
+}
+
+static GLboolean glUnmapBuffer(_glUnmapBuffer f, GLenum target) {
+	return f(target);
+}
+
+static void glBindImageTexture(_glBindImageTexture f, GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format) {
+	f(unit, texture, level, layered, layer, access, format);
+}
+
+static void glTexStorage2D(_glTexStorage2D f, GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height) {
+	f(target, levels, internalFormat, width, height);
+}
+
+static void glBlitFramebuffer(_glBlitFramebuffer f, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) {
+	f(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
+}
+*/
+import "C"
+
+type Context interface{}
+
+type Functions struct {
+	// Query caches.
+	uints  [100]C.GLuint
+	ints   [100]C.GLint
+	floats [100]C.GLfloat
+
+	glActiveTexture                       C._glActiveTexture
+	glAttachShader                        C._glAttachShader
+	glBindAttribLocation                  C._glBindAttribLocation
+	glBindBuffer                          C._glBindBuffer
+	glBindFramebuffer                     C._glBindFramebuffer
+	glBindRenderbuffer                    C._glBindRenderbuffer
+	glBindTexture                         C._glBindTexture
+	glBlendEquation                       C._glBlendEquation
+	glBlendFuncSeparate                   C._glBlendFuncSeparate
+	glBufferData                          C._glBufferData
+	glBufferSubData                       C._glBufferSubData
+	glCheckFramebufferStatus              C._glCheckFramebufferStatus
+	glClear                               C._glClear
+	glClearColor                          C._glClearColor
+	glClearDepthf                         C._glClearDepthf
+	glCompileShader                       C._glCompileShader
+	glCopyTexSubImage2D                   C._glCopyTexSubImage2D
+	glCreateProgram                       C._glCreateProgram
+	glCreateShader                        C._glCreateShader
+	glDeleteBuffers                       C._glDeleteBuffers
+	glDeleteFramebuffers                  C._glDeleteFramebuffers
+	glDeleteProgram                       C._glDeleteProgram
+	glDeleteRenderbuffers                 C._glDeleteRenderbuffers
+	glDeleteShader                        C._glDeleteShader
+	glDeleteTextures                      C._glDeleteTextures
+	glDepthFunc                           C._glDepthFunc
+	glDepthMask                           C._glDepthMask
+	glDisable                             C._glDisable
+	glDisableVertexAttribArray            C._glDisableVertexAttribArray
+	glDrawArrays                          C._glDrawArrays
+	glDrawElements                        C._glDrawElements
+	glEnable                              C._glEnable
+	glEnableVertexAttribArray             C._glEnableVertexAttribArray
+	glFinish                              C._glFinish
+	glFlush                               C._glFlush
+	glFramebufferRenderbuffer             C._glFramebufferRenderbuffer
+	glFramebufferTexture2D                C._glFramebufferTexture2D
+	glGenBuffers                          C._glGenBuffers
+	glGenerateMipmap                      C._glGenerateMipmap
+	glGenFramebuffers                     C._glGenFramebuffers
+	glGenRenderbuffers                    C._glGenRenderbuffers
+	glGenTextures                         C._glGenTextures
+	glGetError                            C._glGetError
+	glGetFramebufferAttachmentParameteriv C._glGetFramebufferAttachmentParameteriv
+	glGetFloatv                           C._glGetFloatv
+	glGetIntegerv                         C._glGetIntegerv
+	glGetIntegeri_v                       C._glGetIntegeri_v
+	glGetProgramiv                        C._glGetProgramiv
+	glGetProgramInfoLog                   C._glGetProgramInfoLog
+	glGetRenderbufferParameteriv          C._glGetRenderbufferParameteriv
+	glGetShaderiv                         C._glGetShaderiv
+	glGetShaderInfoLog                    C._glGetShaderInfoLog
+	glGetString                           C._glGetString
+	glGetUniformLocation                  C._glGetUniformLocation
+	glGetVertexAttribiv                   C._glGetVertexAttribiv
+	glGetVertexAttribPointerv             C._glGetVertexAttribPointerv
+	glIsEnabled                           C._glIsEnabled
+	glLinkProgram                         C._glLinkProgram
+	glPixelStorei                         C._glPixelStorei
+	glReadPixels                          C._glReadPixels
+	glRenderbufferStorage                 C._glRenderbufferStorage
+	glScissor                             C._glScissor
+	glShaderSource                        C._glShaderSource
+	glTexImage2D                          C._glTexImage2D
+	glTexParameteri                       C._glTexParameteri
+	glTexSubImage2D                       C._glTexSubImage2D
+	glUniform1f                           C._glUniform1f
+	glUniform1i                           C._glUniform1i
+	glUniform2f                           C._glUniform2f
+	glUniform3f                           C._glUniform3f
+	glUniform4f                           C._glUniform4f
+	glUseProgram                          C._glUseProgram
+	glVertexAttribPointer                 C._glVertexAttribPointer
+	glViewport                            C._glViewport
+	glBindVertexArray                     C._glBindVertexArray
+	glBindBufferBase                      C._glBindBufferBase
+	glGetUniformBlockIndex                C._glGetUniformBlockIndex
+	glUniformBlockBinding                 C._glUniformBlockBinding
+	glInvalidateFramebuffer               C._glInvalidateFramebuffer
+	glBeginQuery                          C._glBeginQuery
+	glDeleteQueries                       C._glDeleteQueries
+	glDeleteVertexArrays                  C._glDeleteVertexArrays
+	glEndQuery                            C._glEndQuery
+	glGenQueries                          C._glGenQueries
+	glGenVertexArrays                     C._glGenVertexArrays
+	glGetProgramBinary                    C._glGetProgramBinary
+	glGetQueryObjectuiv                   C._glGetQueryObjectuiv
+	glGetStringi                          C._glGetStringi
+	glDispatchCompute                     C._glDispatchCompute
+	glMemoryBarrier                       C._glMemoryBarrier
+	glMapBufferRange                      C._glMapBufferRange
+	glUnmapBuffer                         C._glUnmapBuffer
+	glBindImageTexture                    C._glBindImageTexture
+	glTexStorage2D                        C._glTexStorage2D
+	glBlitFramebuffer                     C._glBlitFramebuffer
+}
+
+func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
+	if ctx != nil {
+		panic("non-nil context")
+	}
+	f := new(Functions)
+	err := f.load(forceES)
+	if err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+func dlsym(handle unsafe.Pointer, s string) unsafe.Pointer {
+	cs := C.CString(s)
+	defer C.free(unsafe.Pointer(cs))
+	return C.dlsym(handle, cs)
+}
+
+func dlopen(lib string) unsafe.Pointer {
+	clib := C.CString(lib)
+	defer C.free(unsafe.Pointer(clib))
+	return C.dlopen(clib, C.RTLD_NOW|C.RTLD_LOCAL)
+}
+
+func (f *Functions) load(forceES bool) error {
+	var (
+		loadErr  error
+		libNames []string
+		handles  []unsafe.Pointer
+	)
+	switch {
+	case runtime.GOOS == "darwin" && !forceES:
+		libNames = []string{"/System/Library/Frameworks/OpenGL.framework/OpenGL"}
+	case runtime.GOOS == "darwin" && forceES:
+		libNames = []string{"libGLESv2.dylib"}
+	case runtime.GOOS == "ios":
+		libNames = []string{"/System/Library/Frameworks/OpenGLES.framework/OpenGLES"}
+	case runtime.GOOS == "android":
+		libNames = []string{"libGLESv2.so", "libGLESv3.so"}
+	default:
+		libNames = []string{"libGLESv2.so.2", "libGLESv2.so.3.0"}
+	}
+	for _, lib := range libNames {
+		if h := dlopen(lib); h != nil {
+			handles = append(handles, h)
+		}
+	}
+	if len(handles) == 0 {
+		return fmt.Errorf("gl: no OpenGL implementation could be loaded (tried %q)", libNames)
+	}
+	load := func(s string) *[0]byte {
+		for _, h := range handles {
+			if f := dlsym(h, s); f != nil {
+				return (*[0]byte)(f)
+			}
+		}
+		return nil
+	}
+	must := func(s string) *[0]byte {
+		ptr := load(s)
+		if ptr == nil {
+			loadErr = fmt.Errorf("gl: failed to load symbol %q", s)
+		}
+		return ptr
+	}
+	// GL ES 2.0 functions.
+	f.glActiveTexture = must("glActiveTexture")
+	f.glAttachShader = must("glAttachShader")
+	f.glBindAttribLocation = must("glBindAttribLocation")
+	f.glBindBuffer = must("glBindBuffer")
+	f.glBindFramebuffer = must("glBindFramebuffer")
+	f.glBindRenderbuffer = must("glBindRenderbuffer")
+	f.glBindTexture = must("glBindTexture")
+	f.glBlendEquation = must("glBlendEquation")
+	f.glBlendFuncSeparate = must("glBlendFuncSeparate")
+	f.glBufferData = must("glBufferData")
+	f.glBufferSubData = must("glBufferSubData")
+	f.glCheckFramebufferStatus = must("glCheckFramebufferStatus")
+	f.glClear = must("glClear")
+	f.glClearColor = must("glClearColor")
+	f.glClearDepthf = must("glClearDepthf")
+	f.glCompileShader = must("glCompileShader")
+	f.glCopyTexSubImage2D = must("glCopyTexSubImage2D")
+	f.glCreateProgram = must("glCreateProgram")
+	f.glCreateShader = must("glCreateShader")
+	f.glDeleteBuffers = must("glDeleteBuffers")
+	f.glDeleteFramebuffers = must("glDeleteFramebuffers")
+	f.glDeleteProgram = must("glDeleteProgram")
+	f.glDeleteRenderbuffers = must("glDeleteRenderbuffers")
+	f.glDeleteShader = must("glDeleteShader")
+	f.glDeleteTextures = must("glDeleteTextures")
+	f.glDepthFunc = must("glDepthFunc")
+	f.glDepthMask = must("glDepthMask")
+	f.glDisable = must("glDisable")
+	f.glDisableVertexAttribArray = must("glDisableVertexAttribArray")
+	f.glDrawArrays = must("glDrawArrays")
+	f.glDrawElements = must("glDrawElements")
+	f.glEnable = must("glEnable")
+	f.glEnableVertexAttribArray = must("glEnableVertexAttribArray")
+	f.glFinish = must("glFinish")
+	f.glFlush = must("glFlush")
+	f.glFramebufferRenderbuffer = must("glFramebufferRenderbuffer")
+	f.glFramebufferTexture2D = must("glFramebufferTexture2D")
+	f.glGenBuffers = must("glGenBuffers")
+	f.glGenerateMipmap = must("glGenerateMipmap")
+	f.glGenFramebuffers = must("glGenFramebuffers")
+	f.glGenRenderbuffers = must("glGenRenderbuffers")
+	f.glGenTextures = must("glGenTextures")
+	f.glGetError = must("glGetError")
+	f.glGetFramebufferAttachmentParameteriv = must("glGetFramebufferAttachmentParameteriv")
+	f.glGetIntegerv = must("glGetIntegerv")
+	f.glGetFloatv = must("glGetFloatv")
+	f.glGetProgramiv = must("glGetProgramiv")
+	f.glGetProgramInfoLog = must("glGetProgramInfoLog")
+	f.glGetRenderbufferParameteriv = must("glGetRenderbufferParameteriv")
+	f.glGetShaderiv = must("glGetShaderiv")
+	f.glGetShaderInfoLog = must("glGetShaderInfoLog")
+	f.glGetString = must("glGetString")
+	f.glGetUniformLocation = must("glGetUniformLocation")
+	f.glGetVertexAttribiv = must("glGetVertexAttribiv")
+	f.glGetVertexAttribPointerv = must("glGetVertexAttribPointerv")
+	f.glIsEnabled = must("glIsEnabled")
+	f.glLinkProgram = must("glLinkProgram")
+	f.glPixelStorei = must("glPixelStorei")
+	f.glReadPixels = must("glReadPixels")
+	f.glRenderbufferStorage = must("glRenderbufferStorage")
+	f.glScissor = must("glScissor")
+	f.glShaderSource = must("glShaderSource")
+	f.glTexImage2D = must("glTexImage2D")
+	f.glTexParameteri = must("glTexParameteri")
+	f.glTexSubImage2D = must("glTexSubImage2D")
+	f.glUniform1f = must("glUniform1f")
+	f.glUniform1i = must("glUniform1i")
+	f.glUniform2f = must("glUniform2f")
+	f.glUniform3f = must("glUniform3f")
+	f.glUniform4f = must("glUniform4f")
+	f.glUseProgram = must("glUseProgram")
+	f.glVertexAttribPointer = must("glVertexAttribPointer")
+	f.glViewport = must("glViewport")
+
+	// Extensions and GL ES 3 functions.
+	f.glBindBufferBase = load("glBindBufferBase")
+	f.glBindVertexArray = load("glBindVertexArray")
+	f.glGetIntegeri_v = load("glGetIntegeri_v")
+	f.glGetUniformBlockIndex = load("glGetUniformBlockIndex")
+	f.glUniformBlockBinding = load("glUniformBlockBinding")
+	f.glInvalidateFramebuffer = load("glInvalidateFramebuffer")
+	f.glGetStringi = load("glGetStringi")
+	// Fall back to EXT_invalidate_framebuffer if available.
+	if f.glInvalidateFramebuffer == nil {
+		f.glInvalidateFramebuffer = load("glDiscardFramebufferEXT")
+	}
+
+	f.glBeginQuery = load("glBeginQuery")
+	if f.glBeginQuery == nil {
+		f.glBeginQuery = load("glBeginQueryEXT")
+	}
+	f.glDeleteQueries = load("glDeleteQueries")
+	if f.glDeleteQueries == nil {
+		f.glDeleteQueries = load("glDeleteQueriesEXT")
+	}
+	f.glEndQuery = load("glEndQuery")
+	if f.glEndQuery == nil {
+		f.glEndQuery = load("glEndQueryEXT")
+	}
+	f.glGenQueries = load("glGenQueries")
+	if f.glGenQueries == nil {
+		f.glGenQueries = load("glGenQueriesEXT")
+	}
+	f.glGetQueryObjectuiv = load("glGetQueryObjectuiv")
+	if f.glGetQueryObjectuiv == nil {
+		f.glGetQueryObjectuiv = load("glGetQueryObjectuivEXT")
+	}
+
+	f.glDeleteVertexArrays = load("glDeleteVertexArrays")
+	f.glGenVertexArrays = load("glGenVertexArrays")
+	f.glMemoryBarrier = load("glMemoryBarrier")
+	f.glDispatchCompute = load("glDispatchCompute")
+	f.glMapBufferRange = load("glMapBufferRange")
+	f.glUnmapBuffer = load("glUnmapBuffer")
+	f.glBindImageTexture = load("glBindImageTexture")
+	f.glTexStorage2D = load("glTexStorage2D")
+	f.glBlitFramebuffer = load("glBlitFramebuffer")
+	f.glGetProgramBinary = load("glGetProgramBinary")
+
+	return loadErr
+}
+
+func (f *Functions) ActiveTexture(texture Enum) {
+	C.glActiveTexture(f.glActiveTexture, C.GLenum(texture))
+}
+
+func (f *Functions) AttachShader(p Program, s Shader) {
+	C.glAttachShader(f.glAttachShader, C.GLuint(p.V), C.GLuint(s.V))
+}
+
+func (f *Functions) BeginQuery(target Enum, query Query) {
+	C.glBeginQuery(f.glBeginQuery, C.GLenum(target), C.GLenum(query.V))
+}
+
+func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+	C.glBindAttribLocation(f.glBindAttribLocation, C.GLuint(p.V), C.GLuint(a), cname)
+}
+
+func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
+	C.glBindBufferBase(f.glBindBufferBase, C.GLenum(target), C.GLuint(index), C.GLuint(b.V))
+}
+
+func (f *Functions) BindBuffer(target Enum, b Buffer) {
+	C.glBindBuffer(f.glBindBuffer, C.GLenum(target), C.GLuint(b.V))
+}
+
+func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
+	C.glBindFramebuffer(f.glBindFramebuffer, C.GLenum(target), C.GLuint(fb.V))
+}
+
+func (f *Functions) BindRenderbuffer(target Enum, fb Renderbuffer) {
+	C.glBindRenderbuffer(f.glBindRenderbuffer, C.GLenum(target), C.GLuint(fb.V))
+}
+
+func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
+	l := C.GLboolean(FALSE)
+	if layered {
+		l = TRUE
+	}
+	C.glBindImageTexture(f.glBindImageTexture, C.GLuint(unit), C.GLuint(t.V), C.GLint(level), l, C.GLint(layer), C.GLenum(access), C.GLenum(format))
+}
+
+func (f *Functions) BindTexture(target Enum, t Texture) {
+	C.glBindTexture(f.glBindTexture, C.GLenum(target), C.GLuint(t.V))
+}
+
+func (f *Functions) BindVertexArray(a VertexArray) {
+	C.glBindVertexArray(f.glBindVertexArray, C.GLuint(a.V))
+}
+
+func (f *Functions) BlendEquation(mode Enum) {
+	C.glBlendEquation(f.glBlendEquation, C.GLenum(mode))
+}
+
+func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
+	C.glBlendFuncSeparate(f.glBlendFuncSeparate, C.GLenum(srcRGB), C.GLenum(dstRGB), C.GLenum(srcA), C.GLenum(dstA))
+}
+
+func (f *Functions) BlitFramebuffer(sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1 int, mask Enum, filter Enum) {
+	C.glBlitFramebuffer(f.glBlitFramebuffer,
+		C.GLint(sx0), C.GLint(sy0), C.GLint(sx1), C.GLint(sy1),
+		C.GLint(dx0), C.GLint(dy0), C.GLint(dx1), C.GLint(dy1),
+		C.GLenum(mask), C.GLenum(filter),
+	)
+}
+
+func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
+	var p unsafe.Pointer
+	if len(data) > 0 {
+		p = unsafe.Pointer(&data[0])
+	}
+	C.glBufferData(f.glBufferData, C.GLenum(target), C.GLsizeiptr(size), p, C.GLenum(usage))
+}
+
+func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
+	var p unsafe.Pointer
+	if len(src) > 0 {
+		p = unsafe.Pointer(&src[0])
+	}
+	C.glBufferSubData(f.glBufferSubData, C.GLenum(target), C.GLintptr(offset), C.GLsizeiptr(len(src)), p)
+}
+
+func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
+	return Enum(C.glCheckFramebufferStatus(f.glCheckFramebufferStatus, C.GLenum(target)))
+}
+
+func (f *Functions) Clear(mask Enum) {
+	C.glClear(f.glClear, C.GLbitfield(mask))
+}
+
+func (f *Functions) ClearColor(red float32, green float32, blue float32, alpha float32) {
+	C.glClearColor(f.glClearColor, C.GLfloat(red), C.GLfloat(green), C.GLfloat(blue), C.GLfloat(alpha))
+}
+
+func (f *Functions) ClearDepthf(d float32) {
+	C.glClearDepthf(f.glClearDepthf, C.GLfloat(d))
+}
+
+func (f *Functions) CompileShader(s Shader) {
+	C.glCompileShader(f.glCompileShader, C.GLuint(s.V))
+}
+
+func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
+	C.glCopyTexSubImage2D(f.glCopyTexSubImage2D, C.GLenum(target), C.GLint(level), C.GLint(xoffset), C.GLint(yoffset), C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
+}
+
+func (f *Functions) CreateBuffer() Buffer {
+	C.glGenBuffers(f.glGenBuffers, 1, &f.uints[0])
+	return Buffer{uint(f.uints[0])}
+}
+
+func (f *Functions) CreateFramebuffer() Framebuffer {
+	C.glGenFramebuffers(f.glGenFramebuffers, 1, &f.uints[0])
+	return Framebuffer{uint(f.uints[0])}
+}
+
+func (f *Functions) CreateProgram() Program {
+	return Program{uint(C.glCreateProgram(f.glCreateProgram))}
+}
+
+func (f *Functions) CreateQuery() Query {
+	C.glGenQueries(f.glGenQueries, 1, &f.uints[0])
+	return Query{uint(f.uints[0])}
+}
+
+func (f *Functions) CreateRenderbuffer() Renderbuffer {
+	C.glGenRenderbuffers(f.glGenRenderbuffers, 1, &f.uints[0])
+	return Renderbuffer{uint(f.uints[0])}
+}
+
+func (f *Functions) CreateShader(ty Enum) Shader {
+	return Shader{uint(C.glCreateShader(f.glCreateShader, C.GLenum(ty)))}
+}
+
+func (f *Functions) CreateTexture() Texture {
+	C.glGenTextures(f.glGenTextures, 1, &f.uints[0])
+	return Texture{uint(f.uints[0])}
+}
+
+func (f *Functions) CreateVertexArray() VertexArray {
+	C.glGenVertexArrays(f.glGenVertexArrays, 1, &f.uints[0])
+	return VertexArray{uint(f.uints[0])}
+}
+
+func (f *Functions) DeleteBuffer(v Buffer) {
+	f.uints[0] = C.GLuint(v.V)
+	C.glDeleteBuffers(f.glDeleteBuffers, 1, &f.uints[0])
+}
+
+func (f *Functions) DeleteFramebuffer(v Framebuffer) {
+	f.uints[0] = C.GLuint(v.V)
+	C.glDeleteFramebuffers(f.glDeleteFramebuffers, 1, &f.uints[0])
+}
+
+func (f *Functions) DeleteProgram(p Program) {
+	C.glDeleteProgram(f.glDeleteProgram, C.GLuint(p.V))
+}
+
+func (f *Functions) DeleteQuery(query Query) {
+	f.uints[0] = C.GLuint(query.V)
+	C.glDeleteQueries(f.glDeleteQueries, 1, &f.uints[0])
+}
+
+func (f *Functions) DeleteVertexArray(array VertexArray) {
+	f.uints[0] = C.GLuint(array.V)
+	C.glDeleteVertexArrays(f.glDeleteVertexArrays, 1, &f.uints[0])
+}
+
+func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
+	f.uints[0] = C.GLuint(v.V)
+	C.glDeleteRenderbuffers(f.glDeleteRenderbuffers, 1, &f.uints[0])
+}
+
+func (f *Functions) DeleteShader(s Shader) {
+	C.glDeleteShader(f.glDeleteShader, C.GLuint(s.V))
+}
+
+func (f *Functions) DeleteTexture(v Texture) {
+	f.uints[0] = C.GLuint(v.V)
+	C.glDeleteTextures(f.glDeleteTextures, 1, &f.uints[0])
+}
+
+func (f *Functions) DepthFunc(v Enum) {
+	C.glDepthFunc(f.glDepthFunc, C.GLenum(v))
+}
+
+func (f *Functions) DepthMask(mask bool) {
+	m := C.GLboolean(FALSE)
+	if mask {
+		m = C.GLboolean(TRUE)
+	}
+	C.glDepthMask(f.glDepthMask, m)
+}
+
+func (f *Functions) DisableVertexAttribArray(a Attrib) {
+	C.glDisableVertexAttribArray(f.glDisableVertexAttribArray, C.GLuint(a))
+}
+
+func (f *Functions) Disable(cap Enum) {
+	C.glDisable(f.glDisable, C.GLenum(cap))
+}
+
+func (f *Functions) DrawArrays(mode Enum, first int, count int) {
+	C.glDrawArrays(f.glDrawArrays, C.GLenum(mode), C.GLint(first), C.GLsizei(count))
+}
+
+func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
+	C.glDrawElements(f.glDrawElements, C.GLenum(mode), C.GLsizei(count), C.GLenum(ty), C.uintptr_t(offset))
+}
+
+func (f *Functions) DispatchCompute(x, y, z int) {
+	C.glDispatchCompute(f.glDispatchCompute, C.GLuint(x), C.GLuint(y), C.GLuint(z))
+}
+
+func (f *Functions) Enable(cap Enum) {
+	C.glEnable(f.glEnable, C.GLenum(cap))
+}
+
+func (f *Functions) EndQuery(target Enum) {
+	C.glEndQuery(f.glEndQuery, C.GLenum(target))
+}
+
+func (f *Functions) EnableVertexAttribArray(a Attrib) {
+	C.glEnableVertexAttribArray(f.glEnableVertexAttribArray, C.GLuint(a))
+}
+
+func (f *Functions) Finish() {
+	C.glFinish(f.glFinish)
+}
+
+func (f *Functions) Flush() {
+	C.glFlush(f.glFinish)
+}
+
+func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
+	C.glFramebufferRenderbuffer(f.glFramebufferRenderbuffer, C.GLenum(target), C.GLenum(attachment), C.GLenum(renderbuffertarget), C.GLuint(renderbuffer.V))
+}
+
+func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
+	C.glFramebufferTexture2D(f.glFramebufferTexture2D, C.GLenum(target), C.GLenum(attachment), C.GLenum(texTarget), C.GLuint(t.V), C.GLint(level))
+}
+
+func (f *Functions) GenerateMipmap(target Enum) {
+	C.glGenerateMipmap(f.glGenerateMipmap, C.GLenum(target))
+}
+
+func (c *Functions) GetBinding(pname Enum) Object {
+	return Object{uint(c.GetInteger(pname))}
+}
+
+func (c *Functions) GetBindingi(pname Enum, idx int) Object {
+	return Object{uint(c.GetIntegeri(pname, idx))}
+}
+
+func (f *Functions) GetError() Enum {
+	return Enum(C.glGetError(f.glGetError))
+}
+
+func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
+	C.glGetRenderbufferParameteriv(f.glGetRenderbufferParameteriv, C.GLenum(target), C.GLenum(pname), &f.ints[0])
+	return int(f.ints[0])
+}
+
+func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
+	C.glGetFramebufferAttachmentParameteriv(f.glGetFramebufferAttachmentParameteriv, C.GLenum(target), C.GLenum(attachment), C.GLenum(pname), &f.ints[0])
+	return int(f.ints[0])
+}
+
+func (f *Functions) GetFloat4(pname Enum) [4]float32 {
+	C.glGetFloatv(f.glGetFloatv, C.GLenum(pname), &f.floats[0])
+	var r [4]float32
+	for i := range r {
+		r[i] = float32(f.floats[i])
+	}
+	return r
+}
+
+func (f *Functions) GetFloat(pname Enum) float32 {
+	C.glGetFloatv(f.glGetFloatv, C.GLenum(pname), &f.floats[0])
+	return float32(f.floats[0])
+}
+
+func (f *Functions) GetInteger4(pname Enum) [4]int {
+	C.glGetIntegerv(f.glGetIntegerv, C.GLenum(pname), &f.ints[0])
+	var r [4]int
+	for i := range r {
+		r[i] = int(f.ints[i])
+	}
+	return r
+}
+
+func (f *Functions) GetInteger(pname Enum) int {
+	C.glGetIntegerv(f.glGetIntegerv, C.GLenum(pname), &f.ints[0])
+	return int(f.ints[0])
+}
+
+func (f *Functions) GetIntegeri(pname Enum, idx int) int {
+	C.glGetIntegeri_v(f.glGetIntegeri_v, C.GLenum(pname), C.GLuint(idx), &f.ints[0])
+	return int(f.ints[0])
+}
+
+func (f *Functions) GetProgrami(p Program, pname Enum) int {
+	C.glGetProgramiv(f.glGetProgramiv, C.GLuint(p.V), C.GLenum(pname), &f.ints[0])
+	return int(f.ints[0])
+}
+
+func (f *Functions) GetProgramBinary(p Program) []byte {
+	sz := f.GetProgrami(p, PROGRAM_BINARY_LENGTH)
+	if sz == 0 {
+		return nil
+	}
+	buf := make([]byte, sz)
+	var format C.GLenum
+	C.glGetProgramBinary(f.glGetProgramBinary, C.GLuint(p.V), C.GLsizei(sz), nil, &format, unsafe.Pointer(&buf[0]))
+	return buf
+}
+
+func (f *Functions) GetProgramInfoLog(p Program) string {
+	n := f.GetProgrami(p, INFO_LOG_LENGTH)
+	buf := make([]byte, n)
+	C.glGetProgramInfoLog(f.glGetProgramInfoLog, C.GLuint(p.V), C.GLsizei(len(buf)), nil, (*C.GLchar)(unsafe.Pointer(&buf[0])))
+	return string(buf)
+}
+
+func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
+	C.glGetQueryObjectuiv(f.glGetQueryObjectuiv, C.GLuint(query.V), C.GLenum(pname), &f.uints[0])
+	return uint(f.uints[0])
+}
+
+func (f *Functions) GetShaderi(s Shader, pname Enum) int {
+	C.glGetShaderiv(f.glGetShaderiv, C.GLuint(s.V), C.GLenum(pname), &f.ints[0])
+	return int(f.ints[0])
+}
+
+func (f *Functions) GetShaderInfoLog(s Shader) string {
+	n := f.GetShaderi(s, INFO_LOG_LENGTH)
+	buf := make([]byte, n)
+	C.glGetShaderInfoLog(f.glGetShaderInfoLog, C.GLuint(s.V), C.GLsizei(len(buf)), nil, (*C.GLchar)(unsafe.Pointer(&buf[0])))
+	return string(buf)
+}
+
+func (f *Functions) getStringi(pname Enum, index int) string {
+	str := C.glGetStringi(f.glGetStringi, C.GLenum(pname), C.GLuint(index))
+	if str == nil {
+		return ""
+	}
+	return C.GoString((*C.char)(unsafe.Pointer(str)))
+}
+
+func (f *Functions) GetString(pname Enum) string {
+	switch {
+	case runtime.GOOS == "darwin" && pname == EXTENSIONS:
+		// macOS OpenGL 3 core profile doesn't support glGetString(GL_EXTENSIONS).
+		// Use glGetStringi(GL_EXTENSIONS, <index>).
+		var exts []string
+		nexts := f.GetInteger(NUM_EXTENSIONS)
+		for i := 0; i < nexts; i++ {
+			ext := f.getStringi(EXTENSIONS, i)
+			exts = append(exts, ext)
+		}
+		return strings.Join(exts, " ")
+	default:
+		str := C.glGetString(f.glGetString, C.GLenum(pname))
+		return C.GoString((*C.char)(unsafe.Pointer(str)))
+	}
+}
+
+func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+	return uint(C.glGetUniformBlockIndex(f.glGetUniformBlockIndex, C.GLuint(p.V), cname))
+}
+
+func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+	return Uniform{int(C.glGetUniformLocation(f.glGetUniformLocation, C.GLuint(p.V), cname))}
+}
+
+func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
+	C.glGetVertexAttribiv(f.glGetVertexAttribiv, C.GLuint(index), C.GLenum(pname), &f.ints[0])
+	return int(f.ints[0])
+}
+
+func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
+	return Object{uint(f.GetVertexAttrib(index, pname))}
+}
+
+func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
+	ptr := C.glGetVertexAttribPointerv(f.glGetVertexAttribPointerv, C.GLuint(index), C.GLenum(pname))
+	return uintptr(ptr)
+}
+
+func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
+	C.glInvalidateFramebuffer(f.glInvalidateFramebuffer, C.GLenum(target), C.GLenum(attachment))
+}
+
+func (f *Functions) IsEnabled(cap Enum) bool {
+	return C.glIsEnabled(f.glIsEnabled, C.GLenum(cap)) == TRUE
+}
+
+func (f *Functions) LinkProgram(p Program) {
+	C.glLinkProgram(f.glLinkProgram, C.GLuint(p.V))
+}
+
+func (f *Functions) PixelStorei(pname Enum, param int) {
+	C.glPixelStorei(f.glPixelStorei, C.GLenum(pname), C.GLint(param))
+}
+
+func (f *Functions) MemoryBarrier(barriers Enum) {
+	C.glMemoryBarrier(f.glMemoryBarrier, C.GLbitfield(barriers))
+}
+
+func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
+	p := C.glMapBufferRange(f.glMapBufferRange, C.GLenum(target), C.GLintptr(offset), C.GLsizeiptr(length), C.GLbitfield(access))
+	if p == nil {
+		return nil
+	}
+	return (*[1 << 30]byte)(p)[:length:length]
+}
+
+func (f *Functions) Scissor(x, y, width, height int32) {
+	C.glScissor(f.glScissor, C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
+}
+
+func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
+	var p unsafe.Pointer
+	if len(data) > 0 {
+		p = unsafe.Pointer(&data[0])
+	}
+	C.glReadPixels(f.glReadPixels, C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(ty), p)
+}
+
+func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
+	C.glRenderbufferStorage(f.glRenderbufferStorage, C.GLenum(target), C.GLenum(internalformat), C.GLsizei(width), C.GLsizei(height))
+}
+
+func (f *Functions) ShaderSource(s Shader, src string) {
+	csrc := C.CString(src)
+	defer C.free(unsafe.Pointer(csrc))
+	strlen := C.GLint(len(src))
+	C.glShaderSource(f.glShaderSource, C.GLuint(s.V), 1, &csrc, &strlen)
+}
+
+func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) {
+	C.glTexImage2D(f.glTexImage2D, C.GLenum(target), C.GLint(level), C.GLint(internalFormat), C.GLsizei(width), C.GLsizei(height), 0, C.GLenum(format), C.GLenum(ty), nil)
+}
+
+func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
+	C.glTexStorage2D(f.glTexStorage2D, C.GLenum(target), C.GLsizei(levels), C.GLenum(internalFormat), C.GLsizei(width), C.GLsizei(height))
+}
+
+func (f *Functions) TexSubImage2D(target Enum, level int, x int, y int, width int, height int, format Enum, ty Enum, data []byte) {
+	var p unsafe.Pointer
+	if len(data) > 0 {
+		p = unsafe.Pointer(&data[0])
+	}
+	C.glTexSubImage2D(f.glTexSubImage2D, C.GLenum(target), C.GLint(level), C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(ty), p)
+}
+
+func (f *Functions) TexParameteri(target, pname Enum, param int) {
+	C.glTexParameteri(f.glTexParameteri, C.GLenum(target), C.GLenum(pname), C.GLint(param))
+}
+
+func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
+	C.glUniformBlockBinding(f.glUniformBlockBinding, C.GLuint(p.V), C.GLuint(uniformBlockIndex), C.GLuint(uniformBlockBinding))
+}
+
+func (f *Functions) Uniform1f(dst Uniform, v float32) {
+	C.glUniform1f(f.glUniform1f, C.GLint(dst.V), C.GLfloat(v))
+}
+
+func (f *Functions) Uniform1i(dst Uniform, v int) {
+	C.glUniform1i(f.glUniform1i, C.GLint(dst.V), C.GLint(v))
+}
+
+func (f *Functions) Uniform2f(dst Uniform, v0 float32, v1 float32) {
+	C.glUniform2f(f.glUniform2f, C.GLint(dst.V), C.GLfloat(v0), C.GLfloat(v1))
+}
+
+func (f *Functions) Uniform3f(dst Uniform, v0 float32, v1 float32, v2 float32) {
+	C.glUniform3f(f.glUniform3f, C.GLint(dst.V), C.GLfloat(v0), C.GLfloat(v1), C.GLfloat(v2))
+}
+
+func (f *Functions) Uniform4f(dst Uniform, v0 float32, v1 float32, v2 float32, v3 float32) {
+	C.glUniform4f(f.glUniform4f, C.GLint(dst.V), C.GLfloat(v0), C.GLfloat(v1), C.GLfloat(v2), C.GLfloat(v3))
+}
+
+func (f *Functions) UseProgram(p Program) {
+	C.glUseProgram(f.glUseProgram, C.GLuint(p.V))
+}
+
+func (f *Functions) UnmapBuffer(target Enum) bool {
+	r := C.glUnmapBuffer(f.glUnmapBuffer, C.GLenum(target))
+	return r == TRUE
+}
+
+func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride int, offset int) {
+	var n C.GLboolean = FALSE
+	if normalized {
+		n = TRUE
+	}
+	C.glVertexAttribPointer(f.glVertexAttribPointer, C.GLuint(dst), C.GLint(size), C.GLenum(ty), n, C.GLsizei(stride), C.uintptr_t(offset))
+}
+
+func (f *Functions) Viewport(x int, y int, width int, height int) {
+	C.glViewport(f.glViewport, C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
+}

+ 627 - 0
vendor/gioui.org/internal/gl/gl_windows.go

@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package gl
+
+import (
+	"fmt"
+	"math"
+	"runtime"
+	"sync"
+	"syscall"
+	"unsafe"
+
+	"golang.org/x/sys/windows"
+)
+
+func loadGLESv2Procs() error {
+	dllName := "libGLESv2.dll"
+	handle, err := windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
+	if err != nil {
+		return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
+	}
+	gles := windows.DLL{Handle: handle, Name: dllName}
+	// d3dcompiler_47.dll is needed internally for shader compilation to function.
+	dllName = "d3dcompiler_47.dll"
+	_, err = windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
+	if err != nil {
+		return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
+	}
+	procs := map[string]**windows.Proc{
+		"glActiveTexture":                       &_glActiveTexture,
+		"glAttachShader":                        &_glAttachShader,
+		"glBeginQuery":                          &_glBeginQuery,
+		"glBindAttribLocation":                  &_glBindAttribLocation,
+		"glBindBuffer":                          &_glBindBuffer,
+		"glBindBufferBase":                      &_glBindBufferBase,
+		"glBindFramebuffer":                     &_glBindFramebuffer,
+		"glBindRenderbuffer":                    &_glBindRenderbuffer,
+		"glBindTexture":                         &_glBindTexture,
+		"glBindVertexArray":                     &_glBindVertexArray,
+		"glBlendEquation":                       &_glBlendEquation,
+		"glBlendFuncSeparate":                   &_glBlendFuncSeparate,
+		"glBufferData":                          &_glBufferData,
+		"glBufferSubData":                       &_glBufferSubData,
+		"glCheckFramebufferStatus":              &_glCheckFramebufferStatus,
+		"glClear":                               &_glClear,
+		"glClearColor":                          &_glClearColor,
+		"glClearDepthf":                         &_glClearDepthf,
+		"glDeleteQueries":                       &_glDeleteQueries,
+		"glDeleteVertexArrays":                  &_glDeleteVertexArrays,
+		"glCompileShader":                       &_glCompileShader,
+		"glCopyTexSubImage2D":                   &_glCopyTexSubImage2D,
+		"glGenerateMipmap":                      &_glGenerateMipmap,
+		"glGenBuffers":                          &_glGenBuffers,
+		"glGenFramebuffers":                     &_glGenFramebuffers,
+		"glGenVertexArrays":                     &_glGenVertexArrays,
+		"glGetUniformBlockIndex":                &_glGetUniformBlockIndex,
+		"glCreateProgram":                       &_glCreateProgram,
+		"glGenRenderbuffers":                    &_glGenRenderbuffers,
+		"glCreateShader":                        &_glCreateShader,
+		"glGenTextures":                         &_glGenTextures,
+		"glDeleteBuffers":                       &_glDeleteBuffers,
+		"glDeleteFramebuffers":                  &_glDeleteFramebuffers,
+		"glDeleteProgram":                       &_glDeleteProgram,
+		"glDeleteShader":                        &_glDeleteShader,
+		"glDeleteRenderbuffers":                 &_glDeleteRenderbuffers,
+		"glDeleteTextures":                      &_glDeleteTextures,
+		"glDepthFunc":                           &_glDepthFunc,
+		"glDepthMask":                           &_glDepthMask,
+		"glDisableVertexAttribArray":            &_glDisableVertexAttribArray,
+		"glDisable":                             &_glDisable,
+		"glDrawArrays":                          &_glDrawArrays,
+		"glDrawElements":                        &_glDrawElements,
+		"glEnable":                              &_glEnable,
+		"glEnableVertexAttribArray":             &_glEnableVertexAttribArray,
+		"glEndQuery":                            &_glEndQuery,
+		"glFinish":                              &_glFinish,
+		"glFlush":                               &_glFlush,
+		"glFramebufferRenderbuffer":             &_glFramebufferRenderbuffer,
+		"glFramebufferTexture2D":                &_glFramebufferTexture2D,
+		"glGenQueries":                          &_glGenQueries,
+		"glGetError":                            &_glGetError,
+		"glGetRenderbufferParameteriv":          &_glGetRenderbufferParameteriv,
+		"glGetFloatv":                           &_glGetFloatv,
+		"glGetFramebufferAttachmentParameteriv": &_glGetFramebufferAttachmentParameteriv,
+		"glGetIntegerv":                         &_glGetIntegerv,
+		"glGetIntegeri_v":                       &_glGetIntegeri_v,
+		"glGetProgramiv":                        &_glGetProgramiv,
+		"glGetProgramInfoLog":                   &_glGetProgramInfoLog,
+		"glGetQueryObjectuiv":                   &_glGetQueryObjectuiv,
+		"glGetShaderiv":                         &_glGetShaderiv,
+		"glGetShaderInfoLog":                    &_glGetShaderInfoLog,
+		"glGetString":                           &_glGetString,
+		"glGetUniformLocation":                  &_glGetUniformLocation,
+		"glGetVertexAttribiv":                   &_glGetVertexAttribiv,
+		"glGetVertexAttribPointerv":             &_glGetVertexAttribPointerv,
+		"glInvalidateFramebuffer":               &_glInvalidateFramebuffer,
+		"glIsEnabled":                           &_glIsEnabled,
+		"glLinkProgram":                         &_glLinkProgram,
+		"glPixelStorei":                         &_glPixelStorei,
+		"glReadPixels":                          &_glReadPixels,
+		"glRenderbufferStorage":                 &_glRenderbufferStorage,
+		"glScissor":                             &_glScissor,
+		"glShaderSource":                        &_glShaderSource,
+		"glTexImage2D":                          &_glTexImage2D,
+		"glTexStorage2D":                        &_glTexStorage2D,
+		"glTexSubImage2D":                       &_glTexSubImage2D,
+		"glTexParameteri":                       &_glTexParameteri,
+		"glUniformBlockBinding":                 &_glUniformBlockBinding,
+		"glUniform1f":                           &_glUniform1f,
+		"glUniform1i":                           &_glUniform1i,
+		"glUniform2f":                           &_glUniform2f,
+		"glUniform3f":                           &_glUniform3f,
+		"glUniform4f":                           &_glUniform4f,
+		"glUseProgram":                          &_glUseProgram,
+		"glVertexAttribPointer":                 &_glVertexAttribPointer,
+		"glViewport":                            &_glViewport,
+	}
+	for name, proc := range procs {
+		p, err := gles.FindProc(name)
+		if err != nil {
+			return fmt.Errorf("failed to locate %s in %s: %w", name, gles.Name, err)
+		}
+		*proc = p
+	}
+	return nil
+}
+
+var (
+	glInitOnce                             sync.Once
+	_glActiveTexture                       *windows.Proc
+	_glAttachShader                        *windows.Proc
+	_glBeginQuery                          *windows.Proc
+	_glBindAttribLocation                  *windows.Proc
+	_glBindBuffer                          *windows.Proc
+	_glBindBufferBase                      *windows.Proc
+	_glBindFramebuffer                     *windows.Proc
+	_glBindRenderbuffer                    *windows.Proc
+	_glBindTexture                         *windows.Proc
+	_glBindVertexArray                     *windows.Proc
+	_glBlendEquation                       *windows.Proc
+	_glBlendFuncSeparate                   *windows.Proc
+	_glBufferData                          *windows.Proc
+	_glBufferSubData                       *windows.Proc
+	_glCheckFramebufferStatus              *windows.Proc
+	_glClear                               *windows.Proc
+	_glClearColor                          *windows.Proc
+	_glClearDepthf                         *windows.Proc
+	_glDeleteQueries                       *windows.Proc
+	_glDeleteVertexArrays                  *windows.Proc
+	_glCompileShader                       *windows.Proc
+	_glCopyTexSubImage2D                   *windows.Proc
+	_glGenerateMipmap                      *windows.Proc
+	_glGenBuffers                          *windows.Proc
+	_glGenFramebuffers                     *windows.Proc
+	_glGenVertexArrays                     *windows.Proc
+	_glGetUniformBlockIndex                *windows.Proc
+	_glCreateProgram                       *windows.Proc
+	_glGenRenderbuffers                    *windows.Proc
+	_glCreateShader                        *windows.Proc
+	_glGenTextures                         *windows.Proc
+	_glDeleteBuffers                       *windows.Proc
+	_glDeleteFramebuffers                  *windows.Proc
+	_glDeleteProgram                       *windows.Proc
+	_glDeleteShader                        *windows.Proc
+	_glDeleteRenderbuffers                 *windows.Proc
+	_glDeleteTextures                      *windows.Proc
+	_glDepthFunc                           *windows.Proc
+	_glDepthMask                           *windows.Proc
+	_glDisableVertexAttribArray            *windows.Proc
+	_glDisable                             *windows.Proc
+	_glDrawArrays                          *windows.Proc
+	_glDrawElements                        *windows.Proc
+	_glEnable                              *windows.Proc
+	_glEnableVertexAttribArray             *windows.Proc
+	_glEndQuery                            *windows.Proc
+	_glFinish                              *windows.Proc
+	_glFlush                               *windows.Proc
+	_glFramebufferRenderbuffer             *windows.Proc
+	_glFramebufferTexture2D                *windows.Proc
+	_glGenQueries                          *windows.Proc
+	_glGetError                            *windows.Proc
+	_glGetRenderbufferParameteriv          *windows.Proc
+	_glGetFloatv                           *windows.Proc
+	_glGetFramebufferAttachmentParameteriv *windows.Proc
+	_glGetIntegerv                         *windows.Proc
+	_glGetIntegeri_v                       *windows.Proc
+	_glGetProgramiv                        *windows.Proc
+	_glGetProgramInfoLog                   *windows.Proc
+	_glGetQueryObjectuiv                   *windows.Proc
+	_glGetShaderiv                         *windows.Proc
+	_glGetShaderInfoLog                    *windows.Proc
+	_glGetString                           *windows.Proc
+	_glGetUniformLocation                  *windows.Proc
+	_glGetVertexAttribiv                   *windows.Proc
+	_glGetVertexAttribPointerv             *windows.Proc
+	_glInvalidateFramebuffer               *windows.Proc
+	_glIsEnabled                           *windows.Proc
+	_glLinkProgram                         *windows.Proc
+	_glPixelStorei                         *windows.Proc
+	_glReadPixels                          *windows.Proc
+	_glRenderbufferStorage                 *windows.Proc
+	_glScissor                             *windows.Proc
+	_glShaderSource                        *windows.Proc
+	_glTexImage2D                          *windows.Proc
+	_glTexStorage2D                        *windows.Proc
+	_glTexSubImage2D                       *windows.Proc
+	_glTexParameteri                       *windows.Proc
+	_glUniformBlockBinding                 *windows.Proc
+	_glUniform1f                           *windows.Proc
+	_glUniform1i                           *windows.Proc
+	_glUniform2f                           *windows.Proc
+	_glUniform3f                           *windows.Proc
+	_glUniform4f                           *windows.Proc
+	_glUseProgram                          *windows.Proc
+	_glVertexAttribPointer                 *windows.Proc
+	_glViewport                            *windows.Proc
+)
+
+type Functions struct {
+	// Query caches.
+	int32s   [100]int32
+	float32s [100]float32
+	uintptrs [100]uintptr
+}
+
+type Context interface{}
+
+func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
+	if ctx != nil {
+		panic("non-nil context")
+	}
+	var err error
+	glInitOnce.Do(func() {
+		err = loadGLESv2Procs()
+	})
+	return new(Functions), err
+}
+
+func (c *Functions) ActiveTexture(t Enum) {
+	syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0)
+}
+func (c *Functions) AttachShader(p Program, s Shader) {
+	syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0)
+}
+func (f *Functions) BeginQuery(target Enum, query Query) {
+	syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0)
+}
+func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) {
+	cname := cString(name)
+	c0 := &cname[0]
+	syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0)))
+	issue34474KeepAlive(c)
+}
+func (c *Functions) BindBuffer(target Enum, b Buffer) {
+	syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0)
+}
+func (c *Functions) BindBufferBase(target Enum, index int, b Buffer) {
+	syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V))
+}
+func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
+	syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0)
+}
+func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
+	syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb.V), 0)
+}
+func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
+	panic("not implemented")
+}
+func (c *Functions) BindTexture(target Enum, t Texture) {
+	syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
+}
+func (c *Functions) BindVertexArray(a VertexArray) {
+	syscall.Syscall(_glBindVertexArray.Addr(), 1, uintptr(a.V), 0, 0)
+}
+func (c *Functions) BlendEquation(mode Enum) {
+	syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
+}
+func (c *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
+	syscall.Syscall6(_glBlendFuncSeparate.Addr(), 4, uintptr(srcRGB), uintptr(dstRGB), uintptr(srcA), uintptr(dstA), 0, 0)
+}
+func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
+	var p unsafe.Pointer
+	if len(data) > 0 {
+		p = unsafe.Pointer(&data[0])
+	}
+	syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(size), uintptr(p), uintptr(usage), 0, 0)
+}
+func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
+	if n := len(src); n > 0 {
+		s0 := &src[0]
+		syscall.Syscall6(_glBufferSubData.Addr(), 4, uintptr(target), uintptr(offset), uintptr(n), uintptr(unsafe.Pointer(s0)), 0, 0)
+		issue34474KeepAlive(s0)
+	}
+}
+func (c *Functions) CheckFramebufferStatus(target Enum) Enum {
+	s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0)
+	return Enum(s)
+}
+func (c *Functions) Clear(mask Enum) {
+	syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0)
+}
+func (c *Functions) ClearColor(red, green, blue, alpha float32) {
+	syscall.Syscall6(_glClearColor.Addr(), 4, uintptr(math.Float32bits(red)), uintptr(math.Float32bits(green)), uintptr(math.Float32bits(blue)), uintptr(math.Float32bits(alpha)), 0, 0)
+}
+func (c *Functions) ClearDepthf(d float32) {
+	syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0)
+}
+func (c *Functions) CompileShader(s Shader) {
+	syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0)
+}
+func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
+	syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0)
+}
+func (f *Functions) GenerateMipmap(target Enum) {
+	syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0)
+}
+func (c *Functions) CreateBuffer() Buffer {
+	var buf uintptr
+	syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
+	return Buffer{uint(buf)}
+}
+func (c *Functions) CreateFramebuffer() Framebuffer {
+	var fb uintptr
+	syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
+	return Framebuffer{uint(fb)}
+}
+func (c *Functions) CreateProgram() Program {
+	p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
+	return Program{uint(p)}
+}
+func (f *Functions) CreateQuery() Query {
+	var q uintptr
+	syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
+	return Query{uint(q)}
+}
+func (c *Functions) CreateRenderbuffer() Renderbuffer {
+	var rb uintptr
+	syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
+	return Renderbuffer{uint(rb)}
+}
+func (c *Functions) CreateShader(ty Enum) Shader {
+	s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
+	return Shader{uint(s)}
+}
+func (c *Functions) CreateTexture() Texture {
+	var t uintptr
+	syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
+	return Texture{uint(t)}
+}
+func (c *Functions) CreateVertexArray() VertexArray {
+	var t uintptr
+	syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
+	return VertexArray{uint(t)}
+}
+func (c *Functions) DeleteBuffer(v Buffer) {
+	syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
+}
+func (c *Functions) DeleteFramebuffer(v Framebuffer) {
+	syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
+}
+func (c *Functions) DeleteProgram(p Program) {
+	syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
+}
+func (f *Functions) DeleteQuery(query Query) {
+	syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
+}
+func (c *Functions) DeleteShader(s Shader) {
+	syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
+}
+func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
+	syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
+}
+func (c *Functions) DeleteTexture(v Texture) {
+	syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
+}
+func (f *Functions) DeleteVertexArray(array VertexArray) {
+	syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0)
+}
+func (c *Functions) DepthFunc(f Enum) {
+	syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
+}
+func (c *Functions) DepthMask(mask bool) {
+	var m uintptr
+	if mask {
+		m = 1
+	}
+	syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
+}
+func (c *Functions) DisableVertexAttribArray(a Attrib) {
+	syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
+}
+func (c *Functions) Disable(cap Enum) {
+	syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
+}
+func (c *Functions) DrawArrays(mode Enum, first, count int) {
+	syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
+}
+func (c *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
+	syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0)
+}
+func (f *Functions) DispatchCompute(x, y, z int) {
+	panic("not implemented")
+}
+func (c *Functions) Enable(cap Enum) {
+	syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
+}
+func (c *Functions) EnableVertexAttribArray(a Attrib) {
+	syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
+}
+func (f *Functions) EndQuery(target Enum) {
+	syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
+}
+func (c *Functions) Finish() {
+	syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
+}
+func (c *Functions) Flush() {
+	syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0)
+}
+func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
+	syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0)
+}
+func (c *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
+	syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0)
+}
+func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
+	cname := cString(name)
+	c0 := &cname[0]
+	u, _, _ := syscall.Syscall(_glGetUniformBlockIndex.Addr(), 2, uintptr(p.V), uintptr(unsafe.Pointer(c0)), 0)
+	issue34474KeepAlive(c0)
+	return uint(u)
+}
+func (c *Functions) GetBinding(pname Enum) Object {
+	return Object{uint(c.GetInteger(pname))}
+}
+func (c *Functions) GetBindingi(pname Enum, idx int) Object {
+	return Object{uint(c.GetIntegeri(pname, idx))}
+}
+func (c *Functions) GetError() Enum {
+	e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
+	return Enum(e)
+}
+func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
+	syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
+	return int(c.int32s[0])
+}
+func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
+	syscall.Syscall6(_glGetFramebufferAttachmentParameteriv.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0, 0)
+	return int(c.int32s[0])
+}
+func (c *Functions) GetInteger4(pname Enum) [4]int {
+	syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
+	var r [4]int
+	for i := range r {
+		r[i] = int(c.int32s[i])
+	}
+	return r
+}
+func (c *Functions) GetInteger(pname Enum) int {
+	syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
+	return int(c.int32s[0])
+}
+func (c *Functions) GetIntegeri(pname Enum, idx int) int {
+	syscall.Syscall(_glGetIntegeri_v.Addr(), 3, uintptr(pname), uintptr(idx), uintptr(unsafe.Pointer(&c.int32s[0])))
+	return int(c.int32s[0])
+}
+func (c *Functions) GetFloat(pname Enum) float32 {
+	syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
+	return c.float32s[0]
+}
+func (c *Functions) GetFloat4(pname Enum) [4]float32 {
+	syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
+	var r [4]float32
+	copy(r[:], c.float32s[:])
+	return r
+}
+func (c *Functions) GetProgrami(p Program, pname Enum) int {
+	syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
+	return int(c.int32s[0])
+}
+func (c *Functions) GetProgramInfoLog(p Program) string {
+	n := c.GetProgrami(p, INFO_LOG_LENGTH)
+	if n == 0 {
+		return ""
+	}
+	buf := make([]byte, n)
+	syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
+	return string(buf)
+}
+func (c *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
+	syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
+	return uint(c.int32s[0])
+}
+func (c *Functions) GetShaderi(s Shader, pname Enum) int {
+	syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
+	return int(c.int32s[0])
+}
+func (c *Functions) GetShaderInfoLog(s Shader) string {
+	n := c.GetShaderi(s, INFO_LOG_LENGTH)
+	buf := make([]byte, n)
+	syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
+	return string(buf)
+}
+func (c *Functions) GetString(pname Enum) string {
+	s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
+	return windows.BytePtrToString((*byte)(unsafe.Pointer(s)))
+}
+func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
+	cname := cString(name)
+	c0 := &cname[0]
+	u, _, _ := syscall.Syscall(_glGetUniformLocation.Addr(), 2, uintptr(p.V), uintptr(unsafe.Pointer(c0)), 0)
+	issue34474KeepAlive(c0)
+	return Uniform{int(u)}
+}
+func (c *Functions) GetVertexAttrib(index int, pname Enum) int {
+	syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
+	return int(c.int32s[0])
+}
+
+func (c *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
+	return Object{uint(c.GetVertexAttrib(index, pname))}
+}
+
+func (c *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
+	syscall.Syscall(_glGetVertexAttribPointerv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.uintptrs[0])))
+	return c.uintptrs[0]
+}
+func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
+	addr := _glInvalidateFramebuffer.Addr()
+	if addr == 0 {
+		// InvalidateFramebuffer is just a hint. Skip it if not supported.
+		return
+	}
+	syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
+}
+func (f *Functions) IsEnabled(cap Enum) bool {
+	u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0)
+	return u == TRUE
+}
+func (c *Functions) LinkProgram(p Program) {
+	syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
+}
+func (c *Functions) PixelStorei(pname Enum, param int) {
+	syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
+}
+func (f *Functions) MemoryBarrier(barriers Enum) {
+	panic("not implemented")
+}
+func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
+	panic("not implemented")
+}
+func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
+	d0 := &data[0]
+	syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0)
+	issue34474KeepAlive(d0)
+}
+func (c *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
+	syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0)
+}
+func (c *Functions) Scissor(x, y, width, height int32) {
+	syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
+}
+func (c *Functions) ShaderSource(s Shader, src string) {
+	var n uintptr = uintptr(len(src))
+	psrc := &src
+	syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
+	issue34474KeepAlive(psrc)
+}
+func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) {
+	syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
+}
+func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
+	syscall.Syscall6(_glTexStorage2D.Addr(), 5, uintptr(target), uintptr(levels), uintptr(internalFormat), uintptr(width), uintptr(height), 0)
+}
+func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
+	d0 := &data[0]
+	syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
+	issue34474KeepAlive(d0)
+}
+func (c *Functions) TexParameteri(target, pname Enum, param int) {
+	syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
+}
+func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
+	syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
+}
+func (c *Functions) Uniform1f(dst Uniform, v float32) {
+	syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
+}
+func (c *Functions) Uniform1i(dst Uniform, v int) {
+	syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
+}
+func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
+	syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)))
+}
+func (c *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
+	syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0)
+}
+func (c *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
+	syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0)
+}
+func (c *Functions) UseProgram(p Program) {
+	syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
+}
+func (f *Functions) UnmapBuffer(target Enum) bool {
+	panic("not implemented")
+}
+func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
+	var norm uintptr
+	if normalized {
+		norm = 1
+	}
+	syscall.Syscall6(_glVertexAttribPointer.Addr(), 6, uintptr(dst), uintptr(size), uintptr(ty), norm, uintptr(stride), uintptr(offset))
+}
+func (c *Functions) Viewport(x, y, width, height int) {
+	syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
+}
+
+func cString(s string) []byte {
+	b := make([]byte, len(s)+1)
+	copy(b, s)
+	return b
+}
+
+// issue34474KeepAlive calls runtime.KeepAlive as a
+// workaround for golang.org/issue/34474.
+func issue34474KeepAlive(v interface{}) {
+	runtime.KeepAlive(v)
+}

+ 77 - 0
vendor/gioui.org/internal/gl/types.go

@@ -0,0 +1,77 @@
+//go:build !js
+// +build !js
+
+package gl
+
+type (
+	Object       struct{ V uint }
+	Buffer       Object
+	Framebuffer  Object
+	Program      Object
+	Renderbuffer Object
+	Shader       Object
+	Texture      Object
+	Query        Object
+	Uniform      struct{ V int }
+	VertexArray  Object
+)
+
+func (o Object) valid() bool {
+	return o.V != 0
+}
+
+func (o Object) equal(o2 Object) bool {
+	return o == o2
+}
+
+func (u Framebuffer) Valid() bool {
+	return Object(u).valid()
+}
+
+func (u Uniform) Valid() bool {
+	return u.V != -1
+}
+
+func (p Program) Valid() bool {
+	return Object(p).valid()
+}
+
+func (s Shader) Valid() bool {
+	return Object(s).valid()
+}
+
+func (a VertexArray) Valid() bool {
+	return Object(a).valid()
+}
+
+func (f Framebuffer) Equal(f2 Framebuffer) bool {
+	return Object(f).equal(Object(f2))
+}
+
+func (p Program) Equal(p2 Program) bool {
+	return Object(p).equal(Object(p2))
+}
+
+func (s Shader) Equal(s2 Shader) bool {
+	return Object(s).equal(Object(s2))
+}
+
+func (u Uniform) Equal(u2 Uniform) bool {
+	return u == u2
+}
+
+func (a VertexArray) Equal(a2 VertexArray) bool {
+	return Object(a).equal(Object(a2))
+}
+
+func (r Renderbuffer) Equal(r2 Renderbuffer) bool {
+	return Object(r).equal(Object(r2))
+}
+
+func (t Texture) Equal(t2 Texture) bool {
+	return Object(t).equal(Object(t2))
+}
+
+func (b Buffer) Equal(b2 Buffer) bool {
+	return Object(b).equal(Object(b2))
+}

+ 90 - 0
vendor/gioui.org/internal/gl/types_js.go

@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package gl
+
+import "syscall/js"
+
+type (
+	Object       js.Value
+	Buffer       Object
+	Framebuffer  Object
+	Program      Object
+	Renderbuffer Object
+	Shader       Object
+	Texture      Object
+	Query        Object
+	Uniform      Object
+	VertexArray  Object
+)
+
+func (o Object) valid() bool {
+	return js.Value(o).Truthy()
+}
+
+func (o Object) equal(o2 Object) bool {
+	return js.Value(o).Equal(js.Value(o2))
+}
+
+func (b Buffer) Valid() bool {
+	return Object(b).valid()
+}
+
+func (f Framebuffer) Valid() bool {
+	return Object(f).valid()
+}
+
+func (p Program) Valid() bool {
+	return Object(p).valid()
+}
+
+func (r Renderbuffer) Valid() bool {
+	return Object(r).valid()
+}
+
+func (s Shader) Valid() bool {
+	return Object(s).valid()
+}
+
+func (t Texture) Valid() bool {
+	return Object(t).valid()
+}
+
+func (u Uniform) Valid() bool {
+	return Object(u).valid()
+}
+
+func (a VertexArray) Valid() bool {
+	return Object(a).valid()
+}
+
+func (f Framebuffer) Equal(f2 Framebuffer) bool {
+	return Object(f).equal(Object(f2))
+}
+
+func (p Program) Equal(p2 Program) bool {
+	return Object(p).equal(Object(p2))
+}
+
+func (s Shader) Equal(s2 Shader) bool {
+	return Object(s).equal(Object(s2))
+}
+
+func (u Uniform) Equal(u2 Uniform) bool {
+	return Object(u).equal(Object(u2))
+}
+
+func (a VertexArray) Equal(a2 VertexArray) bool {
+	return Object(a).equal(Object(a2))
+}
+
+func (r Renderbuffer) Equal(r2 Renderbuffer) bool {
+	return Object(r).equal(Object(r2))
+}
+
+func (t Texture) Equal(t2 Texture) bool {
+	return Object(t).equal(Object(t2))
+}
+
+func (b Buffer) Equal(b2 Buffer) bool {
+	return Object(b).equal(Object(b2))
+}

+ 87 - 0
vendor/gioui.org/internal/gl/util.go

@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package gl
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+)
+
+func CreateProgram(ctx *Functions, vsSrc, fsSrc string, attribs []string) (Program, error) {
+	vs, err := CreateShader(ctx, VERTEX_SHADER, vsSrc)
+	if err != nil {
+		return Program{}, err
+	}
+	defer ctx.DeleteShader(vs)
+	fs, err := CreateShader(ctx, FRAGMENT_SHADER, fsSrc)
+	if err != nil {
+		return Program{}, err
+	}
+	defer ctx.DeleteShader(fs)
+	prog := ctx.CreateProgram()
+	if !prog.Valid() {
+		return Program{}, errors.New("glCreateProgram failed")
+	}
+	ctx.AttachShader(prog, vs)
+	ctx.AttachShader(prog, fs)
+	for i, a := range attribs {
+		ctx.BindAttribLocation(prog, Attrib(i), a)
+	}
+	ctx.LinkProgram(prog)
+	if ctx.GetProgrami(prog, LINK_STATUS) == 0 {
+		log := ctx.GetProgramInfoLog(prog)
+		ctx.DeleteProgram(prog)
+		return Program{}, fmt.Errorf("program link failed: %s", strings.TrimSpace(log))
+	}
+	return prog, nil
+}
+
+func CreateComputeProgram(ctx *Functions, src string) (Program, error) {
+	cs, err := CreateShader(ctx, COMPUTE_SHADER, src)
+	if err != nil {
+		return Program{}, err
+	}
+	defer ctx.DeleteShader(cs)
+	prog := ctx.CreateProgram()
+	if !prog.Valid() {
+		return Program{}, errors.New("glCreateProgram failed")
+	}
+	ctx.AttachShader(prog, cs)
+	ctx.LinkProgram(prog)
+	if ctx.GetProgrami(prog, LINK_STATUS) == 0 {
+		log := ctx.GetProgramInfoLog(prog)
+		ctx.DeleteProgram(prog)
+		return Program{}, fmt.Errorf("program link failed: %s", strings.TrimSpace(log))
+	}
+	return prog, nil
+}
+
+func CreateShader(ctx *Functions, typ Enum, src string) (Shader, error) {
+	sh := ctx.CreateShader(typ)
+	if !sh.Valid() {
+		return Shader{}, errors.New("glCreateShader failed")
+	}
+	ctx.ShaderSource(sh, src)
+	ctx.CompileShader(sh)
+	if ctx.GetShaderi(sh, COMPILE_STATUS) == 0 {
+		log := ctx.GetShaderInfoLog(sh)
+		ctx.DeleteShader(sh)
+		return Shader{}, fmt.Errorf("shader compilation failed: %s", strings.TrimSpace(log))
+	}
+	return sh, nil
+}
+
+func ParseGLVersion(glVer string) (version [2]int, gles bool, err error) {
+	var ver [2]int
+	if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err == nil {
+		return ver, true, nil
+	} else if _, err := fmt.Sscanf(glVer, "WebGL %d.%d", &ver[0], &ver[1]); err == nil {
+		// WebGL major version v corresponds to OpenGL ES version v + 1
+		ver[0]++
+		return ver, true, nil
+	} else if _, err := fmt.Sscanf(glVer, "%d.%d", &ver[0], &ver[1]); err == nil {
+		return ver, false, nil
+	}
+	return ver, false, fmt.Errorf("failed to parse OpenGL ES version (%s)", glVer)
+}

Някои файлове не бяха показани, защото твърде много файлове са промени