12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592 |
- // SPDX-License-Identifier: Unlicense OR MIT
- /*
- Package gpu implements the rendering of Gio drawing operations. It
- is used by package app and package app/headless and is otherwise not
- useful except for integrating with external window implementations.
- */
- package gpu
- import (
- "encoding/binary"
- "errors"
- "fmt"
- "image"
- "image/color"
- "math"
- "reflect"
- "time"
- "unsafe"
- "gioui.org/gpu/internal/driver"
- "gioui.org/internal/byteslice"
- "gioui.org/internal/f32"
- "gioui.org/internal/f32color"
- "gioui.org/internal/ops"
- "gioui.org/internal/scene"
- "gioui.org/internal/stroke"
- "gioui.org/layout"
- "gioui.org/op"
- "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 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
- }
- type gpu struct {
- cache *textureCache
- profile string
- timers *timers
- frameStart time.Time
- stencilTimer, coverTimer, cleanupTimer *timer
- drawOps drawOps
- ctx driver.Device
- renderer *renderer
- }
- type renderer struct {
- ctx driver.Device
- blitter *blitter
- pather *pather
- packer packer
- intersections packer
- layers packer
- layerFBOs fboSet
- }
- type drawOps struct {
- reader ops.Reader
- states []f32.Affine2D
- transStack []f32.Affine2D
- layers []opacityLayer
- opacityStack []int
- vertCache []byte
- viewport image.Point
- clear bool
- clearColor f32color.RGBA
- imageOps []imageOp
- pathOps []*pathOp
- pathOpCache []pathOp
- qs quadSplitter
- pathCache *opCache
- }
- type opacityLayer struct {
- opacity float32
- parent int
- // depth of the opacity stack. Layers of equal depth are
- // independent and may be packed into one atlas.
- depth int
- // opStart and opEnd denote the range of drawOps.imageOps
- // that belong to the layer.
- opStart, opEnd int
- // clip of the layer operations.
- clip image.Rectangle
- place placement
- }
- type drawState struct {
- t f32.Affine2D
- cpath *pathOp
- matType materialType
- // Current paint.ImageOp
- image imageOpData
- // Current paint.ColorOp, if any.
- 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
- // intersect is the intersection of bounds and all
- // previous clip bounds.
- intersect f32.Rectangle
- pathKey opKey
- path bool
- pathVerts []byte
- parent *pathOp
- place placement
- }
- type imageOp struct {
- path *pathOp
- clip image.Rectangle
- material material
- clipType clipType
- // place is either a placement in the path fbos or intersection fbos,
- // depending on clipType.
- place placement
- // layerOps is the number of operations this
- // operation replaces.
- layerOps int
- }
- 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
- opacity float32
- // For materialTypeTexture.
- data imageOpData
- tex driver.Texture
- uvTrans f32.Affine2D
- }
- const (
- filterLinear = 0
- filterNearest = 1
- )
- // imageOpData is the shadow of paint.ImageOp.
- type imageOpData struct {
- src *image.RGBA
- handle interface{}
- filter byte
- }
- type linearGradientOpData struct {
- stop1 f32.Point
- color1 color.NRGBA
- stop2 f32.Point
- color2 color.NRGBA
- }
- func decodeImageOp(data []byte, refs []interface{}) imageOpData {
- handle := refs[1]
- if handle == nil {
- return imageOpData{}
- }
- return imageOpData{
- src: refs[0].(*image.RGBA),
- handle: handle,
- filter: data[1],
- }
- }
- func decodeColorOp(data []byte) color.NRGBA {
- data = data[:ops.TypeColorLen]
- return color.NRGBA{
- R: data[1],
- G: data[2],
- B: data[3],
- A: data[4],
- }
- }
- func decodeLinearGradientOp(data []byte) linearGradientOpData {
- data = data[:ops.TypeLinearGradientLen]
- bo := binary.LittleEndian
- return linearGradientOpData{
- stop1: f32.Point{
- X: math.Float32frombits(bo.Uint32(data[1:])),
- Y: math.Float32frombits(bo.Uint32(data[5:])),
- },
- stop2: f32.Point{
- X: math.Float32frombits(bo.Uint32(data[9:])),
- Y: math.Float32frombits(bo.Uint32(data[13:])),
- },
- 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],
- },
- }
- }
- type resource interface {
- release()
- }
- type texture struct {
- src *image.RGBA
- tex driver.Texture
- }
- type blitter struct {
- ctx driver.Device
- viewport image.Point
- pipelines [2][3]*pipeline
- colUniforms *blitColUniforms
- texUniforms *blitTexUniforms
- linearGradientUniforms *blitLinearGradientUniforms
- quadVerts driver.Buffer
- }
- type blitColUniforms struct {
- blitUniforms
- _ [128 - unsafe.Sizeof(blitUniforms{}) - unsafe.Sizeof(colorUniforms{})]byte // Padding to 128 bytes.
- colorUniforms
- }
- type blitTexUniforms struct {
- blitUniforms
- }
- type blitLinearGradientUniforms struct {
- blitUniforms
- _ [128 - unsafe.Sizeof(blitUniforms{}) - unsafe.Sizeof(gradientUniforms{})]byte // Padding to 128 bytes.
- gradientUniforms
- }
- type uniformBuffer struct {
- buf driver.Buffer
- ptr []byte
- }
- type pipeline struct {
- pipeline driver.Pipeline
- uniforms *uniformBuffer
- }
- type blitUniforms struct {
- transform [4]float32
- uvTransformR1 [4]float32
- uvTransformR2 [4]float32
- opacity float32
- fbo float32
- _ [2]float32
- }
- type colorUniforms struct {
- color f32color.RGBA
- }
- type gradientUniforms struct {
- color1 f32color.RGBA
- color2 f32color.RGBA
- }
- type clipType uint8
- const (
- clipTypeNone clipType = iota
- clipTypePath
- clipTypeIntersection
- )
- type materialType uint8
- const (
- materialColor materialType = iota
- materialLinearGradient
- materialTexture
- )
- // 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()
- feats := d.Caps().Features
- switch {
- case feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
- return newGPU(d)
- }
- return nil, errors.New("no available GPU driver")
- }
- func newGPU(ctx driver.Device) (*gpu, error) {
- g := &gpu{
- cache: newTextureCache(),
- }
- g.drawOps.pathCache = newOpCache()
- if err := g.init(ctx); err != nil {
- return nil, err
- }
- return g, nil
- }
- func (g *gpu) init(ctx driver.Device) error {
- g.ctx = ctx
- g.renderer = newRenderer(ctx)
- return nil
- }
- 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.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) {
- g.renderer.blitter.viewport = viewport
- g.renderer.pather.viewport = viewport
- g.drawOps.reset(viewport)
- g.drawOps.collect(frameOps, viewport)
- if false && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
- g.frameStart = time.Now()
- g.timers = newTimers(g.ctx)
- g.stencilTimer = g.timers.newTimer()
- g.coverTimer = g.timers.newTimer()
- g.cleanupTimer = g.timers.newTimer()
- }
- }
- 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)
- }
- g.stencilTimer.begin()
- 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.renderer.uploadImages(g.cache, g.drawOps.imageOps)
- g.renderer.prepareDrawOps(g.drawOps.imageOps)
- g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
- g.renderer.drawLayers(g.drawOps.layers, 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(false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
- g.coverTimer.end()
- g.ctx.EndRenderPass()
- g.cleanupTimer.begin()
- g.cache.frame()
- g.drawOps.pathCache.frame()
- g.cleanupTimer.end()
- if false && g.timers.ready() {
- st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
- ft := st + covt + cleant
- q := 100 * time.Microsecond
- 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 st:%7s cov:%7s", frameDur, ft, st, covt)
- }
- return nil
- }
- func (g *gpu) Profile() string {
- return g.profile
- }
- func (r *renderer) texHandle(cache *textureCache, data imageOpData) driver.Texture {
- key := textureCacheKey{
- filter: data.filter,
- handle: data.handle,
- }
- var tex *texture
- t, exists := cache.get(key)
- if !exists {
- t = &texture{
- src: data.src,
- }
- cache.put(key, t)
- }
- tex = t.(*texture)
- if tex.tex != nil {
- return tex.tex
- }
- var minFilter, magFilter driver.TextureFilter
- switch data.filter {
- case filterLinear:
- minFilter, magFilter = driver.FilterLinearMipmapLinear, driver.FilterLinear
- case filterNearest:
- minFilter, magFilter = driver.FilterNearest, driver.FilterNearest
- }
- handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA,
- data.src.Bounds().Dx(), data.src.Bounds().Dy(),
- minFilter, magFilter,
- driver.BufferBindingTexture,
- )
- if err != nil {
- panic(err)
- }
- driver.UploadImage(handle, image.Pt(0, 0), data.src)
- tex.tex = handle
- return tex.tex
- }
- func (t *texture) release() {
- if t.tex != nil {
- t.tex.Release()
- }
- }
- func newRenderer(ctx driver.Device) *renderer {
- r := &renderer{
- ctx: ctx,
- blitter: newBlitter(ctx),
- pather: newPather(ctx),
- }
- maxDim := ctx.Caps().MaxTextureSize
- // Large atlas textures cause artifacts due to precision loss in
- // shaders.
- if cap := 8192; maxDim > cap {
- maxDim = cap
- }
- d := image.Pt(maxDim, maxDim)
- r.packer.maxDims = d
- r.intersections.maxDims = d
- r.layers.maxDims = d
- return r
- }
- func (r *renderer) release() {
- r.pather.release()
- r.blitter.release()
- r.layerFBOs.delete(r.ctx, 0)
- }
- 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 {
- panic(err)
- }
- b := &blitter{
- ctx: ctx,
- quadVerts: quadVerts,
- }
- b.colUniforms = new(blitColUniforms)
- b.texUniforms = new(blitTexUniforms)
- 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.pipelines = pipelines
- return b
- }
- func (b *blitter) release() {
- b.quadVerts.Release()
- for _, p := range b.pipelines {
- for _, p := range p {
- p.Release()
- }
- }
- }
- func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) (pipelines [2][3]*pipeline, err error) {
- defer func() {
- if err != nil {
- for _, p := range pipelines {
- for _, p := range p {
- if p != nil {
- p.Release()
- }
- }
- }
- }
- }()
- blend := driver.BlendDesc{
- Enable: true,
- SrcFactor: driver.BlendFactorOne,
- DstFactor: driver.BlendFactorOneMinusSrcAlpha,
- }
- layout := driver.VertexLayout{
- Inputs: []driver.InputDesc{
- {Type: shader.DataTypeFloat, Size: 2, Offset: 0},
- {Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
- },
- Stride: 4 * 4,
- }
- vsh, err := b.NewVertexShader(vsSrc)
- if err != nil {
- return pipelines, err
- }
- defer vsh.Release()
- for i, format := range []driver.TextureFormat{driver.TextureFormatOutput, driver.TextureFormatSRGBA} {
- {
- 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: format,
- Topology: driver.TopologyTriangleStrip,
- })
- if err != nil {
- return pipelines, err
- }
- var vertBuffer *uniformBuffer
- if u := uniforms[materialTexture]; u != nil {
- vertBuffer = newUniformBuffer(b, u)
- }
- pipelines[i][materialTexture] = &pipeline{pipe, vertBuffer}
- }
- {
- var vertBuffer *uniformBuffer
- fsh, err := b.NewFragmentShader(fsSrc[materialColor])
- if err != nil {
- return pipelines, err
- }
- defer fsh.Release()
- pipe, err := b.NewPipeline(driver.PipelineDesc{
- VertexShader: vsh,
- FragmentShader: fsh,
- BlendDesc: blend,
- VertexLayout: layout,
- PixelFormat: format,
- Topology: driver.TopologyTriangleStrip,
- })
- if err != nil {
- return pipelines, err
- }
- if u := uniforms[materialColor]; u != nil {
- vertBuffer = newUniformBuffer(b, u)
- }
- pipelines[i][materialColor] = &pipeline{pipe, vertBuffer}
- }
- {
- var vertBuffer *uniformBuffer
- fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
- if err != nil {
- return pipelines, err
- }
- defer fsh.Release()
- pipe, err := b.NewPipeline(driver.PipelineDesc{
- VertexShader: vsh,
- FragmentShader: fsh,
- BlendDesc: blend,
- VertexLayout: layout,
- PixelFormat: format,
- Topology: driver.TopologyTriangleStrip,
- })
- if err != nil {
- return pipelines, err
- }
- if u := uniforms[materialLinearGradient]; u != nil {
- vertBuffer = newUniformBuffer(b, u)
- }
- pipelines[i][materialLinearGradient] = &pipeline{pipe, vertBuffer}
- }
- }
- return pipelines, nil
- }
- func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
- if len(r.packer.sizes) == 0 {
- return
- }
- fbo := -1
- 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.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) {
- if len(r.intersections.sizes) == 0 {
- return
- }
- fbo := -1
- r.pather.stenciler.beginIntersect(r.intersections.sizes)
- 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]
- 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) {
- if p.parent != nil {
- r.intersectPath(p.parent, clip)
- }
- if !p.path {
- return
- }
- uv := image.Rectangle{
- Min: p.place.Pos,
- Max: p.place.Pos.Add(p.clip.Size()),
- }
- o := clip.Min.Sub(p.clip.Min)
- sub := image.Rectangle{
- Min: o,
- Max: o.Add(clip.Size()),
- }
- fbo := r.pather.stenciler.cover(p.place.Idx)
- r.ctx.BindTexture(0, fbo.tex)
- coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
- subScale, subOff := texSpaceTransform(f32.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) {
- r.intersections.clear()
- for i, img := range ops {
- var npaths int
- var onePath *pathOp
- for p := img.path; p != nil; p = p.parent {
- if p.path {
- onePath = p
- npaths++
- }
- }
- switch npaths {
- case 0:
- case 1:
- place := onePath.place
- place.Pos = place.Pos.Sub(onePath.clip.Min).Add(img.clip.Min)
- ops[i].place = place
- ops[i].clipType = clipTypePath
- default:
- sz := image.Point{X: img.clip.Dx(), Y: img.clip.Dy()}
- place, ok := r.intersections.add(sz)
- if !ok {
- panic("internal error: if the intersection fit, the intersection should fit as well")
- }
- ops[i].clipType = clipTypeIntersection
- ops[i].place = place
- }
- }
- }
- func (r *renderer) packStencils(pops *[]*pathOp) {
- r.packer.clear()
- ops := *pops
- // Allocate atlas space for cover textures.
- var i int
- for i < len(ops) {
- p := ops[i]
- if p.clip.Empty() {
- ops[i] = ops[len(ops)-1]
- ops = ops[:len(ops)-1]
- continue
- }
- place, ok := r.packer.add(p.clip.Size())
- 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 %v", p.clip, r.packer.maxDims))
- }
- p.place = place
- i++
- }
- *pops = ops
- }
- func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
- // Make every layer bounds contain nested layers; cull empty layers.
- for i := len(layers) - 1; i >= 0; i-- {
- l := layers[i]
- if l.parent != -1 {
- b := layers[l.parent].clip
- layers[l.parent].clip = b.Union(l.clip)
- }
- if l.clip.Empty() {
- layers = append(layers[:i], layers[i+1:]...)
- }
- }
- // Pack layers.
- r.layers.clear()
- depth := 0
- for i := range layers {
- l := &layers[i]
- // Only layers of the same depth may be packed together.
- if l.depth != depth {
- r.layers.newPage()
- }
- place, ok := r.layers.add(l.clip.Size())
- if !ok {
- // The layer area is at most the entire screen. Hopefully no
- // screen is larger than GL_MAX_TEXTURE_SIZE.
- panic(fmt.Errorf("layer size %v is larger than maximum texture size %v", l.clip.Size(), r.layers.maxDims))
- }
- l.place = place
- }
- return layers
- }
- func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
- if len(r.layers.sizes) == 0 {
- return
- }
- fbo := -1
- r.layerFBOs.resize(r.ctx, driver.TextureFormatSRGBA, r.layers.sizes)
- for i := len(layers) - 1; i >= 0; i-- {
- l := layers[i]
- if fbo != l.place.Idx {
- if fbo != -1 {
- r.ctx.EndRenderPass()
- r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
- }
- fbo = l.place.Idx
- f := r.layerFBOs.fbos[fbo]
- r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear})
- }
- v := image.Rectangle{
- Min: l.place.Pos,
- Max: l.place.Pos.Add(l.clip.Size()),
- }
- r.ctx.Viewport(v.Min.X, v.Min.Y, v.Dx(), v.Dy())
- f := r.layerFBOs.fbos[fbo]
- r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
- sr := f32.FRect(v)
- uvScale, uvOffset := texSpaceTransform(sr, f.size)
- uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
- // Replace layer ops with one textured op.
- ops[l.opStart] = imageOp{
- clip: l.clip,
- material: material{
- material: materialTexture,
- tex: f.tex,
- uvTrans: uvTrans,
- opacity: l.opacity,
- },
- layerOps: l.opEnd - l.opStart - 1,
- }
- }
- if fbo != -1 {
- r.ctx.EndRenderPass()
- r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
- }
- }
- func (d *drawOps) reset(viewport image.Point) {
- d.viewport = viewport
- d.imageOps = d.imageOps[:0]
- d.pathOps = d.pathOps[:0]
- d.pathOpCache = d.pathOpCache[:0]
- d.vertCache = d.vertCache[:0]
- d.transStack = d.transStack[:0]
- d.layers = d.layers[:0]
- d.opacityStack = d.opacityStack[:0]
- }
- func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
- viewf := f32.Rectangle{
- Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
- }
- 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
- }
- }
- func (d *drawOps) newPathOp() *pathOp {
- d.pathOpCache = append(d.pathOpCache, pathOp{})
- return &d.pathOpCache[len(d.pathOpCache)-1]
- }
- func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point) {
- npath := d.newPathOp()
- *npath = pathOp{
- 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)
- }
- if len(aux) > 0 {
- npath.rect = false
- npath.pathKey = auxKey
- npath.path = true
- npath.pathVerts = aux
- d.pathOps = append(d.pathOps, npath)
- }
- state.cpath = npath
- }
- 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 ops.OpType(encOp.Data[0]) {
- 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 ops.TypePopTransform:
- n := len(d.transStack)
- state.t = d.transStack[n-1]
- d.transStack = d.transStack[:n-1]
- case ops.TypePushOpacity:
- opacity := ops.DecodeOpacity(encOp.Data)
- parent := -1
- depth := len(d.opacityStack)
- if depth > 0 {
- parent = d.opacityStack[depth-1]
- }
- lidx := len(d.layers)
- d.layers = append(d.layers, opacityLayer{
- opacity: opacity,
- parent: parent,
- depth: depth,
- opStart: len(d.imageOps),
- })
- d.opacityStack = append(d.opacityStack, lidx)
- case ops.TypePopOpacity:
- n := len(d.opacityStack)
- idx := d.opacityStack[n-1]
- d.layers[idx].opEnd = len(d.imageOps)
- d.opacityStack = d.opacityStack[: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 := f32.FRect(op.Bounds)
- trans, off := state.t.Split()
- 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.
- 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?
- bounds = v.bounds
- } else {
- 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(quads.key, opCacheValue{bounds: bounds})
- }
- } else {
- quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
- quads.key = opKey{Key: encOp.Key}
- }
- d.addClipPath(&state, quads.aux, quads.key, bounds, off)
- quads = quadsOp{}
- case ops.TypePopClip:
- state.cpath = state.cpath.parent
- case ops.TypeColor:
- state.matType = materialColor
- state.color = decodeColorOp(encOp.Data)
- 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 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.
- t, off := state.t.Split()
- // 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
- }
- if clipData != nil {
- // The paint operation is sheared or rotated, add a clip path representing
- // this transformed rectangle.
- k := opKey{Key: encOp.Key}
- k.SetTransform(t) // TODO: This call has no effect.
- d.addClipPath(&state, clipData, k, bnd, off)
- }
- bounds := cl.Round()
- mat := state.materialFor(bnd, off, partialTrans, bounds)
- rect := state.cpath == nil || state.cpath.rect
- if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) && len(d.opacityStack) == 0 {
- // 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.imageOps = d.imageOps[:0]
- d.clearColor = mat.color.Opaque()
- d.clear = true
- continue
- }
- img := imageOp{
- path: state.cpath,
- clip: bounds,
- material: mat,
- }
- if n := len(d.opacityStack); n > 0 {
- idx := d.opacityStack[n-1]
- lb := d.layers[idx].clip
- if lb.Empty() {
- d.layers[idx].clip = img.clip
- } else {
- d.layers[idx].clip = lb.Union(img.clip)
- }
- }
- d.imageOps = append(d.imageOps, img)
- if clipData != nil {
- // we added a clip path that should not remain
- state.cpath = state.cpath.parent
- }
- 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]
- }
- }
- }
- func expandPathOp(p *pathOp, clip image.Rectangle) {
- for p != nil {
- pclip := p.clip
- if !pclip.Empty() {
- clip = clip.Union(pclip)
- }
- p.clip = clip
- p = p.parent
- }
- }
- func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
- m := material{
- opacity: 1.,
- }
- switch d.matType {
- case materialColor:
- m.material = materialColor
- 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 := rect.Add(off).Round()
- sz := d.image.src.Bounds().Size()
- sr := f32.Rectangle{
- Max: f32.Point{
- X: float32(sz.X),
- Y: float32(sz.Y),
- },
- }
- 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 = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
- m.data = d.image
- }
- return m
- }
- func (r *renderer) uploadImages(cache *textureCache, ops []imageOp) {
- for i := range ops {
- img := &ops[i]
- m := img.material
- if m.material == materialTexture {
- img.material.tex = r.texHandle(cache, m.data)
- }
- }
- }
- func (r *renderer) prepareDrawOps(ops []imageOp) {
- for _, img := range ops {
- m := img.material
- switch m.material {
- case materialTexture:
- r.ctx.PrepareTexture(m.tex)
- }
- var fbo FBO
- 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)
- }
- }
- func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageOp) {
- var coverTex driver.Texture
- for i := 0; i < len(ops); i++ {
- img := ops[i]
- i += img.layerOps
- m := img.material
- switch m.material {
- case materialTexture:
- r.ctx.BindTexture(0, m.tex)
- }
- drc := img.clip.Add(opOff)
- scale, off := clipSpaceTransform(drc, viewport)
- var fbo FBO
- fboIdx := 0
- if isFBO {
- fboIdx = 1
- }
- switch img.clipType {
- case clipTypeNone:
- p := r.blitter.pipelines[fboIdx][m.material]
- r.ctx.BindPipeline(p.pipeline)
- r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
- r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
- continue
- case clipTypePath:
- fbo = r.pather.stenciler.cover(img.place.Idx)
- case clipTypeIntersection:
- fbo = r.pather.stenciler.intersections.fbos[img.place.Idx]
- }
- if coverTex != fbo.tex {
- coverTex = fbo.tex
- r.ctx.BindTexture(1, coverTex)
- }
- uv := image.Rectangle{
- Min: img.place.Pos,
- Max: img.place.Pos.Add(drc.Size()),
- }
- coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
- p := r.pather.coverer.pipelines[fboIdx][m.material]
- r.ctx.BindPipeline(p.pipeline)
- r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
- r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
- }
- }
- func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
- fboIdx := 0
- if fbo {
- fboIdx = 1
- }
- p := b.pipelines[fboIdx][mat]
- b.ctx.BindPipeline(p.pipeline)
- var uniforms *blitUniforms
- switch mat {
- case materialColor:
- b.colUniforms.color = col
- uniforms = &b.colUniforms.blitUniforms
- case materialTexture:
- t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
- uniforms = &b.texUniforms.blitUniforms
- uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
- uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
- case materialLinearGradient:
- b.linearGradientUniforms.color1 = col1
- b.linearGradientUniforms.color2 = col2
- t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
- uniforms = &b.linearGradientUniforms.blitUniforms
- uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
- uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
- }
- uniforms.fbo = 0
- if fbo {
- uniforms.fbo = 1
- }
- uniforms.opacity = opacity
- uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
- 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 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 := unsafe.Slice((*byte)(unsafe.Pointer(ref.Pointer())), size)
- ubuf, err := b.NewBuffer(driver.BufferBindingUniforms, len(ptr))
- if err != nil {
- panic(err)
- }
- return &uniformBuffer{buf: ubuf, ptr: ptr}
- }
- func (u *uniformBuffer) Upload() {
- u.buf.Upload(u.ptr)
- }
- func (u *uniformBuffer) Release() {
- u.buf.Release()
- u.buf = nil
- }
- func (p *pipeline) UploadUniforms(ctx driver.Device) {
- if p.uniforms != nil {
- p.uniforms.Upload()
- ctx.BindUniforms(p.uniforms.buf)
- }
- }
- 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
- // into quad texture coordinates.
- func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Point) {
- size := f32.Point{X: float32(bounds.X), Y: float32(bounds.Y)}
- scale := f32.Point{X: r.Dx() / size.X, Y: r.Dy() / size.Y}
- offset := f32.Point{X: r.Min.X / size.X, Y: r.Min.Y / size.Y}
- 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 GPU driver device coordinates.
- func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32.Point) {
- // First, transform UI coordinates to device coordinates:
- //
- // [(-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 = 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}
- return scale, offset
- }
- // Fill in maximal Y coordinates of the NW and NE corners.
- func fillMaxY(verts []byte) {
- contour := 0
- bo := binary.LittleEndian
- for len(verts) > 0 {
- maxy := float32(math.Inf(-1))
- i := 0
- for ; i+vertStride*4 <= len(verts); i += vertStride * 4 {
- vert := verts[i : i+vertStride]
- // MaxY contains the integer contour index.
- pathContour := int(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).MaxY)):]))
- if contour != pathContour {
- contour = pathContour
- break
- }
- fromy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).FromY)):]))
- ctrly := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).CtrlY)):]))
- toy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).ToY)):]))
- if fromy > maxy {
- maxy = fromy
- }
- if ctrly > maxy {
- maxy = ctrly
- }
- if toy > maxy {
- maxy = toy
- }
- }
- fillContourMaxY(maxy, verts[:i])
- verts = verts[i:]
- }
- }
- func fillContourMaxY(maxy float32, verts []byte) {
- bo := binary.LittleEndian
- for i := 0; i < len(verts); i += vertStride {
- off := int(unsafe.Offsetof(((*vertex)(nil)).MaxY))
- bo.PutUint32(verts[i+off:], math.Float32bits(maxy))
- }
- }
- func (d *drawOps) writeVertCache(n int) []byte {
- d.vertCache = append(d.vertCache, make([]byte, n)...)
- return d.vertCache[len(d.vertCache)-n:]
- }
- // transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
- 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
- startLength := len(d.vertCache)
- 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)
- }
- 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 = binary.LittleEndian.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:
- from, ctrl0, ctrl1, to := scene.DecodeCubic(cmd)
- qs.scratch = stroke.SplitCubic(from, ctrl0, ctrl1, to, qs.scratch[:0])
- for _, q := range qs.scratch {
- 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) {
- // fast-path to allow blitting of pure rectangles
- _, _, ox, _, _, oy := tr.Elems()
- off := f32.Pt(ox, oy)
- bnd.Min = r.Min.Add(off)
- bnd.Max = r.Max.Add(off)
- return
- }
- // transform all corners, find new bounds
- corners := [4]f32.Point{
- tr.Transform(r.Min), tr.Transform(f32.Pt(r.Max.X, r.Min.Y)),
- tr.Transform(r.Max), tr.Transform(f32.Pt(r.Min.X, r.Max.Y)),
- }
- bnd.Min = f32.Pt(math.MaxFloat32, math.MaxFloat32)
- bnd.Max = f32.Pt(-math.MaxFloat32, -math.MaxFloat32)
- for _, c := range corners {
- if c.X < bnd.Min.X {
- bnd.Min.X = c.X
- }
- if c.Y < bnd.Min.Y {
- bnd.Min.Y = c.Y
- }
- if c.X > bnd.Max.X {
- bnd.Max.X = c.X
- }
- if c.Y > bnd.Max.Y {
- bnd.Max.Y = c.Y
- }
- }
- // build the GPU vertices
- l := len(d.vertCache)
- d.vertCache = append(d.vertCache, make([]byte, vertStride*4*4)...)
- aux = d.vertCache[l:]
- encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1])
- encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2])
- encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3])
- encodeQuadTo(aux[vertStride*4*3:], 0, corners[3], corners[3].Add(corners[0]).Mul(0.5), corners[0])
- fillMaxY(aux)
- // establish the transform mapping from bounds rectangle to transformed corners
- var P1, P2, P3 f32.Point
- P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
- P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
- P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
- P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
- P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
- P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
- sx, sy := P2.X-P3.X, P2.Y-P3.Y
- ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
- return
- }
- func isPureOffset(t f32.Affine2D) bool {
- a, b, _, d, e, _ := t.Elems()
- return a == 1 && b == 0 && d == 0 && e == 1
- }
- func newShaders(ctx driver.Device, vsrc, fsrc shader.Sources) (vert driver.VertexShader, frag driver.FragmentShader, err error) {
- vert, err = ctx.NewVertexShader(vsrc)
- if err != nil {
- return
- }
- frag, err = ctx.NewFragmentShader(fsrc)
- if err != nil {
- vert.Release()
- }
- return
- }
|