anti-aliasing optimization
This commit is contained in:
parent
759c045568
commit
1395abaff9
|
@ -1,35 +1,67 @@
|
|||
import benchy, cairo, chroma, math, pixie
|
||||
|
||||
var
|
||||
surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
|
||||
ctx = surface.create()
|
||||
block:
|
||||
var
|
||||
surface = imageSurfaceCreate(FORMAT_ARGB32, 1920, 1080)
|
||||
ctx = surface.create()
|
||||
|
||||
ctx.setSourceRgba(0, 0, 0, 1)
|
||||
ctx.fill()
|
||||
ctx.setSourceRgba(0, 0, 1, 1)
|
||||
ctx.setSourceRgba(0, 0, 1, 1)
|
||||
|
||||
timeIt "cairo":
|
||||
ctx.newPath()
|
||||
ctx.moveTo(0, 0)
|
||||
ctx.lineTo(500, 0)
|
||||
ctx.lineTo(500, 500)
|
||||
ctx.lineTo(0, 500)
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
timeIt "cairo1":
|
||||
ctx.newPath()
|
||||
ctx.moveTo(0, 0)
|
||||
ctx.lineTo(1920, 0)
|
||||
ctx.lineTo(1920, 1080)
|
||||
ctx.lineTo(0, 1080)
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
surface.flush()
|
||||
|
||||
# discard surface.writeToPng("cairo1.png")
|
||||
|
||||
var a = newImage(1920, 1080)
|
||||
a.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
timeIt "pixie1":
|
||||
var p: pixie.Path
|
||||
p.moveTo(0, 0)
|
||||
p.lineTo(1920, 0)
|
||||
p.lineTo(1920, 1080)
|
||||
p.lineTo(0, 1080)
|
||||
p.closePath()
|
||||
a.fillPath(p, rgba(0, 0, 255, 255))
|
||||
|
||||
# a.writeFile("pixie1.png")
|
||||
|
||||
block:
|
||||
var
|
||||
surface = imageSurfaceCreate(FORMAT_ARGB32, 1920, 1080)
|
||||
ctx = surface.create()
|
||||
|
||||
ctx.setSourceRgba(0, 0, 1, 1)
|
||||
|
||||
timeIt "cairo2":
|
||||
ctx.newPath()
|
||||
ctx.moveTo(500, 240)
|
||||
ctx.lineTo(1500, 240)
|
||||
ctx.lineTo(1920, 600)
|
||||
ctx.lineTo(0, 600)
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
surface.flush()
|
||||
|
||||
# discard surface.writeToPng("cairo.png")
|
||||
# discard surface.writeToPng("cairo2.png")
|
||||
|
||||
var a = newImage(1000, 1000)
|
||||
a.fill(rgba(0, 0, 0, 255))
|
||||
var a = newImage(1920, 1080)
|
||||
a.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
timeIt "pixie":
|
||||
var p: pixie.Path
|
||||
p.moveTo(0, 0)
|
||||
p.lineTo(500, 0)
|
||||
p.lineTo(500, 500)
|
||||
p.lineTo(0, 500)
|
||||
p.closePath()
|
||||
a.fillPath(p, rgba(0, 0, 255, 255))
|
||||
timeIt "pixie2":
|
||||
var p: pixie.Path
|
||||
p.moveTo(500, 240)
|
||||
p.lineTo(1500, 240)
|
||||
p.lineTo(1920, 600)
|
||||
p.lineTo(0, 600)
|
||||
p.closePath()
|
||||
a.fillPath(p, rgba(0, 0, 255, 255))
|
||||
|
||||
# a.writeFile("pixie.png")
|
||||
# a.writeFile("pixie2.png")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import blends, bumpy, chroma, common, images, masks, paints, pixie/internal,
|
||||
strutils, vmath
|
||||
strutils, system/memory, vmath
|
||||
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
import nimsimd/sse2
|
||||
|
@ -990,6 +990,21 @@ proc shapesToSegments(shapes: seq[seq[Vec2]]): seq[(Segment, int16)] =
|
|||
|
||||
result.add((segment, winding))
|
||||
|
||||
proc requiresAntiAliasing(segments: seq[(Segment, int16)]): bool =
|
||||
## Returns true if the fill requires antialiasing.
|
||||
|
||||
template hasFractional(v: float32): bool =
|
||||
v - trunc(v) != 0
|
||||
|
||||
for i in 0 ..< segments.len: # For arc
|
||||
let segment = segments[i][0]
|
||||
if segment.at.x != segment.to.x or
|
||||
segment.at.x.hasFractional() or # at.x and to.x are the same
|
||||
segment.at.y.hasFractional() or
|
||||
segment.to.y.hasFractional():
|
||||
# AA is required if all segments are not vertical or have fractional > 0
|
||||
return true
|
||||
|
||||
proc computePixelBounds(segments: seq[(Segment, int16)]): Rect =
|
||||
## Compute the bounds of the segments.
|
||||
var
|
||||
|
@ -1100,11 +1115,12 @@ proc computeCoverages(
|
|||
hits: var seq[(float32, int16)],
|
||||
size: Vec2,
|
||||
y: int,
|
||||
aa: bool,
|
||||
partitioning: Partitioning,
|
||||
windingRule: WindingRule
|
||||
) {.inline.} =
|
||||
const
|
||||
quality = 5 # Must divide 255 cleanly (1, 3, 5, 15, 17, 51, 85)
|
||||
let
|
||||
quality = if aa: 5 else: 1 # Must divide 255 cleanly (1, 3, 5, 15, 17, 51, 85)
|
||||
sampleCoverage = (255 div quality).uint8
|
||||
offset = 1 / quality.float32
|
||||
initialOffset = offset / 2 + epsilon
|
||||
|
@ -1174,15 +1190,18 @@ proc computeCoverages(
|
|||
let fillLen = at.int - fillStart
|
||||
if fillLen > 0:
|
||||
var i = fillStart
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
let vSampleCoverage = mm_set1_epi8(cast[int8](sampleCoverage))
|
||||
for j in countup(i, fillStart + fillLen - 16, 16):
|
||||
var coverage = mm_loadu_si128(coverages[j].addr)
|
||||
coverage = mm_add_epi8(coverage, vSampleCoverage)
|
||||
mm_storeu_si128(coverages[j].addr, coverage)
|
||||
i += 16
|
||||
for j in i ..< fillStart + fillLen:
|
||||
coverages[j] += sampleCoverage
|
||||
if aa:
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
let vSampleCoverage = mm_set1_epi8(cast[int8](sampleCoverage))
|
||||
for j in countup(i, fillStart + fillLen - 16, 16):
|
||||
var coverage = mm_loadu_si128(coverages[j].addr)
|
||||
coverage = mm_add_epi8(coverage, vSampleCoverage)
|
||||
mm_storeu_si128(coverages[j].addr, coverage)
|
||||
i += 16
|
||||
for j in i ..< fillStart + fillLen:
|
||||
coverages[j] += sampleCoverage
|
||||
else:
|
||||
nimSetMem(coverages[fillStart].addr, sampleCoverage.cint, fillLen)
|
||||
|
||||
prevAt = at
|
||||
|
||||
|
@ -1205,6 +1224,7 @@ proc fillShapes(
|
|||
rgbx = color.asRgbx()
|
||||
blender = blendMode.blender()
|
||||
segments = shapes.shapesToSegments()
|
||||
aa = segments.requiresAntiAliasing()
|
||||
bounds = computePixelBounds(segments)
|
||||
startX = max(0, bounds.x.int)
|
||||
startY = max(0, bounds.y.int)
|
||||
|
@ -1221,6 +1241,7 @@ proc fillShapes(
|
|||
hits,
|
||||
image.wh,
|
||||
y,
|
||||
aa,
|
||||
partitioning,
|
||||
windingRule
|
||||
)
|
||||
|
@ -1247,7 +1268,7 @@ proc fillShapes(
|
|||
# If the coverages are not all zero
|
||||
if mm_movemask_epi8(mm_cmpeq_epi32(coverage, first32)) == 0xffff:
|
||||
# Coverages are all 255
|
||||
if rgbx.a == 255 and blendMode == bmNormal:
|
||||
if blendMode == bmNormal and rgbx.a == 255:
|
||||
mm_storeu_si128(image.data[index].addr, vColor)
|
||||
else:
|
||||
let backdrop = mm_loadu_si128(image.data[index].addr)
|
||||
|
@ -1282,25 +1303,18 @@ proc fillShapes(
|
|||
x += 4
|
||||
|
||||
while x < image.width:
|
||||
if x + 8 <= coverages.len:
|
||||
let peeked = cast[ptr uint64](coverages[x].addr)[]
|
||||
if peeked == 0:
|
||||
x += 8
|
||||
continue
|
||||
|
||||
let coverage = coverages[x]
|
||||
if coverage != 0:
|
||||
var source = rgbx
|
||||
if coverage != 255:
|
||||
source.r = ((source.r.uint32 * coverage) div 255).uint8
|
||||
source.g = ((source.g.uint32 * coverage) div 255).uint8
|
||||
source.b = ((source.b.uint32 * coverage) div 255).uint8
|
||||
source.a = ((source.a.uint32 * coverage) div 255).uint8
|
||||
|
||||
if source.a == 255 and blendMode == bmNormal:
|
||||
if blendMode == bmNormal and coverage == 255 and rgbx.a == 255:
|
||||
# Skip blending
|
||||
image.setRgbaUnsafe(x, y, source)
|
||||
image.setRgbaUnsafe(x, y, rgbx)
|
||||
else:
|
||||
var source = rgbx
|
||||
if coverage != 255:
|
||||
source.r = ((source.r.uint32 * coverage) div 255).uint8
|
||||
source.g = ((source.g.uint32 * coverage) div 255).uint8
|
||||
source.b = ((source.b.uint32 * coverage) div 255).uint8
|
||||
source.a = ((source.a.uint32 * coverage) div 255).uint8
|
||||
let backdrop = image.getRgbaUnsafe(x, y)
|
||||
image.setRgbaUnsafe(x, y, blender(backdrop, source))
|
||||
inc x
|
||||
|
@ -1314,6 +1328,7 @@ proc fillShapes(
|
|||
# rasterize only within the total bounds
|
||||
let
|
||||
segments = shapes.shapesToSegments()
|
||||
aa = segments.requiresAntiAliasing()
|
||||
bounds = computePixelBounds(segments)
|
||||
startX = max(0, bounds.x.int)
|
||||
startY = max(0, bounds.y.int)
|
||||
|
@ -1334,6 +1349,7 @@ proc fillShapes(
|
|||
hits,
|
||||
mask.wh,
|
||||
y,
|
||||
aa,
|
||||
partitioning,
|
||||
windingRule
|
||||
)
|
||||
|
@ -1356,18 +1372,10 @@ proc fillShapes(
|
|||
x += 16
|
||||
|
||||
while x < mask.width:
|
||||
if x + 8 <= coverages.len:
|
||||
let peeked = cast[ptr uint64](coverages[x].addr)[]
|
||||
if peeked == 0:
|
||||
x += 8
|
||||
continue
|
||||
|
||||
let coverage = coverages[x]
|
||||
if coverage != 0:
|
||||
let
|
||||
backdrop = mask.getValueUnsafe(x, y)
|
||||
blended = blendAlpha(backdrop, coverage)
|
||||
mask.setValueUnsafe(x, y, blended)
|
||||
let backdrop = mask.getValueUnsafe(x, y)
|
||||
mask.setValueUnsafe(x, y, blendAlpha(backdrop, coverage))
|
||||
inc x
|
||||
|
||||
proc miterLimitToAngle*(limit: float32): float32 =
|
||||
|
|
Loading…
Reference in a new issue