From 0bdd24c51e53d16d46986651b44ced321e41a7c0 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 19 Sep 2021 19:36:48 +0200 Subject: [PATCH] gpu: fold driver.Framebuffer functionality into driver.Texture driver.Device.NewFramebuffer doesn't provide additional information over driver.Device.NewTexture, so Texture can hold its (optional) framebuffer on behalf of the renderers. Metal don't even need a separate framebuffer object. Signed-off-by: Elias Naur --- gpu/compute.go | 25 ++---- gpu/gpu.go | 4 +- gpu/headless/driver_test.go | 13 +-- gpu/headless/headless.go | 16 +--- gpu/internal/d3d11/d3d11_windows.go | 118 +++++++++++++--------------- gpu/internal/driver/driver.go | 19 ++--- gpu/internal/metal/metal_darwin.go | 55 +++++-------- gpu/internal/opengl/opengl.go | 79 ++++++++++--------- gpu/path.go | 10 +-- 9 files changed, 135 insertions(+), 204 deletions(-) diff --git a/gpu/compute.go b/gpu/compute.go index 7a2fa162..d01cbeed 100644 --- a/gpu/compute.go +++ b/gpu/compute.go @@ -149,7 +149,6 @@ type atlasMove struct { type textureAtlas struct { image driver.Texture - fbo driver.Framebuffer format driver.TextureFormat bindings driver.BufferBinding hasCPU bool @@ -356,7 +355,7 @@ type memoryHeader struct { type rectangle [4]f32.Point const ( - layersBindings = driver.BufferBindingShaderStorageWrite | driver.BufferBindingTexture | driver.BufferBindingFramebuffer + layersBindings = driver.BufferBindingShaderStorageWrite | driver.BufferBindingTexture materialsBindings = driver.BufferBindingFramebuffer | driver.BufferBindingShaderStorageRead // Materials and layers can share texture storage if their bindings match. combinedBindings = layersBindings | materialsBindings @@ -644,7 +643,7 @@ func (g *compute) frame(target RenderTarget) error { func (g *compute) dumpAtlases() { for i, a := range g.atlases { - dump, err := driver.DownloadImage(g.ctx, a.fbo, image.Rectangle{Max: a.size}) + dump, err := driver.DownloadImage(g.ctx, a.image, image.Rectangle{Max: a.size}) if err != nil { panic(err) } @@ -752,7 +751,7 @@ func (g *compute) compactAllocs() error { } for _, move := range g.moves { if !move.cpu { - g.ctx.CopyTexture(dstAtlas.image, move.dstPos, move.src.fbo, move.srcRect) + g.ctx.CopyTexture(dstAtlas.image, move.dstPos, move.src.image, move.srcRect) } else { src := move.src.cpuImage.Data() dst := dstAtlas.cpuImage.Data() @@ -848,7 +847,7 @@ func (g *compute) renderLayers(viewport image.Point) error { return nil } -func (g *compute) blitLayers(d driver.LoadDesc, fbo driver.Framebuffer, viewport image.Point) { +func (g *compute) blitLayers(d driver.LoadDesc, fbo driver.Texture, viewport image.Point) { layers := g.collector.frame.layers g.output.layerVertices = g.output.layerVertices[:0] for _, l := range layers { @@ -1008,7 +1007,7 @@ func (g *compute) renderMaterials() error { d.Action = driver.LoadActionClear } g.ctx.PrepareTexture(imgAtlas.image) - g.ctx.BeginRenderPass(atlas.fbo, d) + g.ctx.BeginRenderPass(atlas.image, d) g.ctx.BindTexture(0, imgAtlas.image) g.ctx.BindPipeline(m.pipeline) g.ctx.BindUniforms(m.uniforms.buf) @@ -1023,14 +1022,14 @@ func (g *compute) renderMaterials() error { if !g.useCPU { continue } - copyFBO := atlas.fbo + src := atlas.image data := atlas.cpuImage.Data() for _, a := range newAllocs { stride := atlas.size.X * 4 col := a.rect.Min.X * 4 row := stride * a.rect.Min.Y off := col + row - copyFBO.ReadPixels(a.rect, data[off:], stride) + src.ReadPixels(a.rect, data[off:], stride) } } return nil @@ -1458,12 +1457,6 @@ func (a *textureAtlas) resize(ctx driver.Device, size image.Point) error { if err != nil { return err } - fbo, err := ctx.NewFramebuffer(img) - if err != nil { - img.Release() - return err - } - a.fbo = fbo a.image = img a.size = size return nil @@ -1518,10 +1511,6 @@ func (g *compute) Release() { } func (a *textureAtlas) Release() { - if a.fbo != nil { - a.fbo.Release() - a.fbo = nil - } if a.image != nil { a.image.Release() a.image = nil diff --git a/gpu/gpu.go b/gpu/gpu.go index cd3c0e0c..9e444176 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -657,7 +657,7 @@ func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) { } fbo = p.place.Idx f := r.pather.stenciler.cover(fbo) - r.ctx.BeginRenderPass(f.fbo, driver.LoadDesc{Action: driver.LoadActionClear}) + 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) } @@ -697,7 +697,7 @@ func (r *renderer) intersect(ops []imageOp) { f := r.pather.stenciler.intersections.fbos[fbo] d := driver.LoadDesc{Action: driver.LoadActionClear} d.ClearColor.R = 1.0 - r.ctx.BeginRenderPass(f.fbo, d) + r.ctx.BeginRenderPass(f.tex, d) r.ctx.BindPipeline(r.pather.stenciler.ipipeline.pipeline.pipeline) r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) } diff --git a/gpu/headless/driver_test.go b/gpu/headless/driver_test.go index de18f8ad..34e4d530 100644 --- a/gpu/headless/driver_test.go +++ b/gpu/headless/driver_test.go @@ -144,7 +144,7 @@ func TestFramebuffers(t *testing.T) { } } -func newFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer { +func newFBO(t *testing.T, b driver.Device, size image.Point) driver.Texture { fboTex, err := b.NewTexture( driver.TextureFormatSRGBA, size.X, size.Y, @@ -157,14 +157,7 @@ func newFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer t.Cleanup(func() { fboTex.Release() }) - fbo, err := b.NewFramebuffer(fboTex) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - fbo.Release() - }) - return fbo + return fboTex } func newDriver(t *testing.T) driver.Device { @@ -191,7 +184,7 @@ func newDriver(t *testing.T) driver.Device { return b } -func screenshot(t *testing.T, d driver.Device, fbo driver.Framebuffer, size image.Point) *image.RGBA { +func screenshot(t *testing.T, d driver.Device, fbo driver.Texture, size image.Point) *image.RGBA { img, err := driver.DownloadImage(d, fbo, image.Rectangle{Max: size}) if err != nil { t.Fatal(err) diff --git a/gpu/headless/headless.go b/gpu/headless/headless.go index 9d4d95a3..5122d600 100644 --- a/gpu/headless/headless.go +++ b/gpu/headless/headless.go @@ -21,7 +21,6 @@ type Window struct { dev driver.Device gpu gpu.GPU fboTex driver.Texture - fbo driver.Framebuffer } type context interface { @@ -56,20 +55,13 @@ func NewWindow(width, height int) (*Window, error) { if err != nil { return nil } - fbo, err := dev.NewFramebuffer(fboTex) - if err != nil { - fboTex.Release() - return err - } gp, err := gpu.New(api) if err != nil { - fbo.Release() fboTex.Release() dev.Release() return err } w.fboTex = fboTex - w.fbo = fbo w.gpu = gp w.dev = dev return err @@ -84,10 +76,6 @@ func NewWindow(width, height int) (*Window, error) { // Release resources associated with the window. func (w *Window) Release() { contextDo(w.ctx, func() error { - if w.fbo != nil { - w.fbo.Release() - w.fbo = nil - } if w.fboTex != nil { w.fboTex.Release() w.fboTex = nil @@ -113,7 +101,7 @@ func (w *Window) Release() { func (w *Window) Frame(frame *op.Ops) error { return contextDo(w.ctx, func() error { w.gpu.Clear(color.NRGBA{}) - return w.gpu.Frame(frame, driver.RenderTarget(w.fbo), w.size) + return w.gpu.Frame(frame, w.fboTex, w.size) }) } @@ -122,7 +110,7 @@ func (w *Window) Screenshot() (*image.RGBA, error) { var img *image.RGBA err := contextDo(w.ctx, func() error { var err error - img, err = driver.DownloadImage(w.dev, w.fbo, image.Rectangle{Max: w.size}) + img, err = driver.DownloadImage(w.dev, w.fboTex, image.Rectangle{Max: w.size}) return err }) if err != nil { diff --git a/gpu/internal/d3d11/d3d11_windows.go b/gpu/internal/d3d11/d3d11_windows.go index dd836483..c5bcfc9c 100644 --- a/gpu/internal/d3d11/d3d11_windows.go +++ b/gpu/internal/d3d11/d3d11_windows.go @@ -35,9 +35,6 @@ type Backend struct { caps driver.Caps - // fbo is the currently bound fbo. - fbo *Framebuffer - floatFormat uint32 } @@ -51,15 +48,18 @@ type Pipeline struct { } type Texture struct { - backend *Backend - format uint32 - bindings driver.BufferBinding - tex *d3d11.Texture2D - sampler *d3d11.SamplerState - resView *d3d11.ShaderResourceView - uaView *d3d11.UnorderedAccessView - width int - height int + 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 + foreign bool } type VertexShader struct { @@ -78,15 +78,6 @@ type Program struct { shader *d3d11.ComputeShader } -type Framebuffer struct { - dev *d3d11.Device - ctx *d3d11.DeviceContext - format uint32 - resource *d3d11.Resource - renderTarget *d3d11.RenderTargetView - foreign bool -} - type Buffer struct { backend *Backend bind uint32 @@ -159,7 +150,7 @@ func newDirect3D11Device(api driver.Direct3D11) (driver.Device, error) { return b, nil } -func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Framebuffer { +func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture { var ( renderTarget *d3d11.RenderTargetView ) @@ -167,19 +158,19 @@ func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport im switch t := target.(type) { case driver.Direct3D11RenderTarget: renderTarget = (*d3d11.RenderTargetView)(t.RenderTarget) - case *Framebuffer: + case *Texture: renderTarget = t.renderTarget default: panic(fmt.Errorf("d3d11: invalid render target type: %T", target)) } } b.ctx.OMSetRenderTargets(renderTarget, nil) - return &Framebuffer{ctx: b.ctx, dev: b.dev, renderTarget: renderTarget, foreign: true} + return &Texture{backend: b, renderTarget: renderTarget, foreign: true} } -func (b *Backend) CopyTexture(dstTex driver.Texture, dstOrigin image.Point, srcFBO driver.Framebuffer, srcRect image.Rectangle) { +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 := srcFBO.(*Framebuffer).resource + src := (*d3d11.Resource)(srcTex.(*Texture).tex) b.ctx.CopySubresourceRegion( dst, 0, // Destination subresource. @@ -248,6 +239,7 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min sampler *d3d11.SamplerState resView *d3d11.ShaderResourceView uaView *d3d11.UnorderedAccessView + fbo *d3d11.RenderTargetView ) if bindings&driver.BufferBindingTexture != 0 { var filter uint32 @@ -317,21 +309,24 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min return nil, err } } - return &Texture{backend: b, format: d3dfmt, tex: tex, sampler: sampler, resView: resView, uaView: uaView, bindings: bindings, width: width, height: height}, nil -} - -func (b *Backend) NewFramebuffer(tex driver.Texture) (driver.Framebuffer, error) { - d3dtex := tex.(*Texture) - if d3dtex.bindings&driver.BufferBindingFramebuffer == 0 { - return nil, errors.New("the texture was created without BufferBindingFramebuffer binding") + 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 + } } - resource := (*d3d11.Resource)(unsafe.Pointer(d3dtex.tex)) - renderTarget, err := b.dev.CreateRenderTargetView(resource) - if err != nil { - return nil, err - } - fbo := &Framebuffer{ctx: b.ctx, dev: b.dev, format: d3dtex.format, resource: resource, renderTarget: renderTarget} - return fbo, nil + return &Texture{backend: b, format: d3dfmt, tex: tex, sampler: sampler, resView: resView, uaView: uaView, renderTarget: fbo, bindings: bindings, width: width, height: height}, nil } func (b *Backend) newInputLayout(vertexShader shader.Sources, layout []driver.InputDesc) (*d3d11.InputLayout, error) { @@ -620,6 +615,12 @@ func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) { } 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) } @@ -736,17 +737,14 @@ func (b *Buffer) Release() { *b = Buffer{} } -func (f *Framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) error { - if f.resource == nil { - return errors.New("framebuffer does not support ReadPixels") - } +func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error { w, h := src.Dx(), src.Dy() - tex, err := f.dev.CreateTexture2D(&d3d11.TEXTURE2D_DESC{ + tex, err := t.backend.dev.CreateTexture2D(&d3d11.TEXTURE2D_DESC{ Width: uint32(w), Height: uint32(h), MipLevels: 1, ArraySize: 1, - Format: f.format, + Format: t.format, SampleDesc: d3d11.DXGI_SAMPLE_DESC{ Count: 1, Quality: 0, @@ -759,11 +757,11 @@ func (f *Framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) } defer d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release) res := (*d3d11.Resource)(unsafe.Pointer(tex)) - f.ctx.CopySubresourceRegion( + t.backend.ctx.CopySubresourceRegion( res, 0, // Destination subresource. 0, 0, 0, // Destination coordinates (x, y, z). - f.resource, + (*d3d11.Resource)(t.tex), 0, // Source subresource. &d3d11.BOX{ Left: uint32(src.Min.X), @@ -774,11 +772,11 @@ func (f *Framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) Back: 1, }, ) - resMap, err := f.ctx.Map(res, 0, d3d11.MAP_READ, 0) + resMap, err := t.backend.ctx.Map(res, 0, d3d11.MAP_READ, 0) if err != nil { return fmt.Errorf("ReadPixels: %v", err) } - defer f.ctx.Unmap(res, 0) + defer t.backend.ctx.Unmap(res, 0) srcPitch := stride dstPitch := int(resMap.RowPitch) mapSize := dstPitch * h @@ -797,30 +795,20 @@ func (b *Backend) BeginCompute() { func (b *Backend) EndCompute() { } -func (b *Backend) BeginRenderPass(fbo driver.Framebuffer, d driver.LoadDesc) { - b.fbo = fbo.(*Framebuffer) - b.ctx.OMSetRenderTargets(b.fbo.renderTarget, nil) +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(b.fbo.renderTarget, &b.clearColor) + b.ctx.ClearRenderTargetView(t.renderTarget, &b.clearColor) } } func (b *Backend) EndRenderPass() { } -func (f *Framebuffer) Release() { - if f.foreign { - panic("framebuffer not created by NewFramebuffer") - } - if f.renderTarget != nil { - d3d11.IUnknownRelease(unsafe.Pointer(f.renderTarget), f.renderTarget.Vtbl.Release) - f.renderTarget = nil - } -} - -func (f *Framebuffer) ImplementsRenderTarget() {} +func (f *Texture) ImplementsRenderTarget() {} func convBufferBinding(typ driver.BufferBinding) uint32 { var bindings uint32 diff --git a/gpu/internal/driver/driver.go b/gpu/internal/driver/driver.go index 9a1ba3a2..a67883c7 100644 --- a/gpu/internal/driver/driver.go +++ b/gpu/internal/driver/driver.go @@ -15,7 +15,7 @@ import ( // APIs such as OpenGL, Direct3D useful for rendering Gio // operations. type Device interface { - BeginFrame(target RenderTarget, clear bool, viewport image.Point) Framebuffer + BeginFrame(target RenderTarget, clear bool, viewport image.Point) Texture EndFrame() Caps() Caps NewTimer() Timer @@ -23,7 +23,6 @@ type Device interface { // are valid at the point of call. IsTimeContinuous() bool NewTexture(format TextureFormat, width, height int, minFilter, magFilter TextureFilter, bindings BufferBinding) (Texture, error) - NewFramebuffer(tex Texture) (Framebuffer, error) NewImmutableBuffer(typ BufferBinding, data []byte) (Buffer, error) NewBuffer(typ BufferBinding, size int) (Buffer, error) NewComputeProgram(shader shader.Sources) (Program, error) @@ -35,7 +34,7 @@ type Device interface { DrawArrays(off, count int) DrawElements(off, count int) - BeginRenderPass(f Framebuffer, desc LoadDesc) + BeginRenderPass(t Texture, desc LoadDesc) EndRenderPass() PrepareTexture(t Texture) BindProgram(p Program) @@ -49,7 +48,7 @@ type Device interface { BeginCompute() EndCompute() - CopyTexture(dst Texture, dstOrigin image.Point, src Framebuffer, srcRect image.Rectangle) + CopyTexture(dst Texture, dstOrigin image.Point, src Texture, srcRect image.Rectangle) MemoryBarrier() DispatchCompute(x, y, z int) @@ -133,12 +132,6 @@ type Buffer interface { Download(data []byte) error } -type Framebuffer interface { - RenderTarget - Release() - ReadPixels(src image.Rectangle, pixels []byte, stride int) error -} - type Timer interface { Begin() End() @@ -147,7 +140,9 @@ type Timer interface { } type Texture interface { + RenderTarget Upload(offset, size image.Point, pixels []byte, stride int) + ReadPixels(src image.Rectangle, pixels []byte, stride int) error Release() } @@ -210,9 +205,9 @@ func (f Features) Has(feats Features) bool { return f&feats == feats } -func DownloadImage(d Device, f Framebuffer, r image.Rectangle) (*image.RGBA, error) { +func DownloadImage(d Device, t Texture, r image.Rectangle) (*image.RGBA, error) { img := image.NewRGBA(r) - if err := f.ReadPixels(r, img.Pix, img.Stride); err != nil { + if err := t.ReadPixels(r, img.Pix, img.Stride); err != nil { return nil, err } if d.Caps().BottomLeftOrigin { diff --git a/gpu/internal/metal/metal_darwin.go b/gpu/internal/metal/metal_darwin.go index ac9a6959..96e17258 100644 --- a/gpu/internal/metal/metal_darwin.go +++ b/gpu/internal/metal/metal_darwin.go @@ -407,6 +407,7 @@ type Texture struct { sampler C.CFTypeRef width int height int + foreign bool } type Shader struct { @@ -424,12 +425,6 @@ type Pipeline struct { topology C.MTLPrimitiveType } -type Framebuffer struct { - backend *Backend - texture C.CFTypeRef - foreign bool -} - type Buffer struct { backend *Backend size int @@ -469,7 +464,7 @@ func newMetalDevice(api driver.Metal) (driver.Device, error) { return b, nil } -func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Framebuffer { +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 @@ -477,13 +472,11 @@ func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport im if target == nil { return nil } - var texture C.CFTypeRef switch t := target.(type) { case driver.MetalRenderTarget: - texture = C.CFTypeRef(t.Texture) - return &Framebuffer{texture: texture, foreign: true} - case *Framebuffer: - texture = C.CFTypeRef(t.texture) + 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)) @@ -503,10 +496,10 @@ func (b *Backend) startBlit() C.CFTypeRef { return b.blitEnc } -func (b *Backend) CopyTexture(dst driver.Texture, dorig image.Point, src driver.Framebuffer, srect image.Rectangle) { +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.(*Framebuffer).texture + srcTex := src.(*Texture).texture ssz := srect.Size() C.blitEncCopyFromTexture( enc, @@ -718,13 +711,6 @@ func pixelFormatFor(f driver.TextureFormat) C.MTLPixelFormat { } } -func (b *Backend) NewFramebuffer(tex driver.Texture) (driver.Framebuffer, error) { - t := tex.(*Texture) - C.CFRetain(t.texture) - fbo := &Framebuffer{backend: b, texture: t.texture} - return fbo, nil -} - 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 @@ -921,6 +907,9 @@ func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) { } func (t *Texture) Release() { + if t.foreign { + panic("metal: release of external texture") + } C.CFRelease(t.texture) C.CFRelease(t.sampler) *t = Texture{} @@ -1076,7 +1065,7 @@ func (b *Buffer) Release() { *b = Buffer{} } -func (f *Framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) error { +func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error { if len(pixels) == 0 { return nil } @@ -1092,10 +1081,10 @@ func (f *Framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) } stageStride := sz.X * 4 n := sz.Y * stageStride - buf, off := f.backend.stagingBuffer(n) - enc := f.backend.startBlit() - C.blitEncCopyTextureToBuffer(enc, f.texture, buf, C.NSUInteger(off), C.NSUInteger(stageStride), C.NSUInteger(n), msize, orig) - f.backend.endCmdBuffer(true) + 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++ { @@ -1108,10 +1097,10 @@ func (f *Framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) return nil } -func (b *Backend) BeginRenderPass(fbo driver.Framebuffer, d driver.LoadDesc) { +func (b *Backend) BeginRenderPass(tex driver.Texture, d driver.LoadDesc) { b.endEncoder() b.ensureCmdBuffer() - f := fbo.(*Framebuffer) + f := tex.(*Texture) col := d.ClearColor var act C.MTLLoadAction switch d.Action { @@ -1151,12 +1140,4 @@ func (b *Backend) endEncoder() { } } -func (f *Framebuffer) Release() { - if f.foreign { - panic("metal: invalid release of external framebuffer") - } - C.CFRelease(f.texture) - *f = Framebuffer{} -} - -func (f *Framebuffer) ImplementsRenderTarget() {} +func (f *Texture) ImplementsRenderTarget() {} diff --git a/gpu/internal/opengl/opengl.go b/gpu/internal/opengl/opengl.go index 309d0a09..08879fc5 100644 --- a/gpu/internal/opengl/opengl.go +++ b/gpu/internal/opengl/opengl.go @@ -98,14 +98,11 @@ type timer struct { type texture struct { backend *Backend obj gl.Texture + fbo gl.Framebuffer + hasFBO bool triple textureTriple width int height int -} - -type framebuffer struct { - backend *Backend - obj gl.Framebuffer foreign bool } @@ -213,7 +210,7 @@ func newOpenGLDevice(api driver.OpenGL) (driver.Device, error) { return b, nil } -func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Framebuffer { +func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture { b.clear = clear b.glstate = b.queryState() b.savedState = b.glstate @@ -223,8 +220,8 @@ func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport im switch t := target.(type) { case driver.OpenGLRenderTarget: renderFBO = gl.Framebuffer(t) - case *framebuffer: - renderFBO = t.obj + case *texture: + renderFBO = t.ensureFBO() default: panic(fmt.Errorf("opengl: invalid render target type: %T", target)) } @@ -265,7 +262,7 @@ func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport im if b.sRGBFBO != nil && !clear { b.clearOutput(0, 0, 0, 0) } - return &framebuffer{backend: b, obj: renderFBO, foreign: true} + return &texture{backend: b, fbo: renderFBO, hasFBO: true, foreign: true} } func (b *Backend) EndFrame() { @@ -650,22 +647,30 @@ func (b *Backend) IsTimeContinuous() bool { return b.funcs.GetInteger(gl.GPU_DISJOINT_EXT) == gl.FALSE } -func (b *Backend) NewFramebuffer(tex driver.Texture) (driver.Framebuffer, error) { +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) - gltex := tex.(*texture) fb := b.funcs.CreateFramebuffer() - fbo := &framebuffer{backend: b, obj: fb} - b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo.obj) + b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fb) if err := glErr(b.funcs); err != nil { - fbo.Release() - return nil, err + b.funcs.DeleteFramebuffer(fb) + panic(err) } - b.funcs.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, gltex.obj, 0) + 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 { - fbo.Release() - return nil, fmt.Errorf("incomplete framebuffer, status = 0x%x, err = %d", st, b.funcs.GetError()) + b.funcs.DeleteFramebuffer(fb) + panic(fmt.Errorf("incomplete framebuffer, status = 0x%x, err = %d", st, b.funcs.GetError())) } - return fbo, nil + 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) { @@ -1120,21 +1125,21 @@ func (b *Backend) BindIndexBuffer(buf driver.Buffer) { b.glstate.bindBuffer(b.funcs, gl.ELEMENT_ARRAY_BUFFER, gbuf.obj) } -func (b *Backend) CopyTexture(dst driver.Texture, dstOrigin image.Point, src driver.Framebuffer, srcRect image.Rectangle) { +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.READ_FRAMEBUFFER, src.(*framebuffer).obj) + b.glstate.bindFramebuffer(b.funcs, gl.READ_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 (f *framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) error { - glErr(f.backend.funcs) - f.backend.glstate.bindFramebuffer(f.backend.funcs, gl.FRAMEBUFFER, f.obj) +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()) if len(pixels) < src.Dx()*src.Dy()*4 { return errors.New("unexpected RGBA size") } @@ -1144,9 +1149,9 @@ func (f *framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) if n := stride / 4; n != w { rowLen = n } - f.backend.glstate.pixelStorei(f.backend.funcs, gl.PACK_ROW_LENGTH, rowLen) - f.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels) - return glErr(f.backend.funcs) + 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) + return glErr(t.backend.funcs) } func (b *Backend) BindPipeline(pl driver.Pipeline) { @@ -1163,8 +1168,9 @@ func (b *Backend) BeginCompute() { func (b *Backend) EndCompute() { } -func (b *Backend) BeginRenderPass(fbo driver.Framebuffer, desc driver.LoadDesc) { - b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo.(*framebuffer).obj) +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 @@ -1177,14 +1183,7 @@ func (b *Backend) BeginRenderPass(fbo driver.Framebuffer, desc driver.LoadDesc) func (b *Backend) EndRenderPass() { } -func (f *framebuffer) Release() { - if f.foreign { - panic("framebuffer not created by NewFramebuffer") - } - f.backend.glstate.deleteFramebuffer(f.backend.funcs, f.obj) -} - -func (f *framebuffer) ImplementsRenderTarget() {} +func (f *texture) ImplementsRenderTarget() {} func (p *pipeline) Release() { p.prog.Release() @@ -1209,6 +1208,12 @@ func (b *Backend) BindTexture(unit int, t driver.Texture) { } 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) } diff --git a/gpu/path.go b/gpu/path.go index 1b384d09..5fd73cf0 100644 --- a/gpu/path.go +++ b/gpu/path.go @@ -95,7 +95,6 @@ type fboSet struct { type stencilFBO struct { size image.Point - fbo driver.Framebuffer tex driver.Texture } @@ -257,8 +256,7 @@ func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) { waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y) resize = resize || waste > 1.2 if resize { - if f.fbo != nil { - f.fbo.Release() + if f.tex != nil { f.tex.Release() } tex, err := ctx.NewTexture(driver.TextureFormatFloat, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest, @@ -266,13 +264,8 @@ func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) { if err != nil { panic(err) } - fbo, err := ctx.NewFramebuffer(tex) - if err != nil { - panic(err) - } f.size = sz f.tex = tex - f.fbo = fbo } } // Delete extra fbos. @@ -282,7 +275,6 @@ func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) { func (s *fboSet) delete(ctx driver.Device, idx int) { for i := idx; i < len(s.fbos); i++ { f := s.fbos[i] - f.fbo.Release() f.tex.Release() } s.fbos = s.fbos[:idx]