From 7d84e419c981712604773f2e187509816e277c01 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Wed, 4 Aug 2021 14:10:07 +0200 Subject: [PATCH] gpu,gpu/headless,app/internal/wm: add explicit RenderTarget API Both the OpenGL and the Direct3D API are stateful and gpu.GPU renders to the render target current when Frame is called. Modern GPU API such as Metal don't have a concept of a current render target, and the target even changes each frame. Add RenderTarget and add an explicit target argument to GPU.Frame as well as the underlying driver.Device.BeginFrame. Signed-off-by: Elias Naur --- app/internal/wm/d3d11_windows.go | 7 +++++++ app/internal/wm/gl_ios.go | 4 ++++ app/internal/wm/gl_js.go | 4 ++++ app/internal/wm/gl_macos.go | 4 ++++ app/internal/wm/window.go | 1 + app/loop.go | 2 +- gpu/api.go | 9 +++++++++ gpu/compute.go | 4 ++-- gpu/gpu.go | 10 +++++----- gpu/headless/driver_test.go | 2 +- gpu/headless/headless.go | 2 +- gpu/internal/d3d11/d3d11_windows.go | 26 ++++++++++++++++++-------- gpu/internal/driver/api.go | 19 +++++++++++++++++-- gpu/internal/driver/driver.go | 3 ++- gpu/internal/opengl/opengl.go | 17 +++++++++++++++-- internal/egl/egl.go | 4 ++++ 16 files changed, 95 insertions(+), 23 deletions(-) diff --git a/app/internal/wm/d3d11_windows.go b/app/internal/wm/d3d11_windows.go index 78a05cae..d570a87b 100644 --- a/app/internal/wm/d3d11_windows.go +++ b/app/internal/wm/d3d11_windows.go @@ -54,6 +54,13 @@ func (c *d3d11Context) API() gpu.API { return gpu.Direct3D11{Device: unsafe.Pointer(c.dev)} } +func (c *d3d11Context) RenderTarget() gpu.RenderTarget { + return gpu.Direct3D11RenderTarget{ + RenderTarget: unsafe.Pointer(c.renderTarget), + DepthStencilView: unsafe.Pointer(c.depthView), + } +} + func (c *d3d11Context) Present() error { err := c.swchain.Present(1, 0) if err == nil { diff --git a/app/internal/wm/gl_ios.go b/app/internal/wm/gl_ios.go index 4957c0b5..74a8bddc 100644 --- a/app/internal/wm/gl_ios.go +++ b/app/internal/wm/gl_ios.go @@ -65,6 +65,10 @@ 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() } diff --git a/app/internal/wm/gl_js.go b/app/internal/wm/gl_js.go index 5d2f4b5f..56227235 100644 --- a/app/internal/wm/gl_js.go +++ b/app/internal/wm/gl_js.go @@ -36,6 +36,10 @@ func newContext(w *window) (*context, error) { return c, nil } +func (c *context) RenderTarget() gpu.RenderTarget { + return gpu.OpenGLRenderTarget{} +} + func (c *context) API() gpu.API { return gpu.OpenGL{Context: gl.Context(c.ctx)} } diff --git a/app/internal/wm/gl_macos.go b/app/internal/wm/gl_macos.go index ca7ae71e..cecf5baf 100644 --- a/app/internal/wm/gl_macos.go +++ b/app/internal/wm/gl_macos.go @@ -52,6 +52,10 @@ func newContext(w *window) (*context, error) { return c, nil } +func (c *context) RenderTarget() gpu.RenderTarget { + return gpu.OpenGLRenderTarget{} +} + func (c *context) API() gpu.API { return gpu.OpenGL{} } diff --git a/app/internal/wm/window.go b/app/internal/wm/window.go index 68ebabc4..7c602361 100644 --- a/app/internal/wm/window.go +++ b/app/internal/wm/window.go @@ -68,6 +68,7 @@ type Callbacks interface { type Context interface { API() gpu.API + RenderTarget() gpu.RenderTarget Present() error Refresh() error Release() diff --git a/app/loop.go b/app/loop.go index ae2e45b2..6007df05 100644 --- a/app/loop.go +++ b/app/loop.go @@ -100,7 +100,7 @@ func (l *renderLoop) renderLoop(ctx wm.Context) error { g.Collect(frame.viewport, frame.ops) // Signal that we're done with the frame ops. l.ack <- struct{}{} - res.err = g.Frame() + res.err = g.Frame(ctx.RenderTarget()) if res.err == nil { res.err = ctx.Present() } diff --git a/gpu/api.go b/gpu/api.go index e4f4dd57..19496907 100644 --- a/gpu/api.go +++ b/gpu/api.go @@ -8,6 +8,15 @@ 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. +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 + // OpenGL denotes the OpenGL or OpenGL ES API. type OpenGL = driver.OpenGL diff --git a/gpu/compute.go b/gpu/compute.go index 50110545..4f69df17 100644 --- a/gpu/compute.go +++ b/gpu/compute.go @@ -551,9 +551,9 @@ func (g *compute) Clear(col color.NRGBA) { g.collector.clearColor = f32color.LinearFromSRGB(col) } -func (g *compute) Frame() error { +func (g *compute) Frame(target RenderTarget) error { viewport := g.viewport - defFBO := g.ctx.BeginFrame(g.collector.clear, viewport) + defFBO := g.ctx.BeginFrame(target, g.collector.clear, viewport) defer g.ctx.EndFrame() t := &g.timers diff --git a/gpu/gpu.go b/gpu/gpu.go index deb6f1b9..9e4a5f3c 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -42,8 +42,8 @@ type GPU interface { Clear(color color.NRGBA) // Collect the graphics operations from frame, given the viewport. Collect(viewport image.Point, frame *op.Ops) - // Frame clears the color buffer and draws the collected operations. - Frame() error + // Frame draws the collected operations to target. + Frame(target RenderTarget) error // Profile returns the last available profiling information. Profiling // information is requested when Collect sees a ProfileOp, and the result // is available through Profile at some later time. @@ -356,7 +356,7 @@ func New(api API) (GPU, error) { if err != nil { return nil, err } - d.BeginFrame(false, image.Point{}) + d.BeginFrame(nil, false, image.Point{}) defer d.EndFrame() forceCompute := os.Getenv("GIORENDERER") == "forcecompute" feats := d.Caps().Features @@ -414,9 +414,9 @@ func (g *gpu) Collect(viewport image.Point, frameOps *op.Ops) { } } -func (g *gpu) Frame() error { +func (g *gpu) Frame(target RenderTarget) error { viewport := g.renderer.blitter.viewport - defFBO := g.ctx.BeginFrame(g.drawOps.clear, viewport) + defFBO := g.ctx.BeginFrame(target, g.drawOps.clear, viewport) defer g.ctx.EndFrame() for _, img := range g.drawOps.imageOps { expandPathOp(img.path, img.clip) diff --git a/gpu/headless/driver_test.go b/gpu/headless/driver_test.go index f23e7b7a..47058503 100644 --- a/gpu/headless/driver_test.go +++ b/gpu/headless/driver_test.go @@ -174,7 +174,7 @@ func newDriver(t *testing.T) driver.Device { if err != nil { t.Fatal(err) } - b.BeginFrame(true, image.Pt(1, 1)) + b.BeginFrame(nil, true, image.Pt(1, 1)) t.Cleanup(func() { b.EndFrame() ctx.ReleaseCurrent() diff --git a/gpu/headless/headless.go b/gpu/headless/headless.go index 6d6bd63b..46cddfa7 100644 --- a/gpu/headless/headless.go +++ b/gpu/headless/headless.go @@ -112,7 +112,7 @@ func (w *Window) Frame(frame *op.Ops) error { w.dev.BindFramebuffer(w.fbo) w.gpu.Clear(color.NRGBA{}) w.gpu.Collect(w.size, frame) - return w.gpu.Frame() + return w.gpu.Frame(driver.RenderTarget(w.fbo)) }) } diff --git a/gpu/internal/d3d11/d3d11_windows.go b/gpu/internal/d3d11/d3d11_windows.go index 60506ea7..318d1155 100644 --- a/gpu/internal/d3d11/d3d11_windows.go +++ b/gpu/internal/d3d11/d3d11_windows.go @@ -165,15 +165,23 @@ func newDirect3D11Device(api driver.Direct3D11) (driver.Device, error) { return b, nil } -func (b *Backend) BeginFrame(clear bool, viewport image.Point) driver.Framebuffer { - renderTarget, depthView := b.ctx.OMGetRenderTargets() - // Assume someone else is holding on to the render targets. - if renderTarget != nil { - d3d11.IUnknownRelease(unsafe.Pointer(renderTarget), renderTarget.Vtbl.Release) - } - if depthView != nil { - d3d11.IUnknownRelease(unsafe.Pointer(depthView), depthView.Vtbl.Release) +func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Framebuffer { + var ( + renderTarget *d3d11.RenderTargetView + depthView *d3d11.DepthStencilView + ) + if target != nil { + switch t := target.(type) { + case driver.Direct3D11RenderTarget: + renderTarget = (*d3d11.RenderTargetView)(t.RenderTarget) + depthView = (*d3d11.DepthStencilView)(t.DepthStencilView) + case *Framebuffer: + renderTarget, depthView = t.renderTarget, t.depthView + default: + panic(fmt.Errorf("opengl: invalid render target type: %T", target)) + } } + b.ctx.OMSetRenderTargets(renderTarget, depthView) return &Framebuffer{ctx: b.ctx, dev: b.dev, renderTarget: renderTarget, depthView: depthView, foreign: true} } @@ -713,6 +721,8 @@ func (f *Framebuffer) Release() { } } +func (f *Framebuffer) ImplementsRenderTarget() {} + func (b *Backend) BindInputLayout(layout driver.InputLayout) { b.ctx.IASetInputLayout(layout.(*InputLayout).layout) } diff --git a/gpu/internal/driver/api.go b/gpu/internal/driver/api.go index c15fb897..10a4cf2c 100644 --- a/gpu/internal/driver/api.go +++ b/gpu/internal/driver/api.go @@ -15,6 +15,19 @@ type API interface { implementsAPI() } +type RenderTarget interface { + ImplementsRenderTarget() +} + +type OpenGLRenderTarget gl.Framebuffer + +type Direct3D11RenderTarget struct { + // RenderTarget is a *ID3D11RenderTargetView. + RenderTarget unsafe.Pointer + // DepthStencilView is a *ID3D11DepthStencilView. + DepthStencilView unsafe.Pointer +} + type OpenGL struct { // ES forces the use of ANGLE OpenGL ES libraries on macOS. It is // ignored on all other platforms. @@ -55,5 +68,7 @@ func NewDevice(api API) (Device, error) { return nil, fmt.Errorf("driver: no driver available for the API %T", api) } -func (OpenGL) implementsAPI() {} -func (Direct3D11) implementsAPI() {} +func (OpenGL) implementsAPI() {} +func (Direct3D11) implementsAPI() {} +func (OpenGLRenderTarget) ImplementsRenderTarget() {} +func (Direct3D11RenderTarget) ImplementsRenderTarget() {} diff --git a/gpu/internal/driver/driver.go b/gpu/internal/driver/driver.go index a7e58f60..48b5e400 100644 --- a/gpu/internal/driver/driver.go +++ b/gpu/internal/driver/driver.go @@ -12,7 +12,7 @@ import ( // APIs such as OpenGL, Direct3D useful for rendering Gio // operations. type Device interface { - BeginFrame(clear bool, viewport image.Point) Framebuffer + BeginFrame(target RenderTarget, clear bool, viewport image.Point) Framebuffer EndFrame() Caps() Caps NewTimer() Timer @@ -155,6 +155,7 @@ type Buffer interface { } type Framebuffer interface { + RenderTarget Invalidate() Release() ReadPixels(src image.Rectangle, pixels []byte) error diff --git a/gpu/internal/opengl/opengl.go b/gpu/internal/opengl/opengl.go index 5ce3e2cb..8f2cfc07 100644 --- a/gpu/internal/opengl/opengl.go +++ b/gpu/internal/opengl/opengl.go @@ -212,12 +212,23 @@ func newOpenGLDevice(api driver.OpenGL) (driver.Device, error) { return b, nil } -func (b *Backend) BeginFrame(clear bool, viewport image.Point) driver.Framebuffer { +func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Framebuffer { b.clear = clear b.glstate = b.queryState() b.savedState = b.glstate b.state = state{} - renderFBO := b.glstate.drawFBO + var renderFBO gl.Framebuffer + if target != nil { + switch t := target.(type) { + case driver.OpenGLRenderTarget: + renderFBO = gl.Framebuffer(t) + case *gpuFramebuffer: + renderFBO = t.obj + default: + panic(fmt.Errorf("opengl: invalid render target type: %T", target)) + } + } + b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO) if b.gles { // If the output framebuffer is not in the sRGB colorspace already, emulate it. var fbEncoding int @@ -1244,6 +1255,8 @@ func (f *gpuFramebuffer) Release() { } } +func (f *gpuFramebuffer) ImplementsRenderTarget() {} + func toTexFilter(f driver.TextureFilter) int { switch f { case driver.FilterNearest: diff --git a/internal/egl/egl.go b/internal/egl/egl.go index 7452b62c..772841f5 100644 --- a/internal/egl/egl.go +++ b/internal/egl/egl.go @@ -98,6 +98,10 @@ func NewContext(disp NativeDisplayType) (*Context, error) { return c, nil } +func (c *Context) RenderTarget() gpu.RenderTarget { + return gpu.OpenGLRenderTarget{} +} + func (c *Context) API() gpu.API { return gpu.OpenGL{} }