Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better cairo draw caching + code #1701

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions CairoMakie/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"

[compat]
Cairo = "1.0.4"
Expand All @@ -24,7 +23,6 @@ FileIO = "1.1"
FreeType = "3, 4.0"
GeometryBasics = "0.4.1"
Makie = "=0.16.5"
StaticArrays = "0.12, 1.0"
julia = "1.3"

[extras]
Expand Down
2 changes: 1 addition & 1 deletion CairoMakie/src/CairoMakie.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module CairoMakie

using Makie, LinearAlgebra
using Colors, GeometryBasics, FileIO, StaticArrays
using Colors, GeometryBasics, FileIO
import SHA
import Base64
import Cairo
Expand Down
44 changes: 44 additions & 0 deletions CairoMakie/src/precompiles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,49 @@ function _precompile_()
f, ax2, pl = lines(1:4)
Makie.colorbuffer(ax1.scene)
Makie.colorbuffer(ax2.scene)
scene = Scene()

screen = CairoMakie.CairoScreen(scene)

attributes = Attributes(
colormap=nothing,
colorrange=nothing,
model=Mat4f(I),
color=:red,

)
r = Rect2f(0, 0, 1, 1)
mesh = GeometryBasics.mesh(r)
CairoMakie.draw_mesh2D(scene, screen, attributes, mesh)
mesh = GeometryBasics.uv_mesh(r)
CairoMakie.draw_mesh2D(scene, screen, attributes, mesh)
mesh = GeometryBasics.normal_mesh(r)
CairoMakie.draw_mesh2D(scene, screen, attributes, mesh)
mesh = GeometryBasics.uv_normal_mesh(r)
CairoMakie.draw_mesh2D(scene, screen, attributes, mesh)

color = to_color(:red)
vs = decompose(Point2f, mesh)::Vector{Point2f}
fs = decompose(GLTriangleFace, mesh)::Vector{GLTriangleFace}
uv = decompose_uv(mesh)::Union{Nothing, Vector{Vec2f}}
model = Mat4f(I)
cols = per_face_colors(color, nothing, nothing, nothing, vs, fs, nothing, uv)
CairoMakie.draw_mesh2D(scene, screen, cols, vs, fs, model)

mesh2 = GeometryBasics.normal_mesh(Sphere(Point3f(0), 1f0))

attributes = Attributes(
colormap=nothing,
colorrange=nothing,
model=Mat4f(I),
color=:red,
shading=true,
diffuse=Vec3f(1),
specular=Vec3f(0),
shininess=2f0,
faceculling=true
)

CairoMakie.draw_mesh3D(scene, screen, attributes, mesh2)
return
end
133 changes: 80 additions & 53 deletions CairoMakie/src/primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,12 @@ function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Text{<:Tuple{
position = primitive.position[]
# use cached glyph info
glyph_collection = to_value(primitive[1])

draw_glyph_collection(scene, ctx, position, glyph_collection, remove_billboard(rotation), model, space, offset)

nothing
end


function draw_glyph_collection(scene, ctx, positions, glyph_collections::AbstractArray, rotation, model::SMatrix, space, offset)
function draw_glyph_collection(scene, ctx, positions, glyph_collections::AbstractArray, rotation, model::Mat, space, offset)

# TODO: why is the Ref around model necessary? doesn't broadcast_foreach handle staticarrays matrices?
broadcast_foreach(positions, glyph_collections, rotation,
Expand Down Expand Up @@ -404,15 +402,15 @@ function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation,
glyphpos = let
# project without yflip - we need to apply model before that
p = project_position(scene, position, Mat4f(I), false)

# flip for Cairo
p += (p3_to_p2(glyphoffset .+ p3_offset))
p = (_deref(model) * Vec4f(p[1], p[2], 0, 1))[Vec(1, 2)]
p = (0, 1) .* scene.camera.resolution[] .+ p .* (1, -1)
p
end
# and the scale is just taken as is
scale = length(scale) == 2 ? scale : SVector(scale, scale)
scale = length(scale) == 2 ? scale : Vec(scale, scale)

mat = let
scale_mat = if length(scale) == 2
Expand Down Expand Up @@ -597,11 +595,11 @@ function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Union{Heatmap

# Rectangles and polygons that are directly adjacent usually show
# white lines between them due to anti aliasing. To avoid this we
# increase their size slightly.
# increase their size slightly.

if alpha(colors[i, j]) == 1
# sign.(p - center) gives the direction in which we need to
# extend the polygon. (Which may change due to rotations in the
# sign.(p - center) gives the direction in which we need to
# extend the polygon. (Which may change due to rotations in the
# model matrix.) (i!=1) etc is used to avoid increasing the
# outer extent of the heatmap.
center = 0.25 * (p1 + p2 + p3 + p4)
Expand Down Expand Up @@ -629,35 +627,42 @@ end
################################################################################


function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Makie.Mesh)
function draw_atomic(scene::Scene, screen::CairoScreen, @nospecialize(primitive::Makie.Mesh))
mesh = primitive[1][]
if Makie.cameracontrols(scene) isa Union{Camera2D, Makie.PixelCamera, Makie.EmptyCamera}
draw_mesh2D(scene, screen, primitive)
draw_mesh2D(scene, screen, primitive, mesh)
else
if !haskey(primitive, :faceculling)
primitive[:faceculling] = Observable(-10)
end
draw_mesh3D(scene, screen, primitive)
draw_mesh3D(scene, screen, primitive, mesh)
end
return nothing
end

function draw_mesh2D(scene, screen, primitive)
@get_attribute(primitive, (color,))
function draw_mesh2D(scene, screen, @nospecialize(plot), @nospecialize(mesh))
@get_attribute(plot, (color,))
color = to_color(hasproperty(mesh, :color) ? mesh.color : color)
vs = decompose(Point2f, mesh)::Vector{Point2f}
fs = decompose(GLTriangleFace, mesh)::Vector{GLTriangleFace}
uv = decompose_uv(mesh)::Union{Nothing, Vector{Vec2f}}
model = plot.model[]::Mat4f
colormap = to_colormap(to_value(get(plot, :colormap, nothing)))::Union{Nothing, Vector{RGBAf}}
colorrange = to_value(get(plot, :colorrange, nothing))::Union{Nothing, Vec2f}
cols = per_face_colors(color, colormap, colorrange, nothing, vs, fs, nothing, uv)
return draw_mesh2D(scene, screen, cols, vs, fs, model)
end

function draw_mesh2D(scene, screen, per_face_cols,
vs::Vector{Point2f}, fs::Vector{GLTriangleFace}, model::Mat4f)

colormap = get(primitive, :colormap, nothing) |> to_value |> to_colormap
colorrange = get(primitive, :colorrange, nothing) |> to_value
ctx = screen.context
model = primitive.model[]
mesh = GeometryBasics.mesh(primitive[1][])
# Priorize colors of the mesh if present
# This is a hack, which needs cleaning up in the Mesh plot type!
color = hasproperty(mesh, :color) ? mesh.color : color
vs = decompose(Point, mesh); fs = decompose(TriangleFace, mesh)
uv = hasproperty(mesh, :uv) ? mesh.uv : nothing

pattern = Cairo.CairoPatternMesh()

cols = per_face_colors(color, colormap, colorrange, nothing, vs, fs, nothing, uv)
for (f, (c1, c2, c3)) in zip(fs, cols)
for (f, (c1, c2, c3)) in zip(fs, per_face_cols)
t1, t2, t3 = project_position.(scene, vs[f], (model,)) #triangle points
Cairo.mesh_pattern_begin_patch(pattern)

Expand All @@ -684,47 +689,69 @@ end

nan2zero(x) = !isnan(x) * x

function draw_mesh3D(
scene, screen, primitive;
mesh = primitive[1][], pos = Vec4f(0), scale = 1f0
)
@get_attribute(primitive, (color, shading, diffuse,
specular, shininess, faceculling))

colormap = get(primitive, :colormap, nothing) |> to_value |> to_colormap
colorrange = get(primitive, :colorrange, nothing) |> to_value
matcap = get(primitive, :matcap, nothing) |> to_value
function draw_mesh3D(scene, screen, attributes, mesh; pos = Vec4f(0), scale = 1f0)
# Priorize colors of the mesh if present
@get_attribute(attributes, (color,))

colormap = to_colormap(to_value(get(attributes, :colormap, nothing)))
colorrange = to_value(get(attributes, :colorrange, nothing))
matcap = to_value(get(attributes, :matcap, nothing))

color = hasproperty(mesh, :color) ? mesh.color : color
meshpoints = decompose(Point3f, mesh)::Vector{Point3f}
meshfaces = decompose(GLTriangleFace, mesh)::Vector{GLTriangleFace}
meshnormals = decompose_normals(mesh)::Vector{Vec3f}
meshuvs = texturecoordinates(mesh)::Union{Nothing, Vector{Vec2f}}

lowclip = color_or_nothing(to_value(get(attributes, :lowclip, nothing)))::Union{Nothing, RGBAf}
highclip = color_or_nothing(to_value(get(attributes, :highclip, nothing)))::Union{Nothing, RGBAf}
nan_color = color_or_nothing(to_value(get(attributes, :nan_color, nothing)))::Union{Nothing, RGBAf}

per_face_col = per_face_colors(
color, colormap, colorrange, matcap, meshfaces, meshnormals, meshuvs,
lowclip, highclip, nan_color
)

@get_attribute(attributes, (shading, diffuse,
specular, shininess, faceculling))

model = attributes.model[]::Mat4f

draw_mesh3D(
scene, screen, meshpoints, meshfaces, meshnormals, per_face_col, pos, scale,
model, shading::Bool, diffuse::Vec3f,
specular::Vec3f, shininess::Float32, faceculling::Bool
)
end

function draw_mesh3D(
scene, screen, meshpoints, meshfaces, meshnormals, per_face_col, pos, scale,
model, shading, diffuse,
specular, shininess, faceculling
)


ctx = screen.context

model = primitive.model[]
ctx = screen.context
view = scene.camera.view[]
projection = scene.camera.projection[]
i = SOneTo(3)
i = Vec(1, 2, 3)
normalmatrix = transpose(inv(view[i, i] * model[i, i]))

# Mesh data
# transform to view/camera space
func = Makie.transform_func_obs(scene)[]
# pass func as argument to function, so that we get a function barrier
# and have `func` be fully typed inside closure
vs = broadcast(decompose(Point, mesh), (func,)) do v, f
vs = broadcast(meshpoints, (func,)) do v, f
# Should v get a nan2zero?
v = Makie.apply_transform(f, v)
p4d = to_ndim(Vec4f, scale .* to_ndim(Vec3f, v, 0f0), 1f0)
view * (model * p4d .+ to_ndim(Vec4f, pos, 0f0))
end
fs = decompose(GLTriangleFace, mesh)
uv = texturecoordinates(mesh)
ns = map(n -> normalize(normalmatrix * n), decompose_normals(mesh))
cols = per_face_colors(
color, colormap, colorrange, matcap, vs, fs, ns, uv,
get(primitive, :lowclip, nothing) |> to_value |> color_or_nothing,
get(primitive, :highclip, nothing) |> to_value |> color_or_nothing,
get(primitive, :nan_color, nothing) |> to_value |> color_or_nothing
)

ns = map(n -> normalize(normalmatrix * n), meshnormals)

# Liight math happens in view/camera space
pointlight = Makie.get_point_light(scene)
Expand All @@ -750,37 +777,37 @@ function draw_mesh3D(
@inbounds begin
p = (clip ./ clip[4])[Vec(1, 2)]
p_yflip = Vec2f(p[1], -p[2])
p_0_to_1 = (p_yflip .+ 1f0) / 2f0
p_0_to_1 = (p_yflip .+ 1f0) ./ 2f0
end
p = p_0_to_1 .* scene.camera.resolution[]
return Vec3f(p[1], p[2], clip[3])
end

# Approximate zorder
zorder = sortperm(fs, by = f -> average_z(ts, f))
zorder = sortperm(meshfaces, by = f -> average_z(ts, f))

# Face culling
zorder = filter(i -> any(last.(ns[fs[i]]) .> faceculling), zorder)
zorder = filter(i -> any(last.(ns[meshfaces[i]]) .> faceculling), zorder)

pattern = Cairo.CairoPatternMesh()
for k in reverse(zorder)
f = fs[k]
f = meshfaces[k]
t1, t2, t3 = ts[f]

# light calculation
c1, c2, c3 = if shading
map(ns[f], vs[f], cols[k]) do N, v, c
map(ns[f], vs[f], per_face_col[k]) do N, v, c
L = normalize(lightpos .- v[Vec(1,2,3)])
diff_coeff = max(dot(L, N), 0.0)
H = normalize(L + normalize(-v[SOneTo(3)]))
H = normalize(L + normalize(-v[Vec(1, 2, 3)]))
spec_coeff = max(dot(H, N), 0.0)^shininess
c = RGBA(c)
new_c = (ambient .+ diff_coeff .* diffuse) .* Vec3f(c.r, c.g, c.b) .+
specular * spec_coeff
RGBA(new_c..., c.alpha)
end
else
cols[k]
per_face_col[k]
end
# debug normal coloring
# n1, n2, n3 = Vec3f(0.5) .+ 0.5ns[f]
Expand Down Expand Up @@ -822,7 +849,7 @@ function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Makie.Surface
if !haskey(primitive, :faceculling)
primitive[:faceculling] = Observable(-10)
end
draw_mesh3D(scene, screen, primitive, mesh=mesh)
draw_mesh3D(scene, screen, primitive, mesh)
primitive[:color] = old
return nothing
end
Expand Down Expand Up @@ -912,7 +939,7 @@ function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Makie.MeshSca
scale = markersize isa Vector ? markersize[i] : markersize

draw_mesh3D(
scene, screen, submesh, mesh = m, pos = p,
scene, screen, submesh, m, pos = p,
scale = scale isa Real ? Vec3f(scale) : to_ndim(Vec3f, scale, 1f0)
)
end
Expand Down
6 changes: 3 additions & 3 deletions CairoMakie/src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function project_position(scene, point, model, yflip = true)
# flip y to match cairo
p_yflip = Vec2f(p[1], (1f0 - 2f0 * yflip) * p[2])
# normalize to between 0 and 1
p_0_to_1 = (p_yflip .+ 1f0) / 2f0
p_0_to_1 = (p_yflip .+ 1f0) ./ 2f0
end
# multiply with scene resolution for final position
return p_0_to_1 .* res
Expand Down Expand Up @@ -189,10 +189,10 @@ Base.getindex(fi::FaceIterator{:PerFace}, i::Integer) = fi.data[i]
Base.getindex(fi::FaceIterator{:PerVert}, i::Integer) = fi.data[fi.faces[i]]
Base.getindex(fi::FaceIterator{:Const}, i::Integer) = ntuple(i-> fi.data, 3)

color_or_nothing(c) = c === nothing ? nothing : to_color(c)
color_or_nothing(c) = isnothing(c) ? nothing : to_color(c)

function per_face_colors(
color, colormap, colorrange, matcap, vertices, faces, normals, uv,
color, colormap, colorrange, matcap, faces, normals, uv,
lowclip=nothing, highclip=nothing, nan_color=nothing
)
if matcap !== nothing
Expand Down
2 changes: 0 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
Showoff = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
SignedDistanceFields = "73760f76-fbc4-59ce-8f25-708e95d2df96"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
StatsFuns = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
Expand Down Expand Up @@ -83,7 +82,6 @@ PolygonOps = "0.1.1"
RelocatableFolders = "0.1, 0.2"
Showoff = "0.3, 1.0.2"
SignedDistanceFields = "0.4"
StaticArrays = "0.12, 1.0"
StatsBase = "0.31, 0.32, 0.33"
StatsFuns = "0.9"
StructArrays = "0.3, 0.4, 0.5, 0.6"
Expand Down
Loading