aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/gpu/gpu.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/gpu/gpu.go')
-rw-r--r--vendor/gioui.org/gpu/gpu.go1131
1 files changed, 689 insertions, 442 deletions
diff --git a/vendor/gioui.org/gpu/gpu.go b/vendor/gioui.org/gpu/gpu.go
index 321b2a8..0bd15c6 100644
--- a/vendor/gioui.org/gpu/gpu.go
+++ b/vendor/gioui.org/gpu/gpu.go
@@ -13,36 +13,57 @@ import (
"image"
"image/color"
"math"
+ "os"
"reflect"
"time"
"unsafe"
"gioui.org/f32"
- "gioui.org/gpu/backend"
+ "gioui.org/gpu/internal/driver"
+ "gioui.org/internal/byteslice"
"gioui.org/internal/f32color"
- "gioui.org/internal/opconst"
"gioui.org/internal/ops"
- gunsafe "gioui.org/internal/unsafe"
+ "gioui.org/internal/scene"
+ "gioui.org/internal/stroke"
"gioui.org/layout"
"gioui.org/op"
- "gioui.org/op/paint"
+ "gioui.org/shader"
+ "gioui.org/shader/gio"
+
+ // Register backends.
+ _ "gioui.org/gpu/internal/d3d11"
+ _ "gioui.org/gpu/internal/metal"
+ _ "gioui.org/gpu/internal/opengl"
+ _ "gioui.org/gpu/internal/vulkan"
)
-type GPU struct {
+type GPU interface {
+ // Release non-Go resources. The GPU is no longer valid after Release.
+ Release()
+ // Clear sets the clear color for the next Frame.
+ Clear(color color.NRGBA)
+ // Frame draws the graphics operations from op into a viewport of target.
+ Frame(frame *op.Ops, target RenderTarget, viewport image.Point) error
+ // Profile returns the last available profiling information. Profiling
+ // information is requested when Frame sees an io/profile.Op, and the result
+ // is available through Profile at some later time.
+ Profile() string
+}
+
+type gpu struct {
cache *resourceCache
- defFBO backend.Framebuffer
- profile string
- timers *timers
- frameStart time.Time
- zopsTimer, stencilTimer, coverTimer, cleanupTimer *timer
- drawOps drawOps
- ctx backend.Device
- renderer *renderer
+ profile string
+ timers *timers
+ frameStart time.Time
+ stencilTimer, coverTimer, cleanupTimer *timer
+ drawOps drawOps
+ ctx driver.Device
+ renderer *renderer
}
type renderer struct {
- ctx backend.Device
+ ctx driver.Device
blitter *blitter
pather *pather
packer packer
@@ -50,17 +71,15 @@ type renderer struct {
}
type drawOps struct {
- profile bool
- reader ops.Reader
- cache *resourceCache
- vertCache []byte
- viewport image.Point
- clearColor f32color.RGBA
- imageOps []imageOp
- // zimageOps are the rectangle clipped opaque images
- // that can use fast front-to-back rendering with z-test
- // and no blending.
- zimageOps []imageOp
+ profile bool
+ reader ops.Reader
+ states []f32.Affine2D
+ transStack []f32.Affine2D
+ vertCache []byte
+ viewport image.Point
+ clear bool
+ clearColor f32color.RGBA
+ imageOps []imageOp
pathOps []*pathOp
pathOpCache []pathOp
qs quadSplitter
@@ -68,26 +87,35 @@ type drawOps struct {
}
type drawState struct {
- clip f32.Rectangle
t f32.Affine2D
cpath *pathOp
- rect bool
- z int
matType materialType
// Current paint.ImageOp
image imageOpData
// Current paint.ColorOp, if any.
- color color.RGBA
+ color color.NRGBA
+
+ // Current paint.LinearGradientOp.
+ stop1 f32.Point
+ stop2 f32.Point
+ color1 color.NRGBA
+ color2 color.NRGBA
}
type pathOp struct {
off f32.Point
+ // rect tracks whether the clip stack can be represented by a
+ // pixel-aligned rectangle.
+ rect bool
// clip is the union of all
// later clip rectangles.
- clip image.Rectangle
- bounds f32.Rectangle
- pathKey ops.Key
+ clip image.Rectangle
+ bounds f32.Rectangle
+ // intersect is the intersection of bounds and all
+ // previous clip bounds.
+ intersect f32.Rectangle
+ pathKey opKey
path bool
pathVerts []byte
parent *pathOp
@@ -95,87 +123,70 @@ type pathOp struct {
}
type imageOp struct {
- z float32
path *pathOp
- off f32.Point
clip image.Rectangle
material material
clipType clipType
place placement
}
+func decodeStrokeOp(data []byte) float32 {
+ _ = data[4]
+ bo := binary.LittleEndian
+ return math.Float32frombits(bo.Uint32(data[1:]))
+}
+
+type quadsOp struct {
+ key opKey
+ aux []byte
+}
+
+type opKey struct {
+ outline bool
+ strokeWidth float32
+ sx, hx, sy, hy float32
+ ops.Key
+}
+
type material struct {
material materialType
opaque bool
// For materialTypeColor.
color f32color.RGBA
+ // For materialTypeLinearGradient.
+ color1 f32color.RGBA
+ color2 f32color.RGBA
// For materialTypeTexture.
- texture *texture
+ data imageOpData
uvTrans f32.Affine2D
}
-// clipOp is the shadow of clip.Op.
-type clipOp struct {
- bounds f32.Rectangle
-}
-
// imageOpData is the shadow of paint.ImageOp.
type imageOpData struct {
- rect image.Rectangle
src *image.RGBA
handle interface{}
}
-func (op *clipOp) decode(data []byte) {
- if opconst.OpType(data[0]) != opconst.TypeClip {
- panic("invalid op")
- }
- bo := binary.LittleEndian
- r := f32.Rectangle{
- Min: f32.Point{
- X: math.Float32frombits(bo.Uint32(data[1:])),
- Y: math.Float32frombits(bo.Uint32(data[5:])),
- },
- Max: f32.Point{
- X: math.Float32frombits(bo.Uint32(data[9:])),
- Y: math.Float32frombits(bo.Uint32(data[13:])),
- },
- }
- *op = clipOp{
- bounds: r,
- }
+type linearGradientOpData struct {
+ stop1 f32.Point
+ color1 color.NRGBA
+ stop2 f32.Point
+ color2 color.NRGBA
}
func decodeImageOp(data []byte, refs []interface{}) imageOpData {
- if opconst.OpType(data[0]) != opconst.TypeImage {
- panic("invalid op")
- }
handle := refs[1]
if handle == nil {
return imageOpData{}
}
- bo := binary.LittleEndian
return imageOpData{
- rect: image.Rectangle{
- Min: image.Point{
- X: int(bo.Uint32(data[1:])),
- Y: int(bo.Uint32(data[5:])),
- },
- Max: image.Point{
- X: int(bo.Uint32(data[9:])),
- Y: int(bo.Uint32(data[13:])),
- },
- },
src: refs[0].(*image.RGBA),
handle: handle,
}
}
-func decodeColorOp(data []byte) color.RGBA {
- if opconst.OpType(data[0]) != opconst.TypeColor {
- panic("invalid op")
- }
- return color.RGBA{
+func decodeColorOp(data []byte) color.NRGBA {
+ return color.NRGBA{
R: data[1],
G: data[2],
B: data[3],
@@ -183,23 +194,29 @@ func decodeColorOp(data []byte) color.RGBA {
}
}
-func decodePaintOp(data []byte) paint.PaintOp {
+func decodeLinearGradientOp(data []byte) linearGradientOpData {
bo := binary.LittleEndian
- if opconst.OpType(data[0]) != opconst.TypePaint {
- panic("invalid op")
- }
- r := f32.Rectangle{
- Min: f32.Point{
+ return linearGradientOpData{
+ stop1: f32.Point{
X: math.Float32frombits(bo.Uint32(data[1:])),
Y: math.Float32frombits(bo.Uint32(data[5:])),
},
- Max: f32.Point{
+ stop2: f32.Point{
X: math.Float32frombits(bo.Uint32(data[9:])),
Y: math.Float32frombits(bo.Uint32(data[13:])),
},
- }
- return paint.PaintOp{
- Rect: r,
+ color1: color.NRGBA{
+ R: data[17+0],
+ G: data[17+1],
+ B: data[17+2],
+ A: data[17+3],
+ },
+ color2: color.NRGBA{
+ R: data[21+0],
+ G: data[21+1],
+ B: data[21+2],
+ A: data[21+3],
+ },
}
}
@@ -211,58 +228,60 @@ type resource interface {
type texture struct {
src *image.RGBA
- tex backend.Texture
+ tex driver.Texture
}
type blitter struct {
- ctx backend.Device
- viewport image.Point
- prog [2]*program
- layout backend.InputLayout
- colUniforms *blitColUniforms
- texUniforms *blitTexUniforms
- quadVerts backend.Buffer
+ ctx driver.Device
+ viewport image.Point
+ pipelines [3]*pipeline
+ colUniforms *blitColUniforms
+ texUniforms *blitTexUniforms
+ linearGradientUniforms *blitLinearGradientUniforms
+ quadVerts driver.Buffer
}
type blitColUniforms struct {
- vert struct {
- blitUniforms
- _ [12]byte // Padding to a multiple of 16.
- }
- frag struct {
- colorUniforms
- }
+ blitUniforms
+ _ [128 - unsafe.Sizeof(blitUniforms{}) - unsafe.Sizeof(colorUniforms{})]byte // Padding to 128 bytes.
+ colorUniforms
}
type blitTexUniforms struct {
- vert struct {
- blitUniforms
- _ [12]byte // Padding to a multiple of 16.
- }
+ blitUniforms
+}
+
+type blitLinearGradientUniforms struct {
+ blitUniforms
+ _ [128 - unsafe.Sizeof(blitUniforms{}) - unsafe.Sizeof(gradientUniforms{})]byte // Padding to 128 bytes.
+ gradientUniforms
}
type uniformBuffer struct {
- buf backend.Buffer
+ buf driver.Buffer
ptr []byte
}
-type program struct {
- prog backend.Program
- vertUniforms *uniformBuffer
- fragUniforms *uniformBuffer
+type pipeline struct {
+ pipeline driver.Pipeline
+ uniforms *uniformBuffer
}
type blitUniforms struct {
transform [4]float32
uvTransformR1 [4]float32
uvTransformR2 [4]float32
- z float32
}
type colorUniforms struct {
color f32color.RGBA
}
+type gradientUniforms struct {
+ color1 f32color.RGBA
+ color2 f32color.RGBA
+}
+
type materialType uint8
const (
@@ -273,14 +292,37 @@ const (
const (
materialColor materialType = iota
+ materialLinearGradient
materialTexture
)
-func New(ctx backend.Device) (*GPU, error) {
- defFBO := ctx.CurrentFramebuffer()
- g := &GPU{
- defFBO: defFBO,
- cache: newResourceCache(),
+// New creates a GPU for the given API.
+func New(api API) (GPU, error) {
+ d, err := driver.NewDevice(api)
+ if err != nil {
+ return nil, err
+ }
+ return NewWithDevice(d)
+}
+
+// NewWithDevice creates a GPU with a pre-existing device.
+//
+// Note: for internal use only.
+func NewWithDevice(d driver.Device) (GPU, error) {
+ d.BeginFrame(nil, false, image.Point{})
+ defer d.EndFrame()
+ forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
+ feats := d.Caps().Features
+ switch {
+ case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
+ return newGPU(d)
+ }
+ return newCompute(d)
+}
+
+func newGPU(ctx driver.Device) (*gpu, error) {
+ g := &gpu{
+ cache: newResourceCache(),
}
g.drawOps.pathCache = newOpCache()
if err := g.init(ctx); err != nil {
@@ -289,111 +331,116 @@ func New(ctx backend.Device) (*GPU, error) {
return g, nil
}
-func (g *GPU) init(ctx backend.Device) error {
+func (g *gpu) init(ctx driver.Device) error {
g.ctx = ctx
g.renderer = newRenderer(ctx)
return nil
}
-func (g *GPU) Release() {
+func (g *gpu) Clear(col color.NRGBA) {
+ g.drawOps.clear = true
+ g.drawOps.clearColor = f32color.LinearFromSRGB(col)
+}
+
+func (g *gpu) Release() {
g.renderer.release()
g.drawOps.pathCache.release()
g.cache.release()
if g.timers != nil {
- g.timers.release()
+ g.timers.Release()
}
+ g.ctx.Release()
+}
+
+func (g *gpu) Frame(frameOps *op.Ops, target RenderTarget, viewport image.Point) error {
+ g.collect(viewport, frameOps)
+ return g.frame(target)
}
-func (g *GPU) Collect(viewport image.Point, frameOps *op.Ops) {
+func (g *gpu) collect(viewport image.Point, frameOps *op.Ops) {
g.renderer.blitter.viewport = viewport
g.renderer.pather.viewport = viewport
- g.drawOps.reset(g.cache, viewport)
- g.drawOps.collect(g.cache, frameOps, viewport)
+ g.drawOps.reset(viewport)
+ g.drawOps.collect(frameOps, viewport)
g.frameStart = time.Now()
- if g.drawOps.profile && g.timers == nil && g.ctx.Caps().Features.Has(backend.FeatureTimers) {
+ if g.drawOps.profile && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
g.timers = newTimers(g.ctx)
- g.zopsTimer = g.timers.newTimer()
g.stencilTimer = g.timers.newTimer()
g.coverTimer = g.timers.newTimer()
g.cleanupTimer = g.timers.newTimer()
}
- for _, p := range g.drawOps.pathOps {
- if v, exists := g.drawOps.pathCache.get(p.pathKey); !exists || v.data.data == nil {
- data := buildPath(g.ctx, p.pathVerts)
- g.drawOps.pathCache.put(p.pathKey, opCacheValue{
- data: data,
- bounds: p.bounds,
- })
- }
- p.pathVerts = nil
- }
}
-func (g *GPU) BeginFrame() {
- g.ctx.BeginFrame()
- defer g.ctx.EndFrame()
+func (g *gpu) frame(target RenderTarget) error {
viewport := g.renderer.blitter.viewport
+ defFBO := g.ctx.BeginFrame(target, g.drawOps.clear, viewport)
+ defer g.ctx.EndFrame()
+ g.drawOps.buildPaths(g.ctx)
for _, img := range g.drawOps.imageOps {
expandPathOp(img.path, img.clip)
}
- if g.drawOps.profile {
- g.zopsTimer.begin()
- }
- g.ctx.BindFramebuffer(g.defFBO)
- g.ctx.DepthFunc(backend.DepthFuncGreater)
- g.ctx.ClearDepth(0.0)
- g.ctx.Clear(g.drawOps.clearColor.Float32())
- g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
- g.renderer.drawZOps(g.drawOps.zimageOps)
- g.zopsTimer.end()
g.stencilTimer.begin()
- g.ctx.SetBlend(true)
g.renderer.packStencils(&g.drawOps.pathOps)
g.renderer.stencilClips(g.drawOps.pathCache, g.drawOps.pathOps)
g.renderer.packIntersections(g.drawOps.imageOps)
+ g.renderer.prepareIntersections(g.drawOps.imageOps)
g.renderer.intersect(g.drawOps.imageOps)
g.stencilTimer.end()
g.coverTimer.begin()
- g.ctx.BindFramebuffer(g.defFBO)
+ g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
+ g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps)
+ d := driver.LoadDesc{
+ ClearColor: g.drawOps.clearColor,
+ }
+ if g.drawOps.clear {
+ g.drawOps.clear = false
+ d.Action = driver.LoadActionClear
+ }
+ g.ctx.BeginRenderPass(defFBO, d)
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
- g.renderer.drawOps(g.drawOps.imageOps)
- g.ctx.SetBlend(false)
- g.renderer.pather.stenciler.invalidateFBO()
+ g.renderer.drawOps(g.cache, g.drawOps.imageOps)
g.coverTimer.end()
- g.ctx.BindFramebuffer(g.defFBO)
-}
-
-func (g *GPU) EndFrame() {
+ g.ctx.EndRenderPass()
g.cleanupTimer.begin()
g.cache.frame()
g.drawOps.pathCache.frame()
g.cleanupTimer.end()
if g.drawOps.profile && g.timers.ready() {
- zt, st, covt, cleant := g.zopsTimer.Elapsed, g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
- ft := zt + st + covt + cleant
+ st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
+ ft := st + covt + cleant
q := 100 * time.Microsecond
- zt, st, covt = zt.Round(q), st.Round(q), covt.Round(q)
+ st, covt = st.Round(q), covt.Round(q)
frameDur := time.Since(g.frameStart).Round(q)
ft = ft.Round(q)
- g.profile = fmt.Sprintf("draw:%7s gpu:%7s zt:%7s st:%7s cov:%7s", frameDur, ft, zt, st, covt)
+ g.profile = fmt.Sprintf("draw:%7s gpu:%7s st:%7s cov:%7s", frameDur, ft, st, covt)
}
+ return nil
}
-func (g *GPU) Profile() string {
+func (g *gpu) Profile() string {
return g.profile
}
-func (r *renderer) texHandle(t *texture) backend.Texture {
- if t.tex != nil {
- return t.tex
+func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
+ var tex *texture
+ t, exists := cache.get(data.handle)
+ if !exists {
+ t = &texture{
+ src: data.src,
+ }
+ cache.put(data.handle, t)
}
- tex, err := r.ctx.NewTexture(backend.TextureFormatSRGB, t.src.Bounds().Dx(), t.src.Bounds().Dy(), backend.FilterLinear, backend.FilterLinear, backend.BufferBindingTexture)
+ tex = t.(*texture)
+ if tex.tex != nil {
+ return tex.tex
+ }
+ handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA, data.src.Bounds().Dx(), data.src.Bounds().Dy(), driver.FilterLinear, driver.FilterLinear, driver.BufferBindingTexture)
if err != nil {
panic(err)
}
- tex.Upload(t.src)
- t.tex = tex
- return t.tex
+ driver.UploadImage(handle, image.Pt(0, 0), data.src)
+ tex.tex = handle
+ return tex.tex
}
func (t *texture) release() {
@@ -402,14 +449,22 @@ func (t *texture) release() {
}
}
-func newRenderer(ctx backend.Device) *renderer {
+func newRenderer(ctx driver.Device) *renderer {
r := &renderer{
ctx: ctx,
blitter: newBlitter(ctx),
pather: newPather(ctx),
}
- r.packer.maxDim = ctx.Caps().MaxTextureSize
- r.intersections.maxDim = r.packer.maxDim
+
+ maxDim := ctx.Caps().MaxTextureSize
+ // Large atlas textures cause artifacts due to precision loss in
+ // shaders.
+ if cap := 8192; maxDim > cap {
+ maxDim = cap
+ }
+
+ r.packer.maxDims = image.Pt(maxDim, maxDim)
+ r.intersections.maxDims = image.Pt(maxDim, maxDim)
return r
}
@@ -418,13 +473,13 @@ func (r *renderer) release() {
r.blitter.release()
}
-func newBlitter(ctx backend.Device) *blitter {
- quadVerts, err := ctx.NewImmutableBuffer(backend.BufferBindingVertices,
- gunsafe.BytesView([]float32{
- -1, +1, 0, 0,
- +1, +1, 1, 0,
- -1, -1, 0, 1,
- +1, -1, 1, 1,
+func newBlitter(ctx driver.Device) *blitter {
+ quadVerts, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices,
+ byteslice.Slice([]float32{
+ -1, -1, 0, 0,
+ +1, -1, 1, 0,
+ -1, +1, 0, 1,
+ +1, +1, 1, 1,
}),
)
if err != nil {
@@ -436,65 +491,125 @@ func newBlitter(ctx backend.Device) *blitter {
}
b.colUniforms = new(blitColUniforms)
b.texUniforms = new(blitTexUniforms)
- prog, layout, err := createColorPrograms(ctx, shader_blit_vert, shader_blit_frag,
- [2]interface{}{&b.colUniforms.vert, &b.texUniforms.vert}, [2]interface{}{&b.colUniforms.frag, nil})
+ b.linearGradientUniforms = new(blitLinearGradientUniforms)
+ pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
+ [3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
+ )
if err != nil {
panic(err)
}
- b.prog = prog
- b.layout = layout
+ b.pipelines = pipelines
return b
}
func (b *blitter) release() {
b.quadVerts.Release()
- for _, p := range b.prog {
+ for _, p := range b.pipelines {
p.Release()
}
- b.layout.Release()
}
-func createColorPrograms(b backend.Device, vsSrc backend.ShaderSources, fsSrc [2]backend.ShaderSources, vertUniforms, fragUniforms [2]interface{}) ([2]*program, backend.InputLayout, error) {
- var progs [2]*program
- prog, err := b.NewProgram(vsSrc, fsSrc[materialTexture])
- if err != nil {
- return progs, nil, err
+func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) ([3]*pipeline, error) {
+ var pipelines [3]*pipeline
+ blend := driver.BlendDesc{
+ Enable: true,
+ SrcFactor: driver.BlendFactorOne,
+ DstFactor: driver.BlendFactorOneMinusSrcAlpha,
}
- var vertBuffer *uniformBuffer
- if u := vertUniforms[materialTexture]; u != nil {
- vertBuffer = newUniformBuffer(b, u)
- prog.SetVertexUniforms(vertBuffer.buf)
- }
- var fragBuffer *uniformBuffer
- if u := fragUniforms[materialTexture]; u != nil {
- fragBuffer = newUniformBuffer(b, u)
- prog.SetFragmentUniforms(fragBuffer.buf)
+ layout := driver.VertexLayout{
+ Inputs: []driver.InputDesc{
+ {Type: shader.DataTypeFloat, Size: 2, Offset: 0},
+ {Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
+ },
+ Stride: 4 * 4,
}
- progs[materialTexture] = newProgram(prog, vertBuffer, fragBuffer)
- prog, err = b.NewProgram(vsSrc, fsSrc[materialColor])
+ vsh, err := b.NewVertexShader(vsSrc)
if err != nil {
- progs[materialTexture].Release()
- return progs, nil, err
- }
- if u := vertUniforms[materialColor]; u != nil {
- vertBuffer = newUniformBuffer(b, u)
- prog.SetVertexUniforms(vertBuffer.buf)
- }
- if u := fragUniforms[materialColor]; u != nil {
- fragBuffer = newUniformBuffer(b, u)
- prog.SetFragmentUniforms(fragBuffer.buf)
- }
- progs[materialColor] = newProgram(prog, vertBuffer, fragBuffer)
- layout, err := b.NewInputLayout(vsSrc, []backend.InputDesc{
- {Type: backend.DataTypeFloat, Size: 2, Offset: 0},
- {Type: backend.DataTypeFloat, Size: 2, Offset: 4 * 2},
- })
+ return pipelines, err
+ }
+ defer vsh.Release()
+ {
+ fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
+ if err != nil {
+ return pipelines, err
+ }
+ defer fsh.Release()
+ pipe, err := b.NewPipeline(driver.PipelineDesc{
+ VertexShader: vsh,
+ FragmentShader: fsh,
+ BlendDesc: blend,
+ VertexLayout: layout,
+ PixelFormat: driver.TextureFormatOutput,
+ Topology: driver.TopologyTriangleStrip,
+ })
+ if err != nil {
+ return pipelines, err
+ }
+ var vertBuffer *uniformBuffer
+ if u := uniforms[materialTexture]; u != nil {
+ vertBuffer = newUniformBuffer(b, u)
+ }
+ pipelines[materialTexture] = &pipeline{pipe, vertBuffer}
+ }
+ {
+ var vertBuffer *uniformBuffer
+ fsh, err := b.NewFragmentShader(fsSrc[materialColor])
+ if err != nil {
+ pipelines[materialTexture].Release()
+ return pipelines, err
+ }
+ defer fsh.Release()
+ pipe, err := b.NewPipeline(driver.PipelineDesc{
+ VertexShader: vsh,
+ FragmentShader: fsh,
+ BlendDesc: blend,
+ VertexLayout: layout,
+ PixelFormat: driver.TextureFormatOutput,
+ Topology: driver.TopologyTriangleStrip,
+ })
+ if err != nil {
+ pipelines[materialTexture].Release()
+ return pipelines, err
+ }
+ if u := uniforms[materialColor]; u != nil {
+ vertBuffer = newUniformBuffer(b, u)
+ }
+ pipelines[materialColor] = &pipeline{pipe, vertBuffer}
+ }
+ {
+ var vertBuffer *uniformBuffer
+ fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
+ if err != nil {
+ pipelines[materialTexture].Release()
+ pipelines[materialColor].Release()
+ return pipelines, err
+ }
+ defer fsh.Release()
+ pipe, err := b.NewPipeline(driver.PipelineDesc{
+ VertexShader: vsh,
+ FragmentShader: fsh,
+ BlendDesc: blend,
+ VertexLayout: layout,
+ PixelFormat: driver.TextureFormatOutput,
+ Topology: driver.TopologyTriangleStrip,
+ })
+ if err != nil {
+ pipelines[materialTexture].Release()
+ pipelines[materialColor].Release()
+ return pipelines, err
+ }
+ if u := uniforms[materialLinearGradient]; u != nil {
+ vertBuffer = newUniformBuffer(b, u)
+ }
+ pipelines[materialLinearGradient] = &pipeline{pipe, vertBuffer}
+ }
if err != nil {
- progs[materialTexture].Release()
- progs[materialColor].Release()
- return progs, nil, err
+ for _, p := range pipelines {
+ p.Release()
+ }
+ return pipelines, err
}
- return progs, layout, nil
+ return pipelines, nil
}
func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
@@ -505,14 +620,31 @@ func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
r.pather.begin(r.packer.sizes)
for _, p := range ops {
if fbo != p.place.Idx {
+ if fbo != -1 {
+ r.ctx.EndRenderPass()
+ }
fbo = p.place.Idx
f := r.pather.stenciler.cover(fbo)
- r.ctx.BindFramebuffer(f.fbo)
- r.ctx.Clear(0.0, 0.0, 0.0, 0.0)
+ 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)
}
v, _ := pathCache.get(p.pathKey)
r.pather.stencilPath(p.clip, p.off, p.place.Pos, v.data)
}
+ if fbo != -1 {
+ r.ctx.EndRenderPass()
+ }
+}
+
+func (r *renderer) prepareIntersections(ops []imageOp) {
+ for _, img := range ops {
+ if img.clipType != clipTypeIntersection {
+ continue
+ }
+ fbo := r.pather.stenciler.cover(img.path.place.Idx)
+ r.ctx.PrepareTexture(fbo.tex)
+ }
}
func (r *renderer) intersect(ops []imageOp) {
@@ -521,21 +653,28 @@ func (r *renderer) intersect(ops []imageOp) {
}
fbo := -1
r.pather.stenciler.beginIntersect(r.intersections.sizes)
- r.ctx.BindVertexBuffer(r.blitter.quadVerts, 4*4, 0)
- r.ctx.BindInputLayout(r.pather.stenciler.iprog.layout)
for _, img := range ops {
if img.clipType != clipTypeIntersection {
continue
}
if fbo != img.place.Idx {
+ if fbo != -1 {
+ r.ctx.EndRenderPass()
+ }
fbo = img.place.Idx
f := r.pather.stenciler.intersections.fbos[fbo]
- r.ctx.BindFramebuffer(f.fbo)
- r.ctx.Clear(1.0, 0.0, 0.0, 0.0)
+ d := driver.LoadDesc{Action: driver.LoadActionClear}
+ d.ClearColor.R = 1.0
+ r.ctx.BeginRenderPass(f.tex, d)
+ r.ctx.BindPipeline(r.pather.stenciler.ipipeline.pipeline.pipeline)
+ r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
}
r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy())
r.intersectPath(img.path, img.clip)
}
+ if fbo != -1 {
+ r.ctx.EndRenderPass()
+ }
}
func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) {
@@ -556,12 +695,12 @@ func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) {
}
fbo := r.pather.stenciler.cover(p.place.Idx)
r.ctx.BindTexture(0, fbo.tex)
- coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size)
- subScale, subOff := texSpaceTransform(toRectF(sub), p.clip.Size())
- r.pather.stenciler.iprog.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
- r.pather.stenciler.iprog.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y}
- r.pather.stenciler.iprog.prog.UploadUniforms()
- r.ctx.DrawArrays(backend.DrawModeTriangleStrip, 0, 4)
+ coverScale, coverOff := texSpaceTransform(layout.FRect(uv), fbo.size)
+ subScale, subOff := texSpaceTransform(layout.FRect(sub), p.clip.Size())
+ r.pather.stenciler.ipipeline.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
+ r.pather.stenciler.ipipeline.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y}
+ r.pather.stenciler.ipipeline.pipeline.UploadUniforms(r.ctx)
+ r.ctx.DrawArrays(0, 4)
}
func (r *renderer) packIntersections(ops []imageOp) {
@@ -611,7 +750,7 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
if !ok {
// The clip area is at most the entire screen. Hopefully no
// screen is larger than GL_MAX_TEXTURE_SIZE.
- panic(fmt.Errorf("clip area %v is larger than maximum texture size %dx%d", p.clip, r.packer.maxDim, r.packer.maxDim))
+ panic(fmt.Errorf("clip area %v is larger than maximum texture size %v", p.clip, r.packer.maxDims))
}
p.place = place
i++
@@ -619,8 +758,7 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
*pops = ops
}
-// intersects intersects clip and b where b is offset by off.
-// ceilRect returns a bounding image.Rectangle for a f32.Rectangle.
+// boundRectF returns a bounding image.Rectangle for a f32.Rectangle.
func boundRectF(r f32.Rectangle) image.Rectangle {
return image.Rectangle{
Min: image.Point{
@@ -634,19 +772,6 @@ func boundRectF(r f32.Rectangle) image.Rectangle {
}
}
-func toRectF(r image.Rectangle) f32.Rectangle {
- return f32.Rectangle{
- Min: f32.Point{
- X: float32(r.Min.X),
- Y: float32(r.Min.Y),
- },
- Max: f32.Point{
- X: float32(r.Max.X),
- Y: float32(r.Max.Y),
- },
- }
-}
-
func ceil(v float32) int {
return int(math.Ceil(float64(v)))
}
@@ -655,30 +780,39 @@ func floor(v float32) int {
return int(math.Floor(float64(v)))
}
-func (d *drawOps) reset(cache *resourceCache, viewport image.Point) {
+func (d *drawOps) reset(viewport image.Point) {
d.profile = false
- d.clearColor = f32color.RGBA{R: 1.0, G: 1.0, B: 1.0, A: 1.0}
- d.cache = cache
d.viewport = viewport
d.imageOps = d.imageOps[:0]
- d.zimageOps = d.zimageOps[:0]
d.pathOps = d.pathOps[:0]
d.pathOpCache = d.pathOpCache[:0]
d.vertCache = d.vertCache[:0]
+ d.transStack = d.transStack[:0]
}
-func (d *drawOps) collect(cache *resourceCache, root *op.Ops, viewport image.Point) {
- d.reset(cache, viewport)
- clip := f32.Rectangle{
+func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
+ viewf := f32.Rectangle{
Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
}
- d.reader.Reset(root)
- state := drawState{
- clip: clip,
- rect: true,
- color: color.RGBA{A: 0xff},
+ var ops *ops.Ops
+ if root != nil {
+ ops = &root.Internal
+ }
+ d.reader.Reset(ops)
+ d.collectOps(&d.reader, viewf)
+}
+
+func (d *drawOps) buildPaths(ctx driver.Device) {
+ for _, p := range d.pathOps {
+ if v, exists := d.pathCache.get(p.pathKey); !exists || v.data.data == nil {
+ data := buildPath(ctx, p.pathVerts)
+ d.pathCache.put(p.pathKey, opCacheValue{
+ data: data,
+ bounds: p.bounds,
+ })
+ }
+ p.pathVerts = nil
}
- d.collectOps(&d.reader, state)
}
func (d *drawOps) newPathOp() *pathOp {
@@ -686,24 +820,30 @@ func (d *drawOps) newPathOp() *pathOp {
return &d.pathOpCache[len(d.pathOpCache)-1]
}
-func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey ops.Key, bounds f32.Rectangle, off f32.Point) {
+func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) {
npath := d.newPathOp()
*npath = pathOp{
- parent: state.cpath,
- bounds: bounds,
- off: off,
+ parent: state.cpath,
+ bounds: bounds,
+ off: off,
+ intersect: bounds.Add(off),
+ rect: true,
+ }
+ if npath.parent != nil {
+ npath.rect = npath.parent.rect
+ npath.intersect = npath.parent.intersect.Intersect(npath.intersect)
}
- state.cpath = npath
if len(aux) > 0 {
- state.rect = false
- state.cpath.pathKey = auxKey
- state.cpath.path = true
- state.cpath.pathVerts = aux
- d.pathOps = append(d.pathOps, state.cpath)
+ npath.rect = false
+ npath.pathKey = auxKey
+ npath.path = true
+ npath.pathVerts = aux
+ d.pathOps = append(d.pathOps, npath)
}
+ state.cpath = npath
}
-// split a transform into two parts, one which is pur offset and the
+// split a transform into two parts, one which is pure offset and the
// other representing the scaling, shearing and rotation part
func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) {
sx, hx, ox, hy, sy, oy := t.Elems()
@@ -712,121 +852,169 @@ func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) {
return
}
-func (d *drawOps) collectOps(r *ops.Reader, state drawState) int {
- var aux []byte
- var auxKey ops.Key
+func (d *drawOps) save(id int, state f32.Affine2D) {
+ if extra := id - len(d.states) + 1; extra > 0 {
+ d.states = append(d.states, make([]f32.Affine2D, extra)...)
+ }
+ d.states[id] = state
+}
+
+func (k opKey) SetTransform(t f32.Affine2D) opKey {
+ sx, hx, _, hy, sy, _ := t.Elems()
+ k.sx = sx
+ k.hx = hx
+ k.hy = hy
+ k.sy = sy
+ return k
+}
+
+func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
+ var (
+ quads quadsOp
+ state drawState
+ )
+ reset := func() {
+ state = drawState{
+ color: color.NRGBA{A: 0xff},
+ }
+ }
+ reset()
loop:
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
- switch opconst.OpType(encOp.Data[0]) {
- case opconst.TypeProfile:
+ switch ops.OpType(encOp.Data[0]) {
+ case ops.TypeProfile:
d.profile = true
- case opconst.TypeTransform:
- dop := ops.DecodeTransform(encOp.Data)
+ case ops.TypeTransform:
+ dop, push := ops.DecodeTransform(encOp.Data)
+ if push {
+ d.transStack = append(d.transStack, state.t)
+ }
state.t = state.t.Mul(dop)
- case opconst.TypeAux:
- aux = encOp.Data[opconst.TypeAuxLen:]
- auxKey = encOp.Key
- case opconst.TypeClip:
- var op clipOp
- op.decode(encOp.Data)
- bounds := op.bounds
+ case ops.TypePopTransform:
+ n := len(d.transStack)
+ state.t = d.transStack[n-1]
+ d.transStack = d.transStack[:n-1]
+
+ case ops.TypeStroke:
+ quads.key.strokeWidth = decodeStrokeOp(encOp.Data)
+
+ case ops.TypePath:
+ encOp, ok = r.Decode()
+ if !ok {
+ break loop
+ }
+ quads.aux = encOp.Data[ops.TypeAuxLen:]
+ quads.key.Key = encOp.Key
+
+ case ops.TypeClip:
+ var op ops.ClipOp
+ op.Decode(encOp.Data)
+ quads.key.outline = op.Outline
+ bounds := layout.FRect(op.Bounds)
trans, off := splitTransform(state.t)
- if len(aux) > 0 {
+ if len(quads.aux) > 0 {
// There is a clipping path, build the gpu data and update the
// cache key such that it will be equal only if the transform is the
// same also. Use cached data if we have it.
- auxKey = auxKey.SetTransform(trans)
- if v, ok := d.pathCache.get(auxKey); ok {
+ quads.key = quads.key.SetTransform(trans)
+ if v, ok := d.pathCache.get(quads.key); ok {
// Since the GPU data exists in the cache aux will not be used.
// Why is this not used for the offset shapes?
- op.bounds = v.bounds
+ bounds = v.bounds
} else {
- aux, op.bounds = d.buildVerts(aux, trans)
+ var pathData []byte
+ pathData, bounds = d.buildVerts(
+ quads.aux, trans, quads.key.outline, quads.key.strokeWidth,
+ )
+ quads.aux = pathData
// add it to the cache, without GPU data, so the transform can be
// reused.
- d.pathCache.put(auxKey, opCacheValue{bounds: op.bounds})
+ d.pathCache.put(quads.key, opCacheValue{bounds: bounds})
}
} else {
- aux, op.bounds, _ = d.boundsForTransformedRect(bounds, trans)
- auxKey = encOp.Key
- auxKey.SetTransform(trans)
+ quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
+ quads.key = opKey{Key: encOp.Key}
}
- state.clip = state.clip.Intersect(op.bounds.Add(off))
- d.addClipPath(&state, aux, auxKey, op.bounds, off)
- aux = nil
- auxKey = ops.Key{}
- case opconst.TypeColor:
+ d.addClipPath(&state, quads.aux, quads.key, bounds, off, true)
+ quads = quadsOp{}
+ case ops.TypePopClip:
+ state.cpath = state.cpath.parent
+
+ case ops.TypeColor:
state.matType = materialColor
state.color = decodeColorOp(encOp.Data)
- case opconst.TypeImage:
+ case ops.TypeLinearGradient:
+ state.matType = materialLinearGradient
+ op := decodeLinearGradientOp(encOp.Data)
+ state.stop1 = op.stop1
+ state.stop2 = op.stop2
+ state.color1 = op.color1
+ state.color2 = op.color2
+ case ops.TypeImage:
state.matType = materialTexture
state.image = decodeImageOp(encOp.Data, encOp.Refs)
- case opconst.TypePaint:
- op := decodePaintOp(encOp.Data)
+ case ops.TypePaint:
// Transform (if needed) the painting rectangle and if so generate a clip path,
// for those cases also compute a partialTrans that maps texture coordinates between
// the new bounding rectangle and the transformed original paint rectangle.
- trans, off := splitTransform(state.t)
- clipData, bnd, partialTrans := d.boundsForTransformedRect(op.Rect, trans)
- clip := state.clip.Intersect(bnd.Add(off))
- if clip.Empty() {
+ t, off := splitTransform(state.t)
+ // Fill the clip area, unless the material is a (bounded) image.
+ // TODO: Find a tighter bound.
+ inf := float32(1e6)
+ dst := f32.Rect(-inf, -inf, inf, inf)
+ if state.matType == materialTexture {
+ sz := state.image.src.Rect.Size()
+ dst = f32.Rectangle{Max: layout.FPt(sz)}
+ }
+ clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
+ cl := viewport.Intersect(bnd.Add(off))
+ if state.cpath != nil {
+ cl = state.cpath.intersect.Intersect(cl)
+ }
+ if cl.Empty() {
continue
}
- wasrect := state.rect
if clipData != nil {
// The paint operation is sheared or rotated, add a clip path representing
// this transformed rectangle.
- encOp.Key.SetTransform(trans)
- d.addClipPath(&state, clipData, encOp.Key, bnd, off)
+ k := opKey{Key: encOp.Key}
+ k.SetTransform(t) // TODO: This call has no effect.
+ d.addClipPath(&state, clipData, k, bnd, off, false)
}
- bounds := boundRectF(clip)
- mat := state.materialFor(d.cache, bnd, off, partialTrans, bounds)
+ bounds := boundRectF(cl)
+ mat := state.materialFor(bnd, off, partialTrans, bounds)
- if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && mat.material == materialColor {
+ rect := state.cpath == nil || state.cpath.rect
+ if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) {
// The image is a uniform opaque color and takes up the whole screen.
// Scrap images up to and including this image and set clear color.
- d.zimageOps = d.zimageOps[:0]
d.imageOps = d.imageOps[:0]
- state.z = 0
d.clearColor = mat.color.Opaque()
+ d.clear = true
continue
}
- state.z++
- if state.z != int(uint16(state.z)) {
- // TODO(eliasnaur) gioui.org/issue/127.
- panic("more than 65k paint objects not supported")
- }
- // Assume 16-bit depth buffer.
- const zdepth = 1 << 16
- // Convert z to window-space, assuming depth range [0;1].
- zf := float32(state.z)*2/zdepth - 1.0
img := imageOp{
- z: zf,
path: state.cpath,
- off: off,
clip: bounds,
material: mat,
}
- if state.rect && img.material.opaque {
- d.zimageOps = append(d.zimageOps, img)
- } else {
- d.imageOps = append(d.imageOps, img)
- }
+ d.imageOps = append(d.imageOps, img)
if clipData != nil {
// we added a clip path that should not remain
state.cpath = state.cpath.parent
- state.rect = wasrect
}
- case opconst.TypePush:
- state.z = d.collectOps(r, state)
- case opconst.TypePop:
- break loop
+ case ops.TypeSave:
+ id := ops.DecodeSave(encOp.Data)
+ d.save(id, state.t)
+ case ops.TypeLoad:
+ reset()
+ id := ops.DecodeLoad(encOp.Data)
+ state.t = d.states[id]
}
}
- return state.z
}
func expandPathOp(p *pathOp, clip image.Rectangle) {
@@ -840,78 +1028,83 @@ func expandPathOp(p *pathOp, clip image.Rectangle) {
}
}
-func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f32.Point, trans f32.Affine2D, clip image.Rectangle) material {
+func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
var m material
switch d.matType {
case materialColor:
m.material = materialColor
- m.color = f32color.RGBAFromSRGB(d.color)
+ m.color = f32color.LinearFromSRGB(d.color)
m.opaque = m.color.A == 1.0
+ case materialLinearGradient:
+ m.material = materialLinearGradient
+
+ m.color1 = f32color.LinearFromSRGB(d.color1)
+ m.color2 = f32color.LinearFromSRGB(d.color2)
+ m.opaque = m.color1.A == 1.0 && m.color2.A == 1.0
+
+ m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
case materialTexture:
m.material = materialTexture
dr := boundRectF(rect.Add(off))
sz := d.image.src.Bounds().Size()
- sr := layout.FRect(d.image.rect)
- if dx := float32(dr.Dx()); dx != 0 {
- // Don't clip 1 px width sources.
- if sdx := sr.Dx(); sdx > 1 {
- sr.Min.X += (float32(clip.Min.X-dr.Min.X)*sdx + dx/2) / dx
- sr.Max.X -= (float32(dr.Max.X-clip.Max.X)*sdx + dx/2) / dx
- }
- }
- if dy := float32(dr.Dy()); dy != 0 {
- // Don't clip 1 px height sources.
- if sdy := sr.Dy(); sdy > 1 {
- sr.Min.Y += (float32(clip.Min.Y-dr.Min.Y)*sdy + dy/2) / dy
- sr.Max.Y -= (float32(dr.Max.Y-clip.Max.Y)*sdy + dy/2) / dy
- }
- }
- tex, exists := cache.get(d.image.handle)
- if !exists {
- t := &texture{
- src: d.image.src,
- }
- cache.put(d.image.handle, t)
- tex = t
+ sr := f32.Rectangle{
+ Max: f32.Point{
+ X: float32(sz.X),
+ Y: float32(sz.Y),
+ },
}
- m.texture = tex.(*texture)
+ dx := float32(dr.Dx())
+ sdx := sr.Dx()
+ sr.Min.X += float32(clip.Min.X-dr.Min.X) * sdx / dx
+ sr.Max.X -= float32(dr.Max.X-clip.Max.X) * sdx / dx
+ dy := float32(dr.Dy())
+ sdy := sr.Dy()
+ sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
+ sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
uvScale, uvOffset := texSpaceTransform(sr, sz)
- m.uvTrans = trans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
+ m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
+ m.data = d.image
}
return m
}
-func (r *renderer) drawZOps(ops []imageOp) {
- r.ctx.SetDepthTest(true)
- r.ctx.BindVertexBuffer(r.blitter.quadVerts, 4*4, 0)
- r.ctx.BindInputLayout(r.blitter.layout)
- // Render front to back.
- for i := len(ops) - 1; i >= 0; i-- {
- img := ops[i]
+func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
+ for _, img := range ops {
+ m := img.material
+ if m.material == materialTexture {
+ r.texHandle(cache, m.data)
+ }
+ }
+}
+
+func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
+ for _, img := range ops {
m := img.material
switch m.material {
case materialTexture:
- r.ctx.BindTexture(0, r.texHandle(m.texture))
+ r.ctx.PrepareTexture(r.texHandle(cache, m.data))
}
- drc := img.clip
- scale, off := clipSpaceTransform(drc, r.blitter.viewport)
- r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvTrans)
+
+ var fbo stencilFBO
+ switch img.clipType {
+ case clipTypeNone:
+ continue
+ case clipTypePath:
+ fbo = r.pather.stenciler.cover(img.place.Idx)
+ case clipTypeIntersection:
+ fbo = r.pather.stenciler.intersections.fbos[img.place.Idx]
+ }
+ r.ctx.PrepareTexture(fbo.tex)
}
- r.ctx.SetDepthTest(false)
}
-func (r *renderer) drawOps(ops []imageOp) {
- r.ctx.SetDepthTest(true)
- r.ctx.DepthMask(false)
- r.ctx.BlendFunc(backend.BlendFactorOne, backend.BlendFactorOneMinusSrcAlpha)
- r.ctx.BindVertexBuffer(r.blitter.quadVerts, 4*4, 0)
- r.ctx.BindInputLayout(r.pather.coverer.layout)
- var coverTex backend.Texture
+func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
+ var coverTex driver.Texture
for _, img := range ops {
m := img.material
switch m.material {
case materialTexture:
- r.ctx.BindTexture(0, r.texHandle(m.texture))
+ r.ctx.BindTexture(0, r.texHandle(cache, m.data))
}
drc := img.clip
@@ -919,7 +1112,10 @@ func (r *renderer) drawOps(ops []imageOp) {
var fbo stencilFBO
switch img.clipType {
case clipTypeNone:
- r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvTrans)
+ p := r.blitter.pipelines[m.material]
+ r.ctx.BindPipeline(p.pipeline)
+ r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
+ r.blitter.blit(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans)
continue
case clipTypePath:
fbo = r.pather.stenciler.cover(img.place.Idx)
@@ -934,42 +1130,50 @@ func (r *renderer) drawOps(ops []imageOp) {
Min: img.place.Pos,
Max: img.place.Pos.Add(drc.Size()),
}
- coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size)
- r.pather.cover(img.z, m.material, m.color, scale, off, m.uvTrans, coverScale, coverOff)
+ coverScale, coverOff := texSpaceTransform(layout.FRect(uv), fbo.size)
+ p := r.pather.coverer.pipelines[m.material]
+ r.ctx.BindPipeline(p.pipeline)
+ r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
+ r.pather.cover(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
}
- r.ctx.DepthMask(true)
- r.ctx.SetDepthTest(false)
}
-func (b *blitter) blit(z float32, mat materialType, col f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
- p := b.prog[mat]
- b.ctx.BindProgram(p.prog)
+func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
+ p := b.pipelines[mat]
+ b.ctx.BindPipeline(p.pipeline)
var uniforms *blitUniforms
switch mat {
case materialColor:
- b.colUniforms.frag.color = col
- uniforms = &b.colUniforms.vert.blitUniforms
+ b.colUniforms.color = col
+ uniforms = &b.colUniforms.blitUniforms
case materialTexture:
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
- b.texUniforms.vert.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
- b.texUniforms.vert.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
- uniforms = &b.texUniforms.vert.blitUniforms
+ b.texUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
+ b.texUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
+ uniforms = &b.texUniforms.blitUniforms
+ case materialLinearGradient:
+ b.linearGradientUniforms.color1 = col1
+ b.linearGradientUniforms.color2 = col2
+
+ t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
+ b.linearGradientUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
+ b.linearGradientUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
+ uniforms = &b.linearGradientUniforms.blitUniforms
}
- uniforms.z = z
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
- p.UploadUniforms()
- b.ctx.DrawArrays(backend.DrawModeTriangleStrip, 0, 4)
+ p.UploadUniforms(b.ctx)
+ b.ctx.DrawArrays(0, 4)
}
// newUniformBuffer creates a new GPU uniform buffer backed by the
// structure uniformBlock points to.
-func newUniformBuffer(b backend.Device, uniformBlock interface{}) *uniformBuffer {
+func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer {
ref := reflect.ValueOf(uniformBlock)
// Determine the size of the uniforms structure, *uniforms.
size := ref.Elem().Type().Size()
// Map the uniforms structure as a byte slice.
ptr := (*[1 << 30]byte)(unsafe.Pointer(ref.Pointer()))[:size:size]
- ubuf, err := b.NewBuffer(backend.BufferBindingUniforms, len(ptr))
+ ubuf, err := b.NewBuffer(driver.BufferBindingUniforms, len(ptr))
if err != nil {
panic(err)
}
@@ -985,36 +1189,19 @@ func (u *uniformBuffer) Release() {
u.buf = nil
}
-func newProgram(prog backend.Program, vertUniforms, fragUniforms *uniformBuffer) *program {
- if vertUniforms != nil {
- prog.SetVertexUniforms(vertUniforms.buf)
- }
- if fragUniforms != nil {
- prog.SetFragmentUniforms(fragUniforms.buf)
- }
- return &program{prog: prog, vertUniforms: vertUniforms, fragUniforms: fragUniforms}
-}
-
-func (p *program) UploadUniforms() {
- if p.vertUniforms != nil {
- p.vertUniforms.Upload()
- }
- if p.fragUniforms != nil {
- p.fragUniforms.Upload()
+func (p *pipeline) UploadUniforms(ctx driver.Device) {
+ if p.uniforms != nil {
+ p.uniforms.Upload()
+ ctx.BindUniforms(p.uniforms.buf)
}
}
-func (p *program) Release() {
- p.prog.Release()
- p.prog = nil
- if p.vertUniforms != nil {
- p.vertUniforms.Release()
- p.vertUniforms = nil
- }
- if p.fragUniforms != nil {
- p.fragUniforms.Release()
- p.fragUniforms = nil
+func (p *pipeline) Release() {
+ p.pipeline.Release()
+ if p.uniforms != nil {
+ p.uniforms.Release()
}
+ *p = pipeline{}
}
// texSpaceTransform return the scale and offset that transforms the given subimage
@@ -1026,26 +1213,42 @@ func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Poin
return scale, offset
}
+// gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)].
+func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point) f32.Affine2D {
+ d := stop2.Sub(stop1)
+ l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
+ a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
+
+ // TODO: optimize
+ zp := f32.Point{}
+ return f32.Affine2D{}.
+ Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
+ Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
+ Offset(zp.Sub(stop1)). // offset to first stop point
+ Rotate(zp, a). // rotate to align gradient
+ Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
+}
+
// clipSpaceTransform returns the scale and offset that transforms the given
-// rectangle from a viewport into OpenGL clip space.
+// rectangle from a viewport into GPU driver device coordinates.
func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32.Point) {
- // First, transform UI coordinates to OpenGL coordinates:
+ // First, transform UI coordinates to device coordinates:
//
- // [(-1, +1) (+1, +1)]
// [(-1, -1) (+1, -1)]
+ // [(-1, +1) (+1, +1)]
//
x, y := float32(r.Min.X), float32(r.Min.Y)
w, h := float32(r.Dx()), float32(r.Dy())
vx, vy := 2/float32(viewport.X), 2/float32(viewport.Y)
x = x*vx - 1
- y = 1 - y*vy
+ y = y*vy - 1
w *= vx
h *= vy
// Then, compute the transformation from the fullscreen quad to
// the rectangle at (x, y) and dimensions (w, h).
scale := f32.Point{X: w * .5, Y: h * .5}
- offset := f32.Point{X: x + w*.5, Y: y - h*.5}
+ offset := f32.Point{X: x + w*.5, Y: y + h*.5}
return scale, offset
}
@@ -1097,29 +1300,73 @@ func (d *drawOps) writeVertCache(n int) []byte {
}
// transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
-func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D) (verts []byte, bounds f32.Rectangle) {
+func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, strWidth float32) (verts []byte, bounds f32.Rectangle) {
inf := float32(math.Inf(+1))
d.qs.bounds = f32.Rectangle{
Min: f32.Point{X: inf, Y: inf},
Max: f32.Point{X: -inf, Y: -inf},
}
d.qs.d = d
- bo := binary.LittleEndian
startLength := len(d.vertCache)
- for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
- d.qs.contour = bo.Uint32(aux)
- quad := ops.DecodeQuad(aux[4:])
- quad = quad.Transform(tr)
- d.qs.splitAndEncode(quad)
+ switch {
+ case strWidth > 0:
+ // Stroke path.
+ ss := stroke.StrokeStyle{
+ Width: strWidth,
+ }
+ quads := stroke.StrokePathCommands(ss, pathData)
+ for _, quad := range quads {
+ d.qs.contour = quad.Contour
+ quad.Quad = quad.Quad.Transform(tr)
+
+ d.qs.splitAndEncode(quad.Quad)
+ }
- aux = aux[ops.QuadSize+4:]
+ case outline:
+ decodeToOutlineQuads(&d.qs, tr, pathData)
}
fillMaxY(d.vertCache[startLength:])
return d.vertCache[startLength:], d.qs.bounds
}
+// decodeOutlineQuads decodes scene commands, splits them into quadratic béziers
+// as needed and feeds them to the supplied splitter.
+func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
+ for len(pathData) >= scene.CommandSize+4 {
+ qs.contour = bo.Uint32(pathData)
+ cmd := ops.DecodeCommand(pathData[4:])
+ switch cmd.Op() {
+ case scene.OpLine:
+ var q stroke.QuadSegment
+ q.From, q.To = scene.DecodeLine(cmd)
+ q.Ctrl = q.From.Add(q.To).Mul(.5)
+ q = q.Transform(tr)
+ qs.splitAndEncode(q)
+ case scene.OpGap:
+ var q stroke.QuadSegment
+ q.From, q.To = scene.DecodeGap(cmd)
+ q.Ctrl = q.From.Add(q.To).Mul(.5)
+ q = q.Transform(tr)
+ qs.splitAndEncode(q)
+ case scene.OpQuad:
+ var q stroke.QuadSegment
+ q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd)
+ q = q.Transform(tr)
+ qs.splitAndEncode(q)
+ case scene.OpCubic:
+ for _, q := range stroke.SplitCubic(scene.DecodeCubic(cmd)) {
+ q = q.Transform(tr)
+ qs.splitAndEncode(q)
+ }
+ default:
+ panic("unsupported scene command")
+ }
+ pathData = pathData[scene.CommandSize+4:]
+ }
+}
+
// create GPU vertices for transformed r, find the bounds and establish texture transform.
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
if isPureOffset(tr) {