From 8ff6546285319c5e9c4825f83ba1cc83648cd648 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Thu, 4 Mar 2021 20:51:47 +0100 Subject: [PATCH] gpu,gpu/backend: implement generic backend.NewDevice NewDevice creates a Device given an API, which is the necessary GPU resources for a backend. Convert gpu.New to take an API instead of a backend.Device directly. In turn, this frees us to later unexport the backend package along with the backend implementations (for now just gioui.org/gpu/gl for OpenGL). It also allows programs that embed Gio (such as gioui.org/example/glfw) to freely choose a backend, not just OpenGL. Signed-off-by: Elias Naur --- app/headless/backend_test.go | 2 +- app/headless/headless.go | 7 ++-- app/headless/headless_darwin.go | 12 +++--- app/headless/headless_js.go | 7 ++-- app/headless/headless_windows.go | 12 +++--- app/internal/d3d11/backend_windows.go | 7 +++- app/internal/egl/egl.go | 7 ++-- app/internal/window/d3d11_windows.go | 8 ++-- app/internal/window/gl_ios.go | 7 ++-- app/internal/window/gl_js.go | 7 ++-- app/internal/window/gl_macos.go | 7 ++-- app/internal/window/window.go | 4 +- app/loop.go | 7 +--- cmd/gogio/androidbuild.go | 5 ++- cmd/gogio/windowsbuild.go | 7 ++-- gpu/api.go | 15 +++++++ gpu/backend/api.go | 56 +++++++++++++++++++++++++++ gpu/gl/backend.go | 12 +++--- gpu/gpu.go | 15 +++++-- 19 files changed, 139 insertions(+), 65 deletions(-) create mode 100644 gpu/api.go create mode 100644 gpu/backend/api.go diff --git a/app/headless/backend_test.go b/app/headless/backend_test.go index 5c7d6425..96405e5c 100644 --- a/app/headless/backend_test.go +++ b/app/headless/backend_test.go @@ -170,7 +170,7 @@ func newBackend(t *testing.T) backend.Device { if err := ctx.MakeCurrent(); err != nil { t.Fatal(err) } - b, err := ctx.Backend() + b, err := backend.NewDevice(ctx.API()) if err != nil { t.Fatal(err) } diff --git a/app/headless/headless.go b/app/headless/headless.go index a86f27ad..e4146d92 100644 --- a/app/headless/headless.go +++ b/app/headless/headless.go @@ -25,7 +25,7 @@ type Window struct { } type context interface { - Backend() (backend.Device, error) + API() gpu.API MakeCurrent() error ReleaseCurrent() Release() @@ -42,7 +42,8 @@ func NewWindow(width, height int) (*Window, error) { ctx: ctx, } err = contextDo(ctx, func() error { - dev, err := ctx.Backend() + api := ctx.API() + dev, err := backend.NewDevice(api) if err != nil { return err } @@ -62,7 +63,7 @@ func NewWindow(width, height int) (*Window, error) { fboTex.Release() return err } - gp, err := gpu.New(dev) + gp, err := gpu.New(api) if err != nil { fbo.Release() fboTex.Release() diff --git a/app/headless/headless_darwin.go b/app/headless/headless_darwin.go index 31ce54b7..0b5357cf 100644 --- a/app/headless/headless_darwin.go +++ b/app/headless/headless_darwin.go @@ -3,9 +3,7 @@ package headless import ( - "gioui.org/gpu/backend" - "gioui.org/gpu/gl" - + "gioui.org/gpu" _ "gioui.org/app/internal/cocoainit" ) @@ -32,6 +30,10 @@ func newGLContext() (context, error) { return &nsContext{ctx: ctx}, nil } +func (c *nsContext) API() gpu.API { + return gpu.OpenGL{} +} + func (c *nsContext) MakeCurrent() error { C.gio_headless_makeCurrentContext(c.ctx) if !c.prepared { @@ -45,10 +47,6 @@ func (c *nsContext) ReleaseCurrent() { C.gio_headless_clearCurrentContext(c.ctx) } -func (c *nsContext) Backend() (backend.Device, error) { - return gl.NewBackend(nil) -} - func (d *nsContext) Release() { if d.ctx != 0 { C.gio_headless_releaseContext(d.ctx) diff --git a/app/headless/headless_js.go b/app/headless/headless_js.go index e12a315a..eb2b5e3f 100644 --- a/app/headless/headless_js.go +++ b/app/headless/headless_js.go @@ -6,8 +6,7 @@ import ( "errors" "syscall/js" - "gioui.org/gpu/backend" - "gioui.org/gpu/gl" + "gioui.org/gpu" "gioui.org/internal/glimpl" ) @@ -31,8 +30,8 @@ func newGLContext() (context, error) { return c, nil } -func (c *jsContext) Backend() (backend.Device, error) { - return gl.NewBackend(glimpl.Context(c.ctx)) +func (c *jsContext) API() gpu.API { + return gpu.OpenGL{Context: glimpl.Context(c.ctx)} } func (c *jsContext) Release() { diff --git a/app/headless/headless_windows.go b/app/headless/headless_windows.go index d7234612..3ea2e8a4 100644 --- a/app/headless/headless_windows.go +++ b/app/headless/headless_windows.go @@ -3,8 +3,10 @@ package headless import ( + "unsafe" + + "gioui.org/gpu" "gioui.org/app/internal/d3d11" - "gioui.org/gpu/backend" ) type d3d11Context struct { @@ -19,12 +21,8 @@ func newContext() (context, error) { return &d3d11Context{dev: dev}, nil } -func (c *d3d11Context) Backend() (backend.Device, error) { - backend, err := d3d11.NewBackend(c.dev.Handle) - if err != nil { - return nil, err - } - return backend, nil +func (c *d3d11Context) API() gpu.API { + return gpu.Direct3D11{Device: unsafe.Pointer(c.dev.Handle)} } func (c *d3d11Context) MakeCurrent() error { diff --git a/app/internal/d3d11/backend_windows.go b/app/internal/d3d11/backend_windows.go index 9d831bb8..ea87e562 100644 --- a/app/internal/d3d11/backend_windows.go +++ b/app/internal/d3d11/backend_windows.go @@ -109,6 +109,10 @@ type SwapChain struct { fbo *Framebuffer } +func init() { + backend.NewDirect3D11Device = newDirect3D11Device +} + func NewDevice() (*Device, error) { var flags uint32 if debug { @@ -237,7 +241,8 @@ func (s *SwapChain) Present() error { return s.swchain.Present(1, 0) } -func NewBackend(dev *_ID3D11Device) (*Backend, error) { +func newDirect3D11Device(api backend.Direct3D11) (backend.Device, error) { + dev := (*_ID3D11Device)(api.Device) b := &Backend{ dev: dev, ctx: dev.GetImmediateContext(), diff --git a/app/internal/egl/egl.go b/app/internal/egl/egl.go index a96863e6..1e3f88ef 100644 --- a/app/internal/egl/egl.go +++ b/app/internal/egl/egl.go @@ -11,8 +11,7 @@ import ( "strings" "gioui.org/app/internal/srgb" - "gioui.org/gpu/backend" - "gioui.org/gpu/gl" + "gioui.org/gpu" "gioui.org/internal/glimpl" ) @@ -118,8 +117,8 @@ func NewContext(disp NativeDisplayType) (*Context, error) { return c, nil } -func (c *Context) Backend() (backend.Device, error) { - return gl.NewBackend(nil) +func (c *Context) API() gpu.API { + return gpu.OpenGL{} } func (c *Context) ReleaseSurface() { diff --git a/app/internal/window/d3d11_windows.go b/app/internal/window/d3d11_windows.go index 8dbfe541..961003f3 100644 --- a/app/internal/window/d3d11_windows.go +++ b/app/internal/window/d3d11_windows.go @@ -3,8 +3,10 @@ package window import ( + "unsafe" + + "gioui.org/gpu" "gioui.org/app/internal/d3d11" - "gioui.org/gpu/backend" ) type d3d11Context struct { @@ -34,8 +36,8 @@ func init() { }) } -func (c *d3d11Context) Backend() (backend.Device, error) { - return d3d11.NewBackend(c.dev.Handle) +func (c *d3d11Context) API() gpu.API { + return gpu.Direct3D11{Device: unsafe.Pointer(c.dev.Handle)} } func (c *d3d11Context) Present() error { diff --git a/app/internal/window/gl_ios.go b/app/internal/window/gl_ios.go index 5edc63b3..b24dcc23 100644 --- a/app/internal/window/gl_ios.go +++ b/app/internal/window/gl_ios.go @@ -21,8 +21,7 @@ import ( "errors" "fmt" - "gioui.org/gpu/backend" - "gioui.org/gpu/gl" + "gioui.org/gpu" "gioui.org/internal/glimpl" ) @@ -56,8 +55,8 @@ func newContext(w *window) (*context, error) { return c, nil } -func (c *context) Backend() (backend.Device, error) { - return gl.NewBackend(nil) +func (c *context) API() gpu.API { + return gpu.OpenGL{} } func (c *context) Release() { diff --git a/app/internal/window/gl_js.go b/app/internal/window/gl_js.go index fd950e07..bbb3b292 100644 --- a/app/internal/window/gl_js.go +++ b/app/internal/window/gl_js.go @@ -7,8 +7,7 @@ import ( "syscall/js" "gioui.org/app/internal/srgb" - "gioui.org/gpu/backend" - "gioui.org/gpu/gl" + "gioui.org/gpu" "gioui.org/internal/glimpl" ) @@ -39,8 +38,8 @@ func newContext(w *window) (*context, error) { return c, nil } -func (c *context) Backend() (backend.Device, error) { - return gl.NewBackend(glimpl.Context(c.ctx)) +func (c *context) API() gpu.API { + return gpu.OpenGL{Context: glimpl.Context(c.ctx)} } func (c *context) Release() { diff --git a/app/internal/window/gl_macos.go b/app/internal/window/gl_macos.go index 6b26b96c..cc08baae 100644 --- a/app/internal/window/gl_macos.go +++ b/app/internal/window/gl_macos.go @@ -5,8 +5,7 @@ package window import ( - "gioui.org/gpu/backend" - "gioui.org/gpu/gl" + "gioui.org/gpu" "gioui.org/internal/glimpl" ) @@ -48,8 +47,8 @@ func newContext(w *window) (*context, error) { return c, nil } -func (c *context) Backend() (backend.Device, error) { - return gl.NewBackend(nil) +func (c *context) API() gpu.API { + return gpu.OpenGL{} } func (c *context) Release() { diff --git a/app/internal/window/window.go b/app/internal/window/window.go index f1402140..0148766a 100644 --- a/app/internal/window/window.go +++ b/app/internal/window/window.go @@ -7,7 +7,7 @@ package window import ( "errors" - "gioui.org/gpu/backend" + "gioui.org/gpu" "gioui.org/io/event" "gioui.org/io/pointer" "gioui.org/io/system" @@ -33,7 +33,7 @@ type Callbacks interface { } type Context interface { - Backend() (backend.Device, error) + API() gpu.API Present() error MakeCurrent() error Release() diff --git a/app/loop.go b/app/loop.go index 32b579e3..57aa11a5 100644 --- a/app/loop.go +++ b/app/loop.go @@ -67,12 +67,7 @@ func (l *renderLoop) renderLoop(ctx window.Context) error { initErr <- err return } - b, err := ctx.Backend() - if err != nil { - initErr <- err - return - } - g, err := gpu.New(b) + g, err := gpu.New(ctx.API()) if err != nil { initErr <- err return diff --git a/cmd/gogio/androidbuild.go b/cmd/gogio/androidbuild.go index ecf99ce5..b62be75f 100644 --- a/cmd/gogio/androidbuild.go +++ b/cmd/gogio/androidbuild.go @@ -7,8 +7,6 @@ import ( "bytes" "errors" "fmt" - "golang.org/x/sync/errgroup" - "golang.org/x/tools/go/packages" "io" "io/ioutil" "os" @@ -18,6 +16,9 @@ import ( "strconv" "strings" "text/template" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/packages" ) type androidTools struct { diff --git a/cmd/gogio/windowsbuild.go b/cmd/gogio/windowsbuild.go index 53569efc..1af86681 100644 --- a/cmd/gogio/windowsbuild.go +++ b/cmd/gogio/windowsbuild.go @@ -4,9 +4,6 @@ import ( "bytes" "encoding/binary" "fmt" - "github.com/akavel/rsrc/binutil" - "github.com/akavel/rsrc/coff" - "golang.org/x/text/encoding/unicode" "image/png" "io" "math" @@ -17,6 +14,10 @@ import ( "strconv" "strings" "text/template" + + "github.com/akavel/rsrc/binutil" + "github.com/akavel/rsrc/coff" + "golang.org/x/text/encoding/unicode" ) func buildWindows(tmpDir string, bi *buildInfo) error { diff --git a/gpu/api.go b/gpu/api.go new file mode 100644 index 00000000..10bba8fc --- /dev/null +++ b/gpu/api.go @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package gpu + +import "gioui.org/gpu/backend" + +// 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 = backend.API + +// OpenGL denotes the OpenGL or OpenGL ES API. +type OpenGL = backend.OpenGL + +// Direct3D11 denotes the Direct3D API. +type Direct3D11 = backend.Direct3D11 diff --git a/gpu/backend/api.go b/gpu/backend/api.go new file mode 100644 index 00000000..1e80fb10 --- /dev/null +++ b/gpu/backend/api.go @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package backend + +import ( + "fmt" + "unsafe" + + "gioui.org/internal/glimpl" +) + +// See gpu/api.go for documentation for the API types + +type API interface { + implementsAPI() +} + +type OpenGL struct { + // 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 glimpl.Context +} + +type Direct3D11 struct { + // Device contains a *ID3D11Device. + Device unsafe.Pointer +} + +// API specific device constructors. +var ( + NewOpenGLDevice func(api OpenGL) (Device, error) + NewDirect3D11Device func(api Direct3D11) (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) + } + } + return nil, fmt.Errorf("backend: no backend available for the API %T", api) +} + +func (OpenGL) implementsAPI() {} +func (Direct3D11) implementsAPI() {} diff --git a/gpu/gl/backend.go b/gpu/gl/backend.go index 37d6e4bd..e5bc31ef 100644 --- a/gpu/gl/backend.go +++ b/gpu/gl/backend.go @@ -123,12 +123,12 @@ const ( storageBindings = 32 ) -// NewBackend returns a new Backend. -// -// Pass a WebGL context if GOOS is "js", otherwise pass nil for the current -// context. -func NewBackend(ctx Context) (*Backend, error) { - f, err := glimpl.NewFunctions(ctx) +func init() { + backend.NewOpenGLDevice = newOpenGLDevice +} + +func newOpenGLDevice(api backend.OpenGL) (backend.Device, error) { + f, err := glimpl.NewFunctions(api.Context) if err != nil { return nil, err } diff --git a/gpu/gpu.go b/gpu/gpu.go index df61b737..65427042 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -28,6 +28,9 @@ import ( "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" + + // Register backend. + _ "gioui.org/gpu/gl" ) type GPU interface { @@ -380,14 +383,18 @@ const ( materialTexture ) -func New(ctx backend.Device) (GPU, error) { +func New(api API) (GPU, error) { + d, err := backend.NewDevice(api) + if err != nil { + return nil, err + } forceCompute := os.Getenv("GIORENDERER") == "forcecompute" - feats := ctx.Caps().Features + feats := d.Caps().Features switch { case !forceCompute && feats.Has(backend.FeatureFloatRenderTargets): - return newGPU(ctx) + return newGPU(d) case feats.Has(backend.FeatureCompute): - return newCompute(ctx) + return newCompute(d) default: return nil, errors.New("gpu: no support for float render targets nor compute") }