Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
358d0f3
move scene based clearing to RenderStage
ffreyer Jul 18, 2026
585d4b7
add intermediate accumulation buffer
ffreyer Jul 19, 2026
2ea75ea
start reorganizing renders to be per scene groups
ffreyer Jul 19, 2026
aea3aba
use stencil to render front to back
ffreyer Jul 19, 2026
0857803
make scene adding explicit
ffreyer Jul 19, 2026
dd88a55
prototype scene and plot deletion
ffreyer Jul 20, 2026
d33919a
Merge branch 'ff/breaking-0.25' into ff/scene-render-order
ffreyer Jul 20, 2026
cff8a67
actually disconnect scenes in empty!(scene)
ffreyer Jul 20, 2026
3b95a7e
remove old infrastructure
ffreyer Jul 20, 2026
d53e1aa
copy test from #4724
ffreyer Jul 20, 2026
346e9b5
fix tests
ffreyer Jul 21, 2026
190563b
avoid re-running render pipeline
ffreyer Jul 21, 2026
3493797
fix errors
ffreyer Jul 21, 2026
1886cd4
switch benchmark target
ffreyer Jul 22, 2026
07d2e50
update CairoMakie
ffreyer Jul 22, 2026
0dec701
update WGLMakie
ffreyer Jul 22, 2026
994102e
simplify to per-scene grouping
ffreyer Jul 22, 2026
6aa7c7e
Change order of glscenes array to back to front
ffreyer Jul 22, 2026
cf7f746
cleanup
ffreyer Jul 22, 2026
46a1e62
group scenes without clear in CairoMakie
ffreyer Jul 22, 2026
ffbffb6
fix WGLMakie scene deletion?
ffreyer Jul 22, 2026
4fe78dc
fix GLMakie errors
ffreyer Jul 22, 2026
7bfa0f1
improve plot render order a bit, fix errors
ffreyer Jul 23, 2026
b853be6
fix previous scene finding
ffreyer Jul 23, 2026
57ac257
add local zindex ordering
ffreyer Jul 24, 2026
17b2880
add zindex to plots
ffreyer Jul 26, 2026
35fe6b2
add more context information to attribute errors
ffreyer Jul 26, 2026
353641d
avoid unnecessary WeakRef
ffreyer Jul 26, 2026
df806d0
fix pipeline comparison
ffreyer Jul 26, 2026
cc7c164
get rid of plot sorting overhead
ffreyer Jul 26, 2026
7e4f893
fix WGLMakie draw order being inverted and static after init
ffreyer Jul 27, 2026
ce962d2
move Axis decorations to overlay scene, background poly to user scene…
ffreyer Jul 28, 2026
6a5b530
fix error, fix some tests
ffreyer Jul 28, 2026
7d629c8
fix zindex init
ffreyer Jul 28, 2026
5ecb7d5
fix WGLMakie plot render order
ffreyer Jul 29, 2026
8e69c13
revert to scene groups for plot sorting
ffreyer Aug 17, 2026
740d09d
fix merge
ffreyer Aug 17, 2026
28a8193
revert GLMakie to scene groups for scene independent draw order
ffreyer Aug 18, 2026
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
107 changes: 67 additions & 40 deletions CairoMakie/src/plot-primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,88 @@
########################################

# The main entry point into the drawing pipeline
function cairo_draw(screen::Screen, scene::Scene)
function cairo_draw(screen::Screen, root_scene::Scene)
# So animations based on tick events can finish
screen.last_render_time = Makie.next_tick!(
events(scene).tick, Makie.OneTimeRenderTick, screen.creation_time, screen.last_render_time
events(root_scene).tick, Makie.OneTimeRenderTick, screen.creation_time, screen.last_render_time
)

Cairo.save(screen.context)
draw_background(screen, scene)
# collect all scene in back (first) to front (last) order
all_scenes = Makie.collect_scenes(root_scene, skip_invisible = true)

allplots = Makie.collect_atomic_plots(scene; is_atomic_plot = is_cairomakie_atomic_plot)
sort!(allplots; by = Makie.zvalue2d)
# If the backend is not a vector surface (i.e., PNG/ARGB),
# then there is no point in rasterizing twice.
should_rasterize = is_vector_backend(screen.surface)

last_scene = scene
root_height = widths(viewport(Makie.root(root_scene))[])[2]
last_scene = root_scene

Cairo.save(screen.context)
for p in allplots
check_parent_plots(p) do plot
to_value(get(plot, :visible, true))
end || continue
# only prepare for scene when it changes
# this should reduce the number of unnecessary clipping masks etc.
pparent = Makie.parent_scene(p)::Scene
pparent.visible[]::Bool || continue
if pparent != last_scene
Cairo.restore(screen.context)
Cairo.save(screen.context)
prepare_for_scene(screen, pparent)
last_scene = pparent
start_idx = 1
while start_idx <= length(all_scenes)

Cairo.save(screen.context)

# This is expected to be a scene with clear = true but it's probably fine
# if it isn't (i.e. if the root scene doesn't clear)
draw_background(screen, all_scenes[start_idx], root_height)

# Find group of scenes that draw on top of a cleared scene. These may mix
# when depth-sorting
stop_idx = start_idx
while stop_idx < length(all_scenes)
if all_scenes[stop_idx + 1].clear[]
break
end
stop_idx += 1
end
scenes = view(all_scenes, start_idx : stop_idx)
start_idx = stop_idx + 1

# Collect and depth sort all plots within the current scene group
plots = AbstractPlot[]
for scene in scenes
Makie.collect_atomic_plots(scene.plots, plots, is_atomic_plot = is_cairomakie_atomic_plot)
end
sort!(plots; by = Makie.zvalue2d)

Cairo.save(screen.context)
prepare_for_scene(screen, last_scene)

for p in plots
check_parent_plots(p) do plot
to_value(get(plot, :visible, true))
end || continue

# only prepare for scene when it changes
# this should reduce the number of unnecessary clipping masks etc.
pparent = Makie.parent_scene(p)::Scene
pparent.visible[]::Bool || continue
if pparent != last_scene
Cairo.restore(screen.context)
Cairo.save(screen.context)
prepare_for_scene(screen, pparent)
last_scene = pparent
end
Cairo.save(screen.context)

# When a plot is too large to save with a reasonable file size on a vector backend,
# the user can choose to rasterize it when plotting to vector backends, by using the
# `rasterize` keyword argument. This can be set to a Bool or an Int which describes
# the density of rasterization (in terms of a direct scaling factor.)
# TODO: In future, this can also be set to a Tuple{Module, Int} which describes
# the backend module which should be used to render the scene, and the pixel density
# at which it should be rendered.
if to_value(get(p, :rasterize, false)) != false && should_rasterize
draw_plot_as_image(pparent, screen, p, p[:rasterize][])
else # draw vector
draw_plot(pparent, screen, p)
# When a plot is too large to save with a reasonable file size on a vector backend,
# the user can choose to rasterize it when plotting to vector backends, by using the
# `rasterize` keyword argument. This can be set to a Bool or an Int which describes
# the density of rasterization (in terms of a direct scaling factor.)
# TODO: In future, this can also be set to a Tuple{Module, Int} which describes
# the backend module which should be used to render the scene, and the pixel density
# at which it should be rendered.
if to_value(get(p, :rasterize, false)) != false && should_rasterize
draw_plot_as_image(pparent, screen, p, p[:rasterize][])
else # draw vector
draw_plot(pparent, screen, p)
end
Cairo.restore(screen.context)
end
Cairo.restore(screen.context)

end
Cairo.restore(screen.context)

return
end

Expand Down Expand Up @@ -89,7 +122,6 @@ function check_parent_plots(f, scene::Scene)
end

function prepare_for_scene(screen::Screen, scene::Scene)

# get the root area to correct for its size when translating
root_area_height = widths(Makie.root(scene))[2]
scene_area = viewport(scene)[]
Expand All @@ -111,11 +143,6 @@ function prepare_for_scene(screen::Screen, scene::Scene)
return
end

function draw_background(screen::Screen, scene::Scene)
w, h = Makie.widths(viewport(Makie.root(scene))[])
return draw_background(screen, scene, h)
end

function draw_background(screen::Screen, scene::Scene, root_h)
cr = screen.context
Cairo.save(cr)
Expand All @@ -130,7 +157,7 @@ function draw_background(screen::Screen, scene::Scene, root_h)
fill(cr)
end
Cairo.restore(cr)
return foreach(child_scene -> draw_background(screen, child_scene, root_h), scene.children)
return
end

function draw_plot(scene::Scene, screen::Screen, primitive::Plot)
Expand Down
4 changes: 2 additions & 2 deletions CairoMakie/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ end
# are modified in which case the numbers should just be updated
f, a, p = scatter(rand(10))
colorbuffer(f)
@test length(p.attributes.inputs) == 37
@test length(p.attributes.outputs) == 83
@test length(p.attributes.inputs) == 38
@test length(p.attributes.outputs) == 84
end

excludes = Set(
Expand Down
16 changes: 16 additions & 0 deletions GLMakie/src/GLAbstraction/GLFramebuffer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ mutable struct GLFramebuffer

return obj
end

function GLFramebuffer(::Nothing)
return new(
0, (0, 0), nothing,
Dict{Symbol, Int}(), GLenum[], Texture[], UInt32(0)
)
end
end

function bind(fb::GLFramebuffer, target = fb.id)
Expand Down Expand Up @@ -73,6 +80,15 @@ function unsafe_free(x::GLFramebuffer)
return
end

function Base.empty!(fb::GLFramebuffer)
fb.size = (0, 0)
empty!(fb.name2idx)
empty!(fb.attachments)
empty!(fb.buffers)
fb.counter = 0
return
end

Base.size(fb::GLFramebuffer) = fb.size
Base.haskey(fb::GLFramebuffer, key::Symbol) = haskey(fb.name2idx, key)

Expand Down
8 changes: 5 additions & 3 deletions GLMakie/src/GLAbstraction/RenderObject.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mutable struct RenderObject{IndexType, InstanceType}
context # OpenGL context
id::UInt32
visible::Bool
zindex::Float64

# data of the renderobject
buffers::Dict{Symbol, GLBuffer}
Expand All @@ -41,7 +42,7 @@ mutable struct RenderObject{IndexType, InstanceType}
observables::Vector{Observable} # for clean up

function RenderObject(
context, visible,
context, visible, zindex,
buffers::Dict{Symbol, GLBuffer},
indices::IndexType,
instances::InstanceType,
Expand All @@ -58,7 +59,7 @@ mutable struct RenderObject{IndexType, InstanceType}
# and since this is a UUID, it shouldn't matter
id = pack_bool(RENDER_OBJECT_ID_COUNTER[], fxaa)
robj = new{IndexType, InstanceType}(
context, id, to_value(visible),
context, id, to_value(visible), zindex,
buffers, indices, instances, primitive,
uniforms,
Dict{Symbol, RenderInstructions}(),
Expand Down Expand Up @@ -125,6 +126,7 @@ function RenderObject(context, data::Dict{Symbol, Any})

# Not handled as uniform
visible = pop!(data, :visible, true)
zindex = pop!(data, :zindex, 0.0)
@assert !isa(visible, Observable) "No more of this!"

# for clean up on deletion
Expand Down Expand Up @@ -196,7 +198,7 @@ function RenderObject(context, data::Dict{Symbol, Any})
cleanup = [:indices, :doc_string] # maybe also: overdraw, transparency, ssao, shading?
foreach(key -> pop!(data, key, nothing), cleanup)

robj = RenderObject(context, visible, buffers, indices, instances, primitive, data, observables)
robj = RenderObject(context, visible, zindex, buffers, indices, instances, primitive, data, observables)

# automatically integrate object ID, will be discarded if shader doesn't use it
robj[:objectid] = robj.id
Expand Down
1 change: 1 addition & 0 deletions GLMakie/src/GLMakie.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ using Makie: ClosedInterval, (..)
using Makie: to_native
using Makie: spaces, is_data_space, is_pixel_space, is_relative_space, is_clip_space
using Makie: BudgetedTimer, reset!
using Makie: insert_scene!
import Makie: to_font, el32convert, Shape, CIRCLE, RECTANGLE, ROUNDED_RECTANGLE, DISTANCEFIELD, TRIANGLE

using ShaderAbstractions
Expand Down
Loading
Loading