forked from joejulian/gio
app,gpu,internal/vk: add Vulkan port for Wayland, X11, Android
This change implements a Vulkan port for the two renderers, old and compute. Run with GIORENDERER=forcecompute to test the compute renderer. To shake out bugs faster, it is also made the default on systems that support it. To disable Vulkan and force the use of OpenGL, use the `novulkan` tag: $ go run -tags novulkan gioui.org/example/kitchen Don't forget to file an issue describing the issue that prompted the use of the tag. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -5,6 +5,8 @@ packages:
|
||||
- libxkbcommon
|
||||
- libXcursor
|
||||
- libXfixes
|
||||
- vulkan-loader
|
||||
- vulkan-headers
|
||||
- wayland
|
||||
- mesa-libs
|
||||
- xorg-vfbserver
|
||||
|
||||
@@ -11,6 +11,7 @@ packages:
|
||||
- libgles2-mesa-dev
|
||||
- libegl1-mesa-dev
|
||||
- libffi-dev
|
||||
- libvulkan-dev
|
||||
- libxcursor-dev
|
||||
- libxrandr-dev
|
||||
- libxinerama-dev
|
||||
|
||||
+6
-4
@@ -21,12 +21,14 @@ type androidContext struct {
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
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() {
|
||||
@@ -38,10 +40,10 @@ func (c *androidContext) Release() {
|
||||
|
||||
func (c *androidContext) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.nativeWindow(c.Context.VisualID())
|
||||
if win == nil {
|
||||
return nil
|
||||
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
||||
return err
|
||||
}
|
||||
win, width, height := c.win.nativeWindow()
|
||||
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
||||
c.width, c.height = width, height
|
||||
return nil
|
||||
|
||||
+3
-1
@@ -30,13 +30,15 @@ type wlContext struct {
|
||||
eglWin *C.struct_wl_egl_window
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
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() {
|
||||
|
||||
+3
-1
@@ -17,13 +17,15 @@ type x11Context struct {
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func (w *x11Window) NewContext() (context, error) {
|
||||
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
|
||||
}
|
||||
return &x11Context{win: w, Context: ctx}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11Context) Release() {
|
||||
|
||||
+35
-8
@@ -211,6 +211,33 @@ var (
|
||||
dataPath string
|
||||
)
|
||||
|
||||
var (
|
||||
newAndroidVulkanContext func(w *window) (context, error)
|
||||
newAndroidGLESContext func(w *window) (context, error)
|
||||
)
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
funcs := []func(w *window) (context, error){newAndroidVulkanContext, newAndroidGLESContext}
|
||||
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
|
||||
@@ -471,16 +498,16 @@ func (w *window) setStage(stage system.Stage) {
|
||||
w.callbacks.Event(system.StageEvent{stage})
|
||||
}
|
||||
|
||||
func (w *window) nativeWindow(visID int) (*C.ANativeWindow, int, int) {
|
||||
var width, height int
|
||||
if w.win != nil {
|
||||
func (w *window) setVisual(visID int) error {
|
||||
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
||||
panic(errors.New("ANativeWindow_setBuffersGeometry failed"))
|
||||
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
||||
}
|
||||
w, h := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
|
||||
width, height = int(w), int(h)
|
||||
}
|
||||
return w.win, width, height
|
||||
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) {
|
||||
|
||||
@@ -220,6 +220,11 @@ var callbackMap sync.Map
|
||||
// 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
|
||||
}
|
||||
@@ -1476,6 +1481,28 @@ func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
// Close the window. Not implemented for Wayland.
|
||||
func (w *window) Close() {}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
var firstErr error
|
||||
if f := newWaylandVulkanContext; f != nil {
|
||||
c, err := f(w)
|
||||
if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
firstErr = err
|
||||
}
|
||||
if f := newWaylandEGLContext; 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?
|
||||
|
||||
@@ -102,6 +102,33 @@ type x11Window struct {
|
||||
wakeups chan struct{}
|
||||
}
|
||||
|
||||
var (
|
||||
newX11EGLContext func(w *x11Window) (context, error)
|
||||
newX11VulkanContext func(w *x11Window) (context, error)
|
||||
)
|
||||
|
||||
func (w *x11Window) NewContext() (context, error) {
|
||||
var firstErr error
|
||||
if f := newX11VulkanContext; f != nil {
|
||||
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
|
||||
}
|
||||
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
// 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
|
||||
surf vk.Surface
|
||||
dev vk.Device
|
||||
queueFam int
|
||||
queue vk.Queue
|
||||
acquireSem vk.Semaphore
|
||||
presentSem vk.Semaphore
|
||||
|
||||
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 {
|
||||
vk.DestroySurface(inst, surf)
|
||||
return nil, err
|
||||
}
|
||||
dev, err := vk.CreateDeviceAndQueue(physDev, qFam, "VK_KHR_swapchain")
|
||||
if err != nil {
|
||||
vk.DestroySurface(inst, surf)
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
vk.DestroySurface(inst, surf)
|
||||
vk.DestroyDevice(dev)
|
||||
return nil, err
|
||||
}
|
||||
acquireSem, err := vk.CreateSemaphore(dev)
|
||||
if err != nil {
|
||||
vk.DestroySurface(inst, surf)
|
||||
vk.DestroyDevice(dev)
|
||||
return nil, err
|
||||
}
|
||||
presentSem, err := vk.CreateSemaphore(dev)
|
||||
if err != nil {
|
||||
vk.DestroySemaphore(dev, acquireSem)
|
||||
vk.DestroySurface(inst, surf)
|
||||
vk.DestroyDevice(dev)
|
||||
return nil, err
|
||||
}
|
||||
c := &vkContext{
|
||||
physDev: physDev,
|
||||
inst: inst,
|
||||
surf: surf,
|
||||
dev: dev,
|
||||
queueFam: qFam,
|
||||
queue: vk.GetDeviceQueue(dev, qFam, 0),
|
||||
acquireSem: acquireSem,
|
||||
presentSem: presentSem,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *vkContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
imgIdx, err := vk.AcquireNextImage(c.dev, c.swchain, c.acquireSem, 0)
|
||||
if err != nil {
|
||||
return nil, mapErr(err)
|
||||
}
|
||||
c.presentIdx = imgIdx
|
||||
return gpu.VulkanRenderTarget{
|
||||
WaitSem: uint64(c.acquireSem),
|
||||
SignalSem: uint64(c.presentSem),
|
||||
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) {
|
||||
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.IsDeviceLost():
|
||||
return gpu.ErrDeviceLost
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *vkContext) release() {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
c.destroySurface()
|
||||
vk.DestroySemaphore(c.dev, c.acquireSem)
|
||||
vk.DestroySemaphore(c.dev, c.presentSem)
|
||||
vk.DestroyDevice(c.dev)
|
||||
*c = vkContext{}
|
||||
}
|
||||
|
||||
func (c *vkContext) present() error {
|
||||
return mapErr(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) destroySurface() {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
c.destroyImageViews()
|
||||
if c.swchain != 0 {
|
||||
vk.DestroySwapchain(c.dev, c.swchain)
|
||||
c.swchain = 0
|
||||
}
|
||||
if c.surf != 0 {
|
||||
vk.DestroySurface(c.inst, c.surf)
|
||||
c.surf = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *vkContext) setSurface(surf vk.Surface) {
|
||||
if c.surf != 0 {
|
||||
panic("another surface is active")
|
||||
}
|
||||
c.surf = surf
|
||||
}
|
||||
|
||||
func (c *vkContext) refresh(width, height int) error {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
c.destroyImageViews()
|
||||
swchain, imgs, format, err := vk.CreateSwapchain(c.physDev, c.dev, c.surf, width, height, c.swchain)
|
||||
if c.swchain != 0 {
|
||||
vk.DestroySwapchain(c.dev, c.swchain)
|
||||
c.swchain = 0
|
||||
}
|
||||
if err != nil {
|
||||
return mapErr(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 != nil {
|
||||
return mapErr(err)
|
||||
}
|
||||
defer vk.DestroyRenderPass(c.dev, pass)
|
||||
for _, img := range imgs {
|
||||
view, err := vk.CreateImageView(c.dev, img, format)
|
||||
if err != nil {
|
||||
return mapErr(err)
|
||||
}
|
||||
c.views = append(c.views, view)
|
||||
fbo, err := vk.CreateFramebuffer(c.dev, pass, view, width, height)
|
||||
if err != nil {
|
||||
return mapErr(err)
|
||||
}
|
||||
c.fbos = append(c.fbos, fbo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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
|
||||
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,
|
||||
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.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()
|
||||
c.ctx.destroySurface()
|
||||
surf, err := vk.CreateAndroidSurface(c.inst, unsafe.Pointer(win))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.ctx.setSurface(surf)
|
||||
return c.ctx.refresh(w, h)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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
|
||||
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,
|
||||
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.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(w, h)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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
|
||||
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,
|
||||
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.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(w, h)
|
||||
}
|
||||
+7
-1
@@ -8,7 +8,7 @@ import "gioui.org/gpu/internal/driver"
|
||||
// There is an API type for each supported GPU API such as OpenGL and Direct3D.
|
||||
type API = driver.API
|
||||
|
||||
// A RenderTarget denotest the destination framebuffer for a frame.
|
||||
// A RenderTarget denotes the destination framebuffer for a frame.
|
||||
type RenderTarget = driver.RenderTarget
|
||||
|
||||
// OpenGLRenderTarget is a render target suitable for the OpenGL backend.
|
||||
@@ -20,6 +20,9 @@ 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
|
||||
|
||||
@@ -29,6 +32,9 @@ 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
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
_ "gioui.org/gpu/internal/d3d11"
|
||||
_ "gioui.org/gpu/internal/metal"
|
||||
_ "gioui.org/gpu/internal/opengl"
|
||||
_ "gioui.org/gpu/internal/vulkan"
|
||||
)
|
||||
|
||||
type GPU interface {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package headless
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
"runtime"
|
||||
@@ -30,6 +31,33 @@ type context interface {
|
||||
Release()
|
||||
}
|
||||
|
||||
var (
|
||||
newContextPrimary func() (context, error)
|
||||
newContextFallback func() (context, error)
|
||||
)
|
||||
|
||||
func newContext() (context, error) {
|
||||
funcs := []func() (context, error){newContextPrimary, newContextFallback}
|
||||
var firstErr error
|
||||
for _, f := range funcs {
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
c, err := f()
|
||||
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")
|
||||
}
|
||||
|
||||
// NewWindow creates a new headless window.
|
||||
func NewWindow(width, height int) (*Window, error) {
|
||||
ctx, err := newContext()
|
||||
|
||||
@@ -37,7 +37,8 @@ type mtlContext struct {
|
||||
queue C.CFTypeRef
|
||||
}
|
||||
|
||||
func newContext() (context, error) {
|
||||
func init() {
|
||||
newContextPrimary = func() (context, error) {
|
||||
dev := C.createDevice()
|
||||
if dev == 0 {
|
||||
return nil, errors.New("headless: failed to create Metal device")
|
||||
@@ -48,6 +49,7 @@ func newContext() (context, error) {
|
||||
return nil, errors.New("headless: failed to create MTLQueue")
|
||||
}
|
||||
return &mtlContext{dev: dev, queue: queue}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mtlContext) API() gpu.API {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
func newContext() (context, error) {
|
||||
func init() {
|
||||
newContextFallback = func() (context, error) {
|
||||
return egl.NewContext(egl.EGL_DEFAULT_DISPLAY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ type jsContext struct {
|
||||
ctx js.Value
|
||||
}
|
||||
|
||||
func newContext() (context, error) {
|
||||
func init() {
|
||||
newContextPrimary = func() (context, error) {
|
||||
doc := js.Global().Get("document")
|
||||
cnv := doc.Call("createElement", "canvas")
|
||||
ctx := cnv.Call("getContext", "webgl2")
|
||||
@@ -28,6 +29,7 @@ func newContext() (context, error) {
|
||||
ctx: ctx,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *jsContext) API() gpu.API {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux || freebsd) && !novulkan
|
||||
// +build linux freebsd
|
||||
// +build !novulkan
|
||||
|
||||
package headless
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/vk"
|
||||
)
|
||||
|
||||
type vkContext struct {
|
||||
physDev vk.PhysicalDevice
|
||||
inst vk.Instance
|
||||
dev vk.Device
|
||||
queueFam int
|
||||
}
|
||||
|
||||
func init() {
|
||||
newContextPrimary = newVulkanContext
|
||||
}
|
||||
|
||||
func newVulkanContext() (context, error) {
|
||||
inst, err := vk.CreateInstance()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
physDev, qFam, err := vk.ChoosePhysicalDevice(inst, 0)
|
||||
if err != nil {
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
dev, err := vk.CreateDeviceAndQueue(physDev, qFam)
|
||||
if err != nil {
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
ctx := &vkContext{
|
||||
physDev: physDev,
|
||||
inst: inst,
|
||||
dev: dev,
|
||||
queueFam: qFam,
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (c *vkContext) API() gpu.API {
|
||||
return gpu.Vulkan{
|
||||
PhysDevice: unsafe.Pointer(c.physDev),
|
||||
Device: unsafe.Pointer(c.dev),
|
||||
Format: int(vk.FORMAT_R8G8B8A8_SRGB),
|
||||
QueueFamily: c.queueFam,
|
||||
QueueIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *vkContext) MakeCurrent() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *vkContext) ReleaseCurrent() {
|
||||
}
|
||||
|
||||
func (c *vkContext) Release() {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
vk.DestroyDevice(c.dev)
|
||||
vk.DestroyInstance(c.inst)
|
||||
*c = vkContext{}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ type d3d11Context struct {
|
||||
dev *d3d11.Device
|
||||
}
|
||||
|
||||
func newContext() (context, error) {
|
||||
func init() {
|
||||
newContextPrimary = func() (context, error) {
|
||||
dev, ctx, _, err := d3d11.CreateDevice(
|
||||
d3d11.DRIVER_TYPE_HARDWARE,
|
||||
0,
|
||||
@@ -24,6 +25,7 @@ func newContext() (context, error) {
|
||||
// Don't need it.
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(ctx), ctx.Vtbl.Release)
|
||||
return &d3d11Context{dev: dev}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *d3d11Context) API() gpu.API {
|
||||
|
||||
@@ -31,6 +31,17 @@ type MetalRenderTarget struct {
|
||||
Texture unsafe.Pointer
|
||||
}
|
||||
|
||||
type VulkanRenderTarget struct {
|
||||
// WaitSem is a VkSemaphore that must signaled before accessing Framebuffer.
|
||||
WaitSem uint64
|
||||
// SignalSem is a VkSemaphore that signal access to Framebuffer is complete.
|
||||
SignalSem uint64
|
||||
// Image is the VkImage to render into.
|
||||
Image uint64
|
||||
// Framebuffer is a VkFramebuffer for Image.
|
||||
Framebuffer uint64
|
||||
}
|
||||
|
||||
type OpenGL struct {
|
||||
// ES forces the use of ANGLE OpenGL ES libraries on macOS. It is
|
||||
// ignored on all other platforms.
|
||||
@@ -55,11 +66,25 @@ type Metal struct {
|
||||
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.
|
||||
@@ -81,6 +106,10 @@ func NewDevice(api API) (Device, error) {
|
||||
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)
|
||||
}
|
||||
@@ -88,6 +117,8 @@ func NewDevice(api API) (Device, error) {
|
||||
func (OpenGL) implementsAPI() {}
|
||||
func (Direct3D11) implementsAPI() {}
|
||||
func (Metal) implementsAPI() {}
|
||||
func (Vulkan) implementsAPI() {}
|
||||
func (OpenGLRenderTarget) ImplementsRenderTarget() {}
|
||||
func (Direct3D11RenderTarget) ImplementsRenderTarget() {}
|
||||
func (MetalRenderTarget) ImplementsRenderTarget() {}
|
||||
func (VulkanRenderTarget) ImplementsRenderTarget() {}
|
||||
|
||||
@@ -986,7 +986,7 @@ func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) BindVertexUniforms(buf driver.Buffer) {
|
||||
func (b *Backend) BindUniforms(buf driver.Buffer) {
|
||||
bf := buf.(*Buffer)
|
||||
enc := b.renderEnc
|
||||
if enc == 0 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package vulkan
|
||||
|
||||
// Empty file to avoid the build error for platforms without Vulkan support.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !nowayland
|
||||
// +build !nowayland
|
||||
|
||||
package vk
|
||||
|
||||
/*
|
||||
#define VK_USE_PLATFORM_ANDROID_KHR
|
||||
#define VK_NO_PROTOTYPES 1
|
||||
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
|
||||
#include <android/native_window.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
static VkResult vkCreateAndroidSurfaceKHR(PFN_vkCreateAndroidSurfaceKHR f, VkInstance instance, const VkAndroidSurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) {
|
||||
return f(instance, pCreateInfo, pAllocator, pSurface);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var wlFuncs struct {
|
||||
vkCreateAndroidSurfaceKHR C.PFN_vkCreateAndroidSurfaceKHR
|
||||
}
|
||||
|
||||
func init() {
|
||||
loadFuncs = append(loadFuncs, func(dlopen func(name string) *[0]byte) {
|
||||
wlFuncs.vkCreateAndroidSurfaceKHR = dlopen("vkCreateAndroidSurfaceKHR")
|
||||
})
|
||||
}
|
||||
|
||||
func CreateAndroidSurface(inst Instance, window unsafe.Pointer) (Surface, error) {
|
||||
inf := C.VkAndroidSurfaceCreateInfoKHR{
|
||||
sType: C.VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
|
||||
window: (*C.ANativeWindow)(window),
|
||||
}
|
||||
var surf Surface
|
||||
if err := vkErr(C.vkCreateAndroidSurfaceKHR(wlFuncs.vkCreateAndroidSurfaceKHR, inst, &inf, nil, &surf)); err != nil {
|
||||
return 0, fmt.Errorf("vulkan: vkCreateAndroidSurfaceKHR: %w", err)
|
||||
}
|
||||
return surf, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
|
||||
package vk
|
||||
|
||||
/*
|
||||
#define VK_USE_PLATFORM_WAYLAND_KHR
|
||||
#define VK_NO_PROTOTYPES 1
|
||||
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
static VkResult vkCreateWaylandSurfaceKHR(PFN_vkCreateWaylandSurfaceKHR f, VkInstance instance, const VkWaylandSurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) {
|
||||
return f(instance, pCreateInfo, pAllocator, pSurface);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var wlFuncs struct {
|
||||
vkCreateWaylandSurfaceKHR C.PFN_vkCreateWaylandSurfaceKHR
|
||||
}
|
||||
|
||||
func init() {
|
||||
loadFuncs = append(loadFuncs, func(dlopen func(name string) *[0]byte) {
|
||||
wlFuncs.vkCreateWaylandSurfaceKHR = dlopen("vkCreateWaylandSurfaceKHR")
|
||||
})
|
||||
}
|
||||
|
||||
func CreateWaylandSurface(inst Instance, disp unsafe.Pointer, wlSurf unsafe.Pointer) (Surface, error) {
|
||||
inf := C.VkWaylandSurfaceCreateInfoKHR{
|
||||
sType: C.VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
|
||||
display: (*C.struct_wl_display)(disp),
|
||||
surface: (*C.struct_wl_surface)(wlSurf),
|
||||
}
|
||||
var surf Surface
|
||||
if err := vkErr(C.vkCreateWaylandSurfaceKHR(wlFuncs.vkCreateWaylandSurfaceKHR, inst, &inf, nil, &surf)); err != nil {
|
||||
return 0, fmt.Errorf("vulkan: vkCreateWaylandSurfaceKHR: %w", err)
|
||||
}
|
||||
return surf, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nox11
|
||||
// +build linux,!android freebsd
|
||||
// +build !nox11
|
||||
|
||||
package vk
|
||||
|
||||
/*
|
||||
#define VK_USE_PLATFORM_XLIB_KHR
|
||||
#define VK_NO_PROTOTYPES 1
|
||||
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
static VkResult vkCreateXlibSurfaceKHR(PFN_vkCreateXlibSurfaceKHR f, VkInstance instance, const VkXlibSurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) {
|
||||
return f(instance, pCreateInfo, pAllocator, pSurface);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var x11Funcs struct {
|
||||
vkCreateXlibSurfaceKHR C.PFN_vkCreateXlibSurfaceKHR
|
||||
}
|
||||
|
||||
func init() {
|
||||
loadFuncs = append(loadFuncs, func(dlopen func(name string) *[0]byte) {
|
||||
x11Funcs.vkCreateXlibSurfaceKHR = dlopen("vkCreateXlibSurfaceKHR")
|
||||
})
|
||||
}
|
||||
|
||||
func CreateXlibSurface(inst Instance, dpy unsafe.Pointer, window uintptr) (Surface, error) {
|
||||
inf := C.VkXlibSurfaceCreateInfoKHR{
|
||||
sType: C.VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
|
||||
dpy: (*C.Display)(dpy),
|
||||
window: (C.Window)(window),
|
||||
}
|
||||
var surf Surface
|
||||
if err := vkErr(C.vkCreateXlibSurfaceKHR(x11Funcs.vkCreateXlibSurfaceKHR, inst, &inf, nil, &surf)); err != nil {
|
||||
return 0, fmt.Errorf("vulkan: vkCreateXlibSurfaceKHR: %w", err)
|
||||
}
|
||||
return surf, nil
|
||||
}
|
||||
Reference in New Issue
Block a user