From 554fa84a0a823aaa1807814e9b0079e158693c42 Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Fri, 3 May 2019 12:10:05 +0200 Subject: [PATCH] added code for MSAA, doesn't work properly yet though --- backend/softwarebackend/color.go | 10 ++ backend/softwarebackend/fill.go | 35 +++--- backend/softwarebackend/software.go | 9 +- backend/softwarebackend/triangles.go | 172 ++++++++++++++++++++++++++- 4 files changed, 200 insertions(+), 26 deletions(-) diff --git a/backend/softwarebackend/color.go b/backend/softwarebackend/color.go index 8f6e3ec..50be62c 100644 --- a/backend/softwarebackend/color.go +++ b/backend/softwarebackend/color.go @@ -5,6 +5,16 @@ import ( "math" ) +func toRGBA(src color.Color) color.RGBA { + ir, ig, ib, ia := src.RGBA() + return color.RGBA{ + R: uint8(ir >> 8), + G: uint8(ig >> 8), + B: uint8(ib >> 8), + A: uint8(ia >> 8), + } +} + func mix(src, dest color.Color) color.RGBA { ir1, ig1, ib1, ia1 := src.RGBA() r1 := float64(ir1) / 65535.0 diff --git a/backend/softwarebackend/fill.go b/backend/softwarebackend/fill.go index 2800db7..224b767 100644 --- a/backend/softwarebackend/fill.go +++ b/backend/softwarebackend/fill.go @@ -10,7 +10,7 @@ import ( func (b *SoftwareBackend) Clear(pts [4][2]float64) { iterateTriangles(pts[:], func(tri [][2]float64) { - b.fillTriangle(tri, func(x, y int) { + b.fillTriangleNoAA(tri, func(x, y int) { if b.clip.AlphaAt(x, y).A == 0 { return } @@ -29,11 +29,10 @@ func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { dirlen := math.Sqrt(dir[0]*dir[0] + dir[1]*dir[1]) dir[0] /= dirlen dir[1] /= dirlen - b.fillTriangles(pts, func(x, y int) { - pos := [2]float64{float64(x) - from[0], float64(y) - from[1]} + b.fillTriangles(pts, func(x, y float64) color.RGBA { + pos := [2]float64{x - from[0], y - from[1]} r := (pos[0]*dir[0] + pos[1]*dir[1]) / dirlen - color := lg.data.ColorAt(r) - b.Image.SetRGBA(x, y, mix(color, b.Image.RGBAAt(x, y))) + return lg.data.ColorAt(r) }) } else if rg := style.RadialGradient; rg != nil { rg := rg.(*RadialGradient) @@ -41,8 +40,8 @@ func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { to := [2]float64{style.Gradient.X1, style.Gradient.Y1} radFrom := style.Gradient.RadFrom radTo := style.Gradient.RadTo - b.fillTriangles(pts, func(x, y int) { - pos := [2]float64{float64(x), float64(y)} + b.fillTriangles(pts, func(x, y float64) color.RGBA { + pos := [2]float64{x, y} oa := 0.5 * math.Sqrt( math.Pow(-2.0*from[0]*from[0]+2.0*from[0]*to[0]+2.0*from[0]*pos[0]-2.0*to[0]*pos[0]-2.0*from[1]*from[1]+2.0*from[1]*to[1]+2.0*from[1]*pos[1]-2.0*to[1]*pos[1]+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)- 4.0*(from[0]*from[0]-2.0*from[0]*pos[0]+pos[0]*pos[0]+from[1]*from[1]-2.0*from[1]*pos[1]+pos[1]*pos[1]-radFrom*radFrom)* @@ -52,11 +51,10 @@ func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { o1 := (-oa + ob) / oc o2 := (oa + ob) / oc if math.IsNaN(o1) && math.IsNaN(o2) { - return + return color.RGBA{} } o := math.Max(o1, o2) - col := rg.data.ColorAt(o) - b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y))) + return rg.data.ColorAt(o) }) } else if ip := style.ImagePattern; ip != nil { ip := ip.(*ImagePattern) @@ -66,16 +64,16 @@ func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { fw, fh := float64(w), float64(h) rx := ip.data.Repeat == backendbase.Repeat || ip.data.Repeat == backendbase.RepeatX ry := ip.data.Repeat == backendbase.Repeat || ip.data.Repeat == backendbase.RepeatY - b.fillTriangles(pts, func(x, y int) { - pos := [2]float64{float64(x), float64(y)} + b.fillTriangles(pts, func(x, y float64) color.RGBA { + pos := [2]float64{x, y} tfptx := pos[0]*ip.data.Transform[0] + pos[1]*ip.data.Transform[1] + ip.data.Transform[2] tfpty := pos[0]*ip.data.Transform[3] + pos[1]*ip.data.Transform[4] + ip.data.Transform[5] if !rx && (tfptx < 0 || tfptx >= fw) { - return + return color.RGBA{} } if !ry && (tfpty < 0 || tfpty >= fh) { - return + return color.RGBA{} } mx := int(math.Floor(tfptx)) % w @@ -87,12 +85,11 @@ func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { my += h } - col := mip.At(mx, my) - b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y))) + return toRGBA(mip.At(mx, my)) }) } else { - b.fillTriangles(pts, func(x, y int) { - b.Image.SetRGBA(x, y, mix(style.Color, b.Image.RGBAAt(x, y))) + b.fillTriangles(pts, func(x, y float64) color.RGBA { + return style.Color }) } } @@ -132,7 +129,7 @@ func (b *SoftwareBackend) Clip(pts [][2]float64) { } iterateTriangles(pts[:], func(tri [][2]float64) { - b.fillTriangle(tri, func(x, y int) { + b.fillTriangleNoAA(tri, func(x, y int) { b.mask.SetAlpha(x, y, color.Alpha{A: 255}) }) }) diff --git a/backend/softwarebackend/software.go b/backend/softwarebackend/software.go index 8a22f45..81935c0 100644 --- a/backend/softwarebackend/software.go +++ b/backend/softwarebackend/software.go @@ -9,9 +9,12 @@ import ( type SoftwareBackend struct { Image *image.RGBA - clip *image.Alpha - mask *image.Alpha - w, h int + + // MSAA int + + clip *image.Alpha + mask *image.Alpha + w, h int } func New(w, h int) *SoftwareBackend { diff --git a/backend/softwarebackend/triangles.go b/backend/softwarebackend/triangles.go index ce5fda7..8b1ea76 100644 --- a/backend/softwarebackend/triangles.go +++ b/backend/softwarebackend/triangles.go @@ -46,7 +46,7 @@ func triangleLR(tri [][2]float64, y float64) (l, r float64, outside bool) { return } -func (b *SoftwareBackend) fillTriangle(tri [][2]float64, fn func(x, y int)) { +func (b *SoftwareBackend) fillTriangleNoAA(tri [][2]float64, fn func(x, y int)) { minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1]))) maxY := int(math.Ceil(math.Max(math.Max(tri[0][1], tri[1][1]), tri[2][1]))) if minY < 0 { @@ -88,6 +88,102 @@ func (b *SoftwareBackend) fillTriangle(tri [][2]float64, fn func(x, y int)) { } } +type msaaPixel struct { + ix, iy int + fx, fy float64 +} + +func (b *SoftwareBackend) fillTriangleMSAA(tri [][2]float64, msaaLevel int, msaaPixels []msaaPixel, fn func(x, y int)) []msaaPixel { + msaaStep := 1.0 / float64(msaaLevel+1) + + minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1]))) + maxY := int(math.Ceil(math.Max(math.Max(tri[0][1], tri[1][1]), tri[2][1]))) + if minY < 0 { + minY = 0 + } else if minY >= b.h { + return msaaPixels + } + if maxY < 0 { + return msaaPixels + } else if maxY >= b.h { + maxY = b.h - 1 + } + + for y := minY; y <= maxY; y++ { + var l, r [5]float64 + allOut := true + minL, maxR := math.MaxFloat64, 0.0 + + sy := float64(y) + msaaStep*0.5 + for step := 0; step <= msaaLevel; step++ { + var out bool + l[step], r[step], out = triangleLR(tri, sy) + if l[step] < 0 { + l[step] = 0 + } else if l[step] > float64(b.w) { + l[step] = float64(b.w) + out = true + } + if r[step] < 0 { + r[step] = 0 + out = true + } else if r[step] > float64(b.w) { + r[step] = float64(b.w) + } + if r[step] <= l[step] { + out = true + } + if !out { + allOut = false + minL = math.Min(minL, l[step]) + maxR = math.Max(maxR, r[step]) + } + sy += msaaStep + } + + if allOut { + continue + } + + fl, cr := int(math.Floor(minL)), int(math.Ceil(maxR)) + for x := fl; x <= cr; x++ { + sy = float64(y) + msaaStep*0.5 + allIn := true + check: + for stepy := 0; stepy <= msaaLevel; stepy++ { + sx := float64(x) + msaaStep*0.5 + for stepx := 0; stepx <= msaaLevel; stepx++ { + if sx < l[stepy] || sx >= r[stepy] { + allIn = false + break check + } + sx += msaaStep + } + sy += msaaStep + } + + if allIn { + fn(x, y) + continue + } + + sy = float64(y) + msaaStep*0.5 + for stepy := 0; stepy <= msaaLevel; stepy++ { + sx := float64(x) + msaaStep*0.5 + for stepx := 0; stepx <= msaaLevel; stepx++ { + if sx >= l[stepy] && sx < r[stepy] { + msaaPixels = append(msaaPixels, msaaPixel{ix: x, iy: y, fx: sx, fy: sy}) + } + sx += msaaStep + } + sy += msaaStep + } + } + } + + return msaaPixels +} + func quadArea(quad [4][2]float64) float64 { leftv := [2]float64{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]} topv := [2]float64{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]} @@ -181,9 +277,9 @@ func iterateTriangles(pts [][2]float64, fn func(tri [][2]float64)) { } } -func (b *SoftwareBackend) fillTriangles(pts [][2]float64, fn func(x, y int)) { +func (b *SoftwareBackend) fillTrianglesNoAA(pts [][2]float64, fn func(x, y float64) color.RGBA) { iterateTriangles(pts[:], func(tri [][2]float64) { - b.fillTriangle(tri, func(x, y int) { + b.fillTriangleNoAA(tri, func(x, y int) { if b.clip.AlphaAt(x, y).A == 0 { return } @@ -191,7 +287,75 @@ func (b *SoftwareBackend) fillTriangles(pts [][2]float64, fn func(x, y int)) { return } b.mask.SetAlpha(x, y, color.Alpha{A: 255}) - fn(x, y) + col := fn(float64(x), float64(y)) + if col.A > 0 { + b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y))) + } }) }) } + +func (b *SoftwareBackend) fillTrianglesMSAA(pts [][2]float64, msaaLevel int, fn func(x, y float64) color.RGBA) { + var msaaPixelBuf [500]msaaPixel + msaaPixels := msaaPixelBuf[:0] + + iterateTriangles(pts[:], func(tri [][2]float64) { + msaaPixels = b.fillTriangleMSAA(tri, msaaLevel, msaaPixels, func(x, y int) { + if b.clip.AlphaAt(x, y).A == 0 { + return + } + if b.mask.AlphaAt(x, y).A > 0 { + return + } + b.mask.SetAlpha(x, y, color.Alpha{A: 255}) + col := fn(float64(x), float64(y)) + if col.A > 0 { + b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y))) + } + }) + }) + + samples := (msaaLevel + 1) * (msaaLevel + 1) + + for i, px := range msaaPixels { + if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 || b.mask.AlphaAt(px.ix, px.iy).A > 0 { + continue + } + b.mask.SetAlpha(px.ix, px.iy, color.Alpha{A: 255}) + + var mr, mg, mb, ma int + for j, px2 := range msaaPixels[i:] { + if px2.ix != px.ix || px2.iy != px.iy { + continue + } + + col := fn(px.fx, px.fy) + if col.A == 0 { + return + } + mr += int(col.R) + mg += int(col.G) + mb += int(col.B) + ma += int(col.A) + + msaaPixels[i+j].ix = -1 + } + + combined := color.RGBA{ + R: uint8(mr / samples), + G: uint8(mg / samples), + B: uint8(mb / samples), + A: uint8(ma / samples), + } + b.Image.SetRGBA(px.ix, px.iy, mix(combined, b.Image.RGBAAt(px.ix, px.iy))) + + } +} + +func (b *SoftwareBackend) fillTriangles(pts [][2]float64, fn func(x, y float64) color.RGBA) { + // if b.MSAA > 0 { + // b.fillTrianglesMSAA(pts, b.MSAA, fn) + // } else { + b.fillTrianglesNoAA(pts, fn) + // } +}