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