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:
Elias Naur
2021-08-30 15:06:43 +02:00
parent bdd0893dd0
commit 8999747ad2
28 changed files with 4059 additions and 66 deletions
+7 -1
View File
@@ -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
+1
View File
@@ -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 {
+28
View File
@@ -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()
+12 -10
View File
@@ -37,17 +37,19 @@ type mtlContext struct {
queue C.CFTypeRef
}
func newContext() (context, error) {
dev := C.createDevice()
if dev == 0 {
return nil, errors.New("headless: failed to create Metal device")
func init() {
newContextPrimary = func() (context, error) {
dev := C.createDevice()
if dev == 0 {
return nil, errors.New("headless: failed to create Metal device")
}
queue := C.newCommandQueue(dev)
if queue == 0 {
C.CFRelease(dev)
return nil, errors.New("headless: failed to create MTLQueue")
}
return &mtlContext{dev: dev, queue: queue}, nil
}
queue := C.newCommandQueue(dev)
if queue == 0 {
C.CFRelease(dev)
return nil, errors.New("headless: failed to create MTLQueue")
}
return &mtlContext{dev: dev, queue: queue}, nil
}
func (c *mtlContext) API() gpu.API {
+4 -2
View File
@@ -9,6 +9,8 @@ import (
"gioui.org/internal/egl"
)
func newContext() (context, error) {
return egl.NewContext(egl.EGL_DEFAULT_DISPLAY)
func init() {
newContextFallback = func() (context, error) {
return egl.NewContext(egl.EGL_DEFAULT_DISPLAY)
}
}
+15 -13
View File
@@ -14,20 +14,22 @@ type jsContext struct {
ctx js.Value
}
func newContext() (context, error) {
doc := js.Global().Get("document")
cnv := doc.Call("createElement", "canvas")
ctx := cnv.Call("getContext", "webgl2")
if ctx.IsNull() {
ctx = cnv.Call("getContext", "webgl")
func init() {
newContextPrimary = func() (context, error) {
doc := js.Global().Get("document")
cnv := doc.Call("createElement", "canvas")
ctx := cnv.Call("getContext", "webgl2")
if ctx.IsNull() {
ctx = cnv.Call("getContext", "webgl")
}
if ctx.IsNull() {
return nil, errors.New("headless: webgl is not supported")
}
c := &jsContext{
ctx: ctx,
}
return c, nil
}
if ctx.IsNull() {
return nil, errors.New("headless: webgl is not supported")
}
c := &jsContext{
ctx: ctx,
}
return c, nil
}
func (c *jsContext) API() gpu.API {
+74
View File
@@ -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{}
}
+12 -10
View File
@@ -13,17 +13,19 @@ type d3d11Context struct {
dev *d3d11.Device
}
func newContext() (context, error) {
dev, ctx, _, err := d3d11.CreateDevice(
d3d11.DRIVER_TYPE_HARDWARE,
0,
)
if err != nil {
return nil, err
func init() {
newContextPrimary = func() (context, error) {
dev, ctx, _, err := d3d11.CreateDevice(
d3d11.DRIVER_TYPE_HARDWARE,
0,
)
if err != nil {
return nil, err
}
// Don't need it.
d3d11.IUnknownRelease(unsafe.Pointer(ctx), ctx.Vtbl.Release)
return &d3d11Context{dev: dev}, nil
}
// Don't need it.
d3d11.IUnknownRelease(unsafe.Pointer(ctx), ctx.Vtbl.Release)
return &d3d11Context{dev: dev}, nil
}
func (c *d3d11Context) API() gpu.API {
+31
View File
@@ -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() {}
+1 -1
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: Unlicense OR MIT
package vulkan
// Empty file to avoid the build error for platforms without Vulkan support.