mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 10:25:37 +00:00
gpu: fold buffer clearing into framebuffer bind
In Metal, clearing a framebuffer is most efficiently done during bind. Modify our driver accordingly. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+12
-13
@@ -582,12 +582,6 @@ func (g *compute) Frame(target RenderTarget) error {
|
|||||||
t.blit = t.t.newTimer()
|
t.blit = t.t.newTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
g.ctx.BindFramebuffer(defFBO)
|
|
||||||
if g.collector.clear {
|
|
||||||
g.collector.clear = false
|
|
||||||
g.ctx.Clear(g.collector.clearColor.Float32())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.compact.begin()
|
t.compact.begin()
|
||||||
if err := g.compactLayers(); err != nil {
|
if err := g.compactLayers(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -598,7 +592,14 @@ func (g *compute) Frame(target RenderTarget) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.render.end()
|
t.render.end()
|
||||||
g.ctx.BindFramebuffer(defFBO)
|
var d driver.LoadDesc
|
||||||
|
if g.collector.clear {
|
||||||
|
g.collector.clear = false
|
||||||
|
d.Action = driver.LoadActionClear
|
||||||
|
c := &d.ClearColor
|
||||||
|
c.R, c.G, c.B, c.A = g.collector.clearColor.Float32()
|
||||||
|
}
|
||||||
|
g.ctx.BindFramebuffer(defFBO, d)
|
||||||
t.blit.begin()
|
t.blit.begin()
|
||||||
g.blitLayers(viewport)
|
g.blitLayers(viewport)
|
||||||
t.blit.end()
|
t.blit.end()
|
||||||
@@ -691,9 +692,6 @@ func (g *compute) compactLayers() error {
|
|||||||
dr := image.Rectangle{Min: l.newPlace.pos, Max: l.newPlace.pos.Add(sz)}
|
dr := image.Rectangle{Min: l.newPlace.pos, Max: l.newPlace.pos.Add(sz)}
|
||||||
g.ctx.BlitFramebuffer(dst, src, sr, dr)
|
g.ctx.BlitFramebuffer(dst, src, sr, dr)
|
||||||
l.place.atlas.layers--
|
l.place.atlas.layers--
|
||||||
if l.place.atlas.layers == 0 {
|
|
||||||
l.place.atlas.fbo.Invalidate()
|
|
||||||
}
|
|
||||||
layers[i].place = l.newPlace
|
layers[i].place = l.newPlace
|
||||||
}
|
}
|
||||||
layers = layers[end:]
|
layers = layers[end:]
|
||||||
@@ -926,11 +924,12 @@ restart:
|
|||||||
m.buffer.ensureCapacity(false, g.ctx, driver.BufferBindingVertices, n)
|
m.buffer.ensureCapacity(false, g.ctx, driver.BufferBindingVertices, n)
|
||||||
m.buffer.buffer.Upload(vertexData)
|
m.buffer.buffer.Upload(vertexData)
|
||||||
g.ctx.BindTexture(0, g.images.tex)
|
g.ctx.BindTexture(0, g.images.tex)
|
||||||
g.ctx.BindFramebuffer(m.fbo)
|
var d driver.LoadDesc
|
||||||
g.ctx.Viewport(0, 0, texSize, texSize)
|
|
||||||
if reclaimed {
|
if reclaimed {
|
||||||
g.ctx.Clear(0, 0, 0, 0)
|
d.Action = driver.LoadActionClear
|
||||||
}
|
}
|
||||||
|
g.ctx.BindFramebuffer(m.fbo, d)
|
||||||
|
g.ctx.Viewport(0, 0, texSize, texSize)
|
||||||
g.ctx.BindPipeline(m.pipeline)
|
g.ctx.BindPipeline(m.pipeline)
|
||||||
g.ctx.BindVertexBuffer(m.buffer.buffer, int(unsafe.Sizeof(m.quads[0])), 0)
|
g.ctx.BindVertexBuffer(m.buffer.buffer, int(unsafe.Sizeof(m.quads[0])), 0)
|
||||||
g.ctx.DrawArrays(driver.DrawModeTriangles, 0, len(m.quads))
|
g.ctx.DrawArrays(driver.DrawModeTriangles, 0, len(m.quads))
|
||||||
|
|||||||
+12
-12
@@ -445,11 +445,6 @@ func (g *gpu) Frame(target RenderTarget) error {
|
|||||||
for _, img := range g.drawOps.imageOps {
|
for _, img := range g.drawOps.imageOps {
|
||||||
expandPathOp(img.path, img.clip)
|
expandPathOp(img.path, img.clip)
|
||||||
}
|
}
|
||||||
g.ctx.BindFramebuffer(defFBO)
|
|
||||||
if g.drawOps.clear {
|
|
||||||
g.drawOps.clear = false
|
|
||||||
g.ctx.Clear(g.drawOps.clearColor.Float32())
|
|
||||||
}
|
|
||||||
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
||||||
g.stencilTimer.begin()
|
g.stencilTimer.begin()
|
||||||
g.renderer.packStencils(&g.drawOps.pathOps)
|
g.renderer.packStencils(&g.drawOps.pathOps)
|
||||||
@@ -458,12 +453,17 @@ func (g *gpu) Frame(target RenderTarget) error {
|
|||||||
g.renderer.intersect(g.drawOps.imageOps)
|
g.renderer.intersect(g.drawOps.imageOps)
|
||||||
g.stencilTimer.end()
|
g.stencilTimer.end()
|
||||||
g.coverTimer.begin()
|
g.coverTimer.begin()
|
||||||
g.ctx.BindFramebuffer(defFBO)
|
var d driver.LoadDesc
|
||||||
|
if g.drawOps.clear {
|
||||||
|
g.drawOps.clear = false
|
||||||
|
d.Action = driver.LoadActionClear
|
||||||
|
c := &d.ClearColor
|
||||||
|
c.R, c.G, c.B, c.A = g.drawOps.clearColor.Float32()
|
||||||
|
}
|
||||||
|
g.ctx.BindFramebuffer(defFBO, d)
|
||||||
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
||||||
g.renderer.drawOps(g.cache, g.drawOps.imageOps)
|
g.renderer.drawOps(g.cache, g.drawOps.imageOps)
|
||||||
g.renderer.pather.stenciler.invalidateFBO()
|
|
||||||
g.coverTimer.end()
|
g.coverTimer.end()
|
||||||
g.ctx.BindFramebuffer(defFBO)
|
|
||||||
g.cleanupTimer.begin()
|
g.cleanupTimer.begin()
|
||||||
g.cache.frame()
|
g.cache.frame()
|
||||||
g.drawOps.pathCache.frame()
|
g.drawOps.pathCache.frame()
|
||||||
@@ -689,8 +689,7 @@ func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
|
|||||||
if fbo != p.place.Idx {
|
if fbo != p.place.Idx {
|
||||||
fbo = p.place.Idx
|
fbo = p.place.Idx
|
||||||
f := r.pather.stenciler.cover(fbo)
|
f := r.pather.stenciler.cover(fbo)
|
||||||
r.ctx.BindFramebuffer(f.fbo)
|
r.ctx.BindFramebuffer(f.fbo, driver.LoadDesc{Action: driver.LoadActionClear})
|
||||||
r.ctx.Clear(0.0, 0.0, 0.0, 0.0)
|
|
||||||
}
|
}
|
||||||
v, _ := pathCache.get(p.pathKey)
|
v, _ := pathCache.get(p.pathKey)
|
||||||
r.pather.stencilPath(p.clip, p.off, p.place.Pos, v.data)
|
r.pather.stencilPath(p.clip, p.off, p.place.Pos, v.data)
|
||||||
@@ -711,8 +710,9 @@ func (r *renderer) intersect(ops []imageOp) {
|
|||||||
if fbo != img.place.Idx {
|
if fbo != img.place.Idx {
|
||||||
fbo = img.place.Idx
|
fbo = img.place.Idx
|
||||||
f := r.pather.stenciler.intersections.fbos[fbo]
|
f := r.pather.stenciler.intersections.fbos[fbo]
|
||||||
r.ctx.BindFramebuffer(f.fbo)
|
d := driver.LoadDesc{Action: driver.LoadActionClear}
|
||||||
r.ctx.Clear(1.0, 0.0, 0.0, 0.0)
|
d.ClearColor.R = 1.0
|
||||||
|
r.ctx.BindFramebuffer(f.fbo, d)
|
||||||
}
|
}
|
||||||
r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy())
|
r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy())
|
||||||
r.intersectPath(img.path, img.clip)
|
r.intersectPath(img.path, img.clip)
|
||||||
|
|||||||
@@ -141,10 +141,12 @@ func TestFramebuffers(t *testing.T) {
|
|||||||
col2 = color.NRGBA{R: 0xfe, G: 0xba, B: 0xbe, A: 0xca}
|
col2 = color.NRGBA{R: 0xfe, G: 0xba, B: 0xbe, A: 0xca}
|
||||||
)
|
)
|
||||||
fcol1, fcol2 := f32color.LinearFromSRGB(col1), f32color.LinearFromSRGB(col2)
|
fcol1, fcol2 := f32color.LinearFromSRGB(col1), f32color.LinearFromSRGB(col2)
|
||||||
b.BindFramebuffer(fbo1)
|
d := driver.LoadDesc{Action: driver.LoadActionClear}
|
||||||
b.Clear(fcol1.Float32())
|
c := &d.ClearColor
|
||||||
b.BindFramebuffer(fbo2)
|
c.R, c.G, c.B, c.A = fcol1.Float32()
|
||||||
b.Clear(fcol2.Float32())
|
b.BindFramebuffer(fbo1, d)
|
||||||
|
c.R, c.G, c.B, c.A = fcol2.Float32()
|
||||||
|
b.BindFramebuffer(fbo2, d)
|
||||||
img := screenshot(t, b, fbo1, sz)
|
img := screenshot(t, b, fbo1, sz)
|
||||||
if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col1) {
|
if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col1) {
|
||||||
t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col1))
|
t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col1))
|
||||||
@@ -157,11 +159,13 @@ func TestFramebuffers(t *testing.T) {
|
|||||||
|
|
||||||
func setupFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer {
|
func setupFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer {
|
||||||
fbo := newFBO(t, b, size)
|
fbo := newFBO(t, b, size)
|
||||||
b.BindFramebuffer(fbo)
|
|
||||||
// ClearColor accepts linear RGBA colors, while 8-bit colors
|
// ClearColor accepts linear RGBA colors, while 8-bit colors
|
||||||
// are in the sRGB color space.
|
// are in the sRGB color space.
|
||||||
col := f32color.LinearFromSRGB(clearCol)
|
col := f32color.LinearFromSRGB(clearCol)
|
||||||
b.Clear(col.Float32())
|
d := driver.LoadDesc{Action: driver.LoadActionClear}
|
||||||
|
c := &d.ClearColor
|
||||||
|
c.R, c.G, c.B, c.A = col.Float32()
|
||||||
|
b.BindFramebuffer(fbo, d)
|
||||||
b.Viewport(0, 0, size.X, size.Y)
|
b.Viewport(0, 0, size.X, size.Y)
|
||||||
return fbo
|
return fbo
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,11 +432,6 @@ func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader,
|
|||||||
return &FragmentShader{b, fs}, nil
|
return &FragmentShader{b, fs}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) Clear(colr, colg, colb, cola float32) {
|
|
||||||
b.clearColor = [4]float32{colr, colg, colb, cola}
|
|
||||||
b.ctx.ClearRenderTargetView(b.fbo.renderTarget, &b.clearColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Backend) Viewport(x, y, width, height int) {
|
func (b *Backend) Viewport(x, y, width, height int) {
|
||||||
b.viewport = d3d11.VIEWPORT{
|
b.viewport = d3d11.VIEWPORT{
|
||||||
TopLeftX: float32(x),
|
TopLeftX: float32(x),
|
||||||
@@ -643,9 +638,14 @@ func (f *Framebuffer) ReadPixels(src image.Rectangle, pixels []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) BindFramebuffer(fbo driver.Framebuffer) {
|
func (b *Backend) BindFramebuffer(fbo driver.Framebuffer, d driver.LoadDesc) {
|
||||||
b.fbo = fbo.(*Framebuffer)
|
b.fbo = fbo.(*Framebuffer)
|
||||||
b.ctx.OMSetRenderTargets(b.fbo.renderTarget, nil)
|
b.ctx.OMSetRenderTargets(b.fbo.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Framebuffer) Invalidate() {
|
func (f *Framebuffer) Invalidate() {
|
||||||
|
|||||||
@@ -30,14 +30,13 @@ type Device interface {
|
|||||||
NewFragmentShader(src shader.Sources) (FragmentShader, error)
|
NewFragmentShader(src shader.Sources) (FragmentShader, error)
|
||||||
NewPipeline(desc PipelineDesc) (Pipeline, error)
|
NewPipeline(desc PipelineDesc) (Pipeline, error)
|
||||||
|
|
||||||
Clear(r, g, b, a float32)
|
|
||||||
Viewport(x, y, width, height int)
|
Viewport(x, y, width, height int)
|
||||||
DrawArrays(mode DrawMode, off, count int)
|
DrawArrays(mode DrawMode, off, count int)
|
||||||
DrawElements(mode DrawMode, off, count int)
|
DrawElements(mode DrawMode, off, count int)
|
||||||
|
|
||||||
BindProgram(p Program)
|
BindProgram(p Program)
|
||||||
BindPipeline(p Pipeline)
|
BindPipeline(p Pipeline)
|
||||||
BindFramebuffer(f Framebuffer)
|
BindFramebuffer(f Framebuffer, a LoadDesc)
|
||||||
BindTexture(unit int, t Texture)
|
BindTexture(unit int, t Texture)
|
||||||
BindVertexBuffer(b Buffer, stride, offset int)
|
BindVertexBuffer(b Buffer, stride, offset int)
|
||||||
BindIndexBuffer(b Buffer)
|
BindIndexBuffer(b Buffer)
|
||||||
@@ -53,6 +52,16 @@ type Device interface {
|
|||||||
Release()
|
Release()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoadDesc struct {
|
||||||
|
Action LoadAction
|
||||||
|
ClearColor struct {
|
||||||
|
R float32
|
||||||
|
G float32
|
||||||
|
B float32
|
||||||
|
A float32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Pipeline interface {
|
type Pipeline interface {
|
||||||
Release()
|
Release()
|
||||||
}
|
}
|
||||||
@@ -89,6 +98,8 @@ type TextureFormat uint8
|
|||||||
|
|
||||||
type BufferBinding uint8
|
type BufferBinding uint8
|
||||||
|
|
||||||
|
type LoadAction uint8
|
||||||
|
|
||||||
type Features uint
|
type Features uint
|
||||||
|
|
||||||
type Caps struct {
|
type Caps struct {
|
||||||
@@ -119,7 +130,6 @@ type Buffer interface {
|
|||||||
|
|
||||||
type Framebuffer interface {
|
type Framebuffer interface {
|
||||||
RenderTarget
|
RenderTarget
|
||||||
Invalidate()
|
|
||||||
Release()
|
Release()
|
||||||
ReadPixels(src image.Rectangle, pixels []byte) error
|
ReadPixels(src image.Rectangle, pixels []byte) error
|
||||||
}
|
}
|
||||||
@@ -182,6 +192,12 @@ const (
|
|||||||
BlendFactorDstColor
|
BlendFactorDstColor
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LoadActionKeep LoadAction = iota
|
||||||
|
LoadActionClear
|
||||||
|
LoadActionInvalidate
|
||||||
|
)
|
||||||
|
|
||||||
var ErrContentLost = errors.New("buffer content lost")
|
var ErrContentLost = errors.New("buffer content lost")
|
||||||
|
|
||||||
func (f Features) Has(feats Features) bool {
|
func (f Features) Has(feats Features) bool {
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport im
|
|||||||
}
|
}
|
||||||
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO)
|
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO)
|
||||||
if b.sRGBFBO != nil && !clear {
|
if b.sRGBFBO != nil && !clear {
|
||||||
b.Clear(0, 0, 0, 0)
|
b.clearOutput(0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
return &framebuffer{backend: b, obj: renderFBO, foreign: true}
|
return &framebuffer{backend: b, obj: renderFBO, foreign: true}
|
||||||
}
|
}
|
||||||
@@ -652,7 +652,7 @@ func (b *Backend) NewFramebuffer(tex driver.Texture) (driver.Framebuffer, error)
|
|||||||
gltex := tex.(*texture)
|
gltex := tex.(*texture)
|
||||||
fb := b.funcs.CreateFramebuffer()
|
fb := b.funcs.CreateFramebuffer()
|
||||||
fbo := &framebuffer{backend: b, obj: fb}
|
fbo := &framebuffer{backend: b, obj: fb}
|
||||||
b.BindFramebuffer(fbo)
|
b.BindFramebuffer(fbo, driver.LoadDesc{})
|
||||||
if err := glErr(b.funcs); err != nil {
|
if err := glErr(b.funcs); err != nil {
|
||||||
fbo.Release()
|
fbo.Release()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -849,7 +849,7 @@ func (b *Backend) Viewport(x, y, width, height int) {
|
|||||||
b.glstate.setViewport(b.funcs, x, y, width, height)
|
b.glstate.setViewport(b.funcs, x, y, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) Clear(colR, colG, colB, colA float32) {
|
func (b *Backend) clearOutput(colR, colG, colB, colA float32) {
|
||||||
b.glstate.setClearColor(b.funcs, colR, colG, colB, colA)
|
b.glstate.setClearColor(b.funcs, colR, colG, colB, colA)
|
||||||
b.funcs.Clear(gl.COLOR_BUFFER_BIT)
|
b.funcs.Clear(gl.COLOR_BUFFER_BIT)
|
||||||
}
|
}
|
||||||
@@ -1179,7 +1179,7 @@ func (b *Backend) BlitFramebuffer(dst, src driver.Framebuffer, srect, drect imag
|
|||||||
|
|
||||||
func (f *framebuffer) ReadPixels(src image.Rectangle, pixels []byte) error {
|
func (f *framebuffer) ReadPixels(src image.Rectangle, pixels []byte) error {
|
||||||
glErr(f.backend.funcs)
|
glErr(f.backend.funcs)
|
||||||
f.backend.BindFramebuffer(f)
|
f.backend.BindFramebuffer(f, driver.LoadDesc{})
|
||||||
if len(pixels) < src.Dx()*src.Dy()*4 {
|
if len(pixels) < src.Dx()*src.Dy()*4 {
|
||||||
return errors.New("unexpected RGBA size")
|
return errors.New("unexpected RGBA size")
|
||||||
}
|
}
|
||||||
@@ -1195,13 +1195,15 @@ func (b *Backend) BindPipeline(pl driver.Pipeline) {
|
|||||||
b.BlendFunc(p.blend.SrcFactor, p.blend.DstFactor)
|
b.BlendFunc(p.blend.SrcFactor, p.blend.DstFactor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) BindFramebuffer(fbo driver.Framebuffer) {
|
func (b *Backend) BindFramebuffer(fbo driver.Framebuffer, desc driver.LoadDesc) {
|
||||||
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo.(*framebuffer).obj)
|
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo.(*framebuffer).obj)
|
||||||
}
|
switch desc.Action {
|
||||||
|
case driver.LoadActionClear:
|
||||||
func (f *framebuffer) Invalidate() {
|
c := desc.ClearColor
|
||||||
f.backend.BindFramebuffer(f)
|
b.clearOutput(c.R, c.G, c.B, c.A)
|
||||||
f.backend.funcs.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
|
case driver.LoadActionInvalidate:
|
||||||
|
b.funcs.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *framebuffer) Release() {
|
func (f *framebuffer) Release() {
|
||||||
|
|||||||
-11
@@ -284,12 +284,6 @@ func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) {
|
|||||||
s.delete(ctx, len(sizes))
|
s.delete(ctx, len(sizes))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fboSet) invalidate(ctx driver.Device) {
|
|
||||||
for _, f := range s.fbos {
|
|
||||||
f.fbo.Invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fboSet) delete(ctx driver.Device, idx int) {
|
func (s *fboSet) delete(ctx driver.Device, idx int) {
|
||||||
for i := idx; i < len(s.fbos); i++ {
|
for i := idx; i < len(s.fbos); i++ {
|
||||||
f := s.fbos[i]
|
f := s.fbos[i]
|
||||||
@@ -348,11 +342,6 @@ func (s *stenciler) beginIntersect(sizes []image.Point) {
|
|||||||
s.ctx.BindPipeline(s.ipipeline.pipeline.pipeline)
|
s.ctx.BindPipeline(s.ipipeline.pipeline.pipeline)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stenciler) invalidateFBO() {
|
|
||||||
s.intersections.invalidate(s.ctx)
|
|
||||||
s.fbos.invalidate(s.ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stenciler) cover(idx int) stencilFBO {
|
func (s *stenciler) cover(idx int) stencilFBO {
|
||||||
return s.fbos.fbos[idx]
|
return s.fbos.fbos[idx]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user