Open koehlerson opened 1 year ago
MWE
import GeometryBasics
using ShaderAbstractions
using GLMakie
using FileIO
using BenchmarkTools
brain = load(assetpath("brain.stl"))
mesh_brain = @btime mesh(
brain,
color = [tri[1][2] for tri in brain for i in 1:3],
colormap = Reverse(:Spectral),
figure = (resolution = (1000, 1000),)
)
triangles = Buffer(GeometryBasics.faces(brain))
positions = Buffer(Observable(GeometryBasics.metafree(GeometryBasics.coordinates(brain))))
brain_buf = GeometryBasics.Mesh(positions,triangles)
mesh_brain_buf = @btime mesh(
brain_buf,
color = [tri[1][2] for tri in brain for i in 1:3],
colormap = Reverse(:Spectral),
figure = (resolution = (1000, 1000),)
)
11.978 ms (37014 allocations: 3.20 MiB)
12.345 ms (37020 allocations: 7.23 MiB)
This happens only for Buffer(Observabe(Point32[]))
for a Vector{Point32}
its fine
hm I don't understand this. Why should the change from typeof(positions)==Buffer{Point{3,Float32},Vector{Point{3,Float32}}
to typeof(positions)==Vector{Point{3,Float32}}
be beneficial at all?
import GeometryBasics
using ShaderAbstractions
using GLMakie
using FileIO
using BenchmarkTools
brain = load(assetpath("brain.stl"))
println("benchmark with typeof(positions): $(typeof(brain.position))")
println("benchmark with typeof(simplices): $(typeof(getfield(brain,:simplices)))")
mesh_brain = @btime mesh(
brain,
color = [tri[1][2] for tri in brain for i in 1:3],
colormap = Reverse(:Spectral),
figure = (resolution = (1000, 1000),)
)
println("")
triangles = Buffer(GeometryBasics.faces(brain))
positions = Buffer(Observable(GeometryBasics.metafree(GeometryBasics.coordinates(brain))))
brain_buf = GeometryBasics.Mesh(positions,triangles)
println("benchmark with typeof(positions): $(typeof(positions))")
println("benchmark with typeof(simplices): $(typeof(triangles))")
mesh_brain_buf = @btime mesh(
brain_buf,
color = [tri[1][2] for tri in brain for i in 1:3],
colormap = Reverse(:Spectral),
figure = (resolution = (1000, 1000),)
)
println("")
positions_as_vec_without_buf_or_obs = GeometryBasics.metafree(GeometryBasics.coordinates(brain))
brain_buf_without = GeometryBasics.Mesh(positions_as_vec_without_buf_or_obs,triangles)
println("benchmark with typeof(positions): $(typeof(positions_as_vec_without_buf_or_obs))")
println("benchmark with typeof(simplices): $(typeof(triangles))")
mesh_brain_buf = @btime mesh(
brain_buf_without,
color = [tri[1][2] for tri in brain for i in 1:3],
colormap = Reverse(:Spectral),
figure = (resolution = (1000, 1000),)
)
benchmark with typeof(positions): Vector{Point{3, Float32}}
benchmark with typeof(simplices): GeometryBasics.FaceView{GeometryBasics.TriangleP{3, Float32, GeometryBasics.PointMeta{3, Float32, Point{3, Float32}, (:normals,), Tuple{GeometryBasics.Vec{3, Float32}}}}, GeometryBasics.PointMeta{3, Float32, Point{3, Float32}, (:normals,), Tuple{GeometryBasics.Vec{3, Float32}}}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}, StructArrays.StructVector{GeometryBasics.PointMeta{3, Float32, Point{3, Float32}, (:normals,), Tuple{GeometryBasics.Vec{3, Float32}}}, NamedTuple{(:position, :normals), Tuple{Vector{Point{3, Float32}}, Vector{GeometryBasics.Vec{3, Float32}}}}, Int64}, Vector{GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}
12.015 ms (37014 allocations: 3.20 MiB)
benchmark with typeof(positions): Buffer{Point{3, Float32}, Vector{Point{3, Float32}}}
benchmark with typeof(simplices): Buffer{GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}, Vector{GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}
12.358 ms (37020 allocations: 7.23 MiB)
benchmark with typeof(positions): Vector{Point{3, Float32}}
benchmark with typeof(simplices): Buffer{GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}, Vector{GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}
11.982 ms (37012 allocations: 3.20 MiB)
Note that the benchmarks remain the same for
< positions = Buffer(Observable(GeometryBasics.metafree(GeometryBasics.coordinates(brain))))
---
> positions = Buffer(GeometryBasics.metafree(GeometryBasics.coordinates(brain)))
So the performance hit solely stems from the fact that it's a Buffer
of Point
and not a Vector
of Point
Okay, so to summarize the problem and report a possible option for fixing it I start with the stacktrace I looked at by including
Base.getindex(A::$Typ, idx...) = error("hi")
in ShaderAbstractions/src/types.jl
line 63.
From this I got a stacktrace which looked (from highest level to lowest level)
update_boundingbox!(bb_ref, data_limits(plot))
limits_from_transformed_points(iterate_transformed(plot))
points = point_iterator(plot)
point_iterator(plot::Mesh) = point_iterator(plot.mesh[])
Therefore, the computation of data_limits
is the bottleneck by iterating somehow expensively over ShaderAbstractions.Buffer
.
There are in my opinion two fixes (as far as I understand).
ShaderAbstractions.Buffer
need to be improved data_limits
dispatch for ::Mesh
I played around with the second and I came up with a small function that produce a mesh
plot call that does not allocate
function data_limits(plot::Mesh)
xyz = plot.mesh[].position
mini,maxi = extrema(xyz)
return Rect3f(mini, maxi)
end
with data_limits
julia> begin
fig = GLMakie.Figure()
ax = GLMakie.LScene(fig[1, 1]; scenekw=(; limits=GeometryBasics.Rect3f(GeometryBasics.Vec3f(-1), GeometryBasics.Vec3f(2))))
@time meshplot =GLMakie.mesh!(ax, plotter.mesh)
fig
end
0.093828 seconds (15.10 k allocations: 1.110 MiB)
without it
julia> begin
fig = GLMakie.Figure()
ax = GLMakie.LScene(fig[1, 1]; scenekw=(; limits=GeometryBasics.Rect3f(GeometryBasics.Vec3f(-1), GeometryBasics.Vec3f(2))))
@time meshplot =GLMakie.mesh!(ax, plotter.mesh)
fig
end
0.173314 seconds (15.10 k allocations: 283.734 MiB)
If I drop scenekw
with limits
then it behaves faulty in terms of performance
julia> begin
fig = GLMakie.Figure()
ax = GLMakie.LScene(fig[1, 1])
@time meshplot =GLMakie.mesh!(ax, plotter.mesh)
fig
end
0.247098 seconds (17.31 k allocations: 1.328 MiB)
and in terms of camera perspective:
So, my question is: How is data_limits
different to the provided scenekw
. Does it make at all sense to pursue approach 2 or do I miss something fundamentally due to the lack of my Makie knowledge?
Ah nvm, found my problem. Rect3f
needs origin and width, but I assumed that it takes two corners. So, I'd have a solution to it and could make a PR for option two, that basically adds
function data_limits(plot::Mesh)
xyz = plot.mesh[].position
mini,maxi = extrema(xyz)
return Rect3f(mini, maxi-mini)
end
which performs without specifying the limits in the following way:
julia> begin
fig = GLMakie.Figure()
ax = GLMakie.LScene(fig[1, 1])
@time meshplot =GLMakie.mesh!(ax, plotter.mesh)
fig
end
0.189127 seconds (15.13 k allocations: 1.111 MiB)
Btw can this be closed or still a thing?
I think this is still a thing
I don't know what kind of times I should optimally be expecting here. The initial example runs at 3.7ms / 5.6ms for me now, with data_limits/boundingbox using~0.4ms each. A 10 points 3D scatter takes 2.1ms for comparison.
I see some overhead in terms of memory and time to plot for a mesh that contains
ShaderAbstractions.Buffer
The collect call is just to convert it from
Buffer
to the normal triangle type (I'm not sure about the difference betweenGeometryBasics.SimpleFaceView
andGeometryBasics.FaceView