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

Add tick event #3948

Merged
merged 42 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2aa7a26
initial Makie setup
ffreyer Jun 7, 2024
b16bc89
update CairoMakie
ffreyer Jun 7, 2024
8f5a021
track delta time and adjust states
ffreyer Jun 8, 2024
7e32659
implement tick events in GLMakie
ffreyer Jun 8, 2024
8a0de3a
implement ticks in WGLMakie
ffreyer Jun 8, 2024
5f6d59b
track frame_delta_time in record
ffreyer Jun 8, 2024
362ab8a
update some comments
ffreyer Jun 8, 2024
ae21c8d
Merge branch 'master' into ff/render_tick
ffreyer Jun 8, 2024
ac5d092
cleanup CairoMakie
ffreyer Jun 9, 2024
d7bc606
cleanup WGLMakie
ffreyer Jun 9, 2024
9243f0e
add refimg test for record
ffreyer Jun 9, 2024
c3af54a
add frame count, time since start, skip event ticks
ffreyer Jun 10, 2024
27aa39b
update test
ffreyer Jun 10, 2024
d150377
update docs
ffreyer Jun 10, 2024
b384eb8
update changelog
ffreyer Jun 10, 2024
3782e38
add more tests
ffreyer Jun 10, 2024
214e28e
try more time
ffreyer Jun 10, 2024
1f45d66
make record ticks match set framerate
ffreyer Jun 11, 2024
e74d1b8
Merge branch 'master' into ff/render_tick
ffreyer Jun 18, 2024
9d43f93
add safeguard
ffreyer Jun 18, 2024
f787ef2
try stabilize normals of cat
ffreyer Jun 18, 2024
5cdeb78
Merge branch 'ff/render_tick' of https://github.com/MakieOrg/Makie.jl…
ffreyer Jun 18, 2024
efa3a98
try fix tests
ffreyer Jun 18, 2024
5fc9c73
try fix tests
ffreyer Jun 19, 2024
6adafd5
drop docs ref to enum to avoid docs failure
ffreyer Jun 19, 2024
8bd331b
add more documentation
ffreyer Jun 19, 2024
fefaac7
add tick safeguards to record()
ffreyer Jun 19, 2024
649195c
improve WGLMakie record suggestion & general formating
ffreyer Jun 19, 2024
6a917dd
move tick filtering to VideoStream
ffreyer Jun 19, 2024
c582a50
update docs
ffreyer Jun 19, 2024
bf50649
Merge branch 'master' into ff/render_tick
ffreyer Jun 19, 2024
7df0fa0
Merge branch 'master' into ff/render_tick
ffreyer Jun 19, 2024
278a0ee
move tick generating functor to Makie
ffreyer Jun 21, 2024
3227808
run WGLMakie ticks on an independent clock
ffreyer Jun 21, 2024
3ceaf2e
Merge branch 'ff/render_tick' of https://github.com/MakieOrg/Makie.jl…
ffreyer Jun 21, 2024
e3f4bf2
fix missing namespace
ffreyer Jun 21, 2024
33c5308
Merge branch 'master' into ff/render_tick
ffreyer Jul 25, 2024
5ef7e33
use BudgetedTimer for WGLMakie
ffreyer Jul 25, 2024
fbd6783
Merge branch 'master' into ff/render_tick
SimonDanisch Aug 8, 2024
8f8769c
Merge branch 'master' into ff/render_tick
SimonDanisch Aug 8, 2024
907f69a
Merge branch 'master' into ff/render_tick
ffreyer Aug 9, 2024
e1a928d
Merge branch 'master' into ff/render_tick
ffreyer Aug 9, 2024
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## [Unreleased]

- Added `events.tick` to allow linking actions like animations to the renderloop. [#3948](https://github.com/MakieOrg/Makie.jl/pull/3948)

## [0.21.3] - 2024-06-17

- Fix stack overflows when using `markerspace = :data` with `scatter` [#3960](https://github.com/MakieOrg/Makie.jl/issues/3960).
Expand Down
33 changes: 33 additions & 0 deletions CairoMakie/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,36 @@ end

@test_throws ArgumentError save(filename, Figure(), pdf_version="foo")
end

@testset "Tick Events" begin
f, a, p = scatter(rand(10));
@test events(f).tick[] == Makie.Tick()

filename = "$(tempname()).png"
try
save(filename, f)
tick = events(f).tick[]
@test tick.state == Makie.OneTimeRenderTick
@test tick.count == 0
@test tick.time == 0.0
@test tick.delta_time == 0.0
finally
rm(filename)
end

filename = "$(tempname()).mp4"
try
tick_record = Makie.Tick[]
on(tick -> push!(tick_record, tick), events(f).tick)
record(_ -> nothing, f, filename, 1:10, framerate = 30)
dt = 1.0 / 30.0
for (i, tick) in enumerate(tick_record)
@test tick.state == Makie.OneTimeRenderTick
@test tick.count == i
@test tick.time ≈ dt * i
@test tick.delta_time ≈ dt
end
finally
rm(filename)
end
end
2 changes: 1 addition & 1 deletion GLMakie/src/display.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function Base.display(screen::Screen, scene::Scene; connect=true)
else
@assert screen.root_scene === scene "internal error. Scene already displayed by screen but not as root scene"
end
pollevents(screen)
pollevents(screen, Makie.BackendTick)
return screen
end

Expand Down
8 changes: 4 additions & 4 deletions GLMakie/src/drawing_primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function handle_lights(attr::Dict, screen::Screen, lights::Vector{Makie.Abstract
attr[:light_colors] = Observable(sizehint!(RGBf[], MAX_LIGHTS))
attr[:light_parameters] = Observable(sizehint!(Float32[], MAX_PARAMS))

on(screen.render_tick, priority = typemin(Int)) do _
on(screen.render_tick, priority = -1000) do _
# derive number of lights from available lights. Both MAX_LIGHTS and
# MAX_PARAMS are considered for this.
n_lights = 0
Expand Down Expand Up @@ -231,7 +231,7 @@ const EXCLUDE_KEYS = Set([:transformation, :tickranges, :ticklabels, :raw, :SSAO

function cached_robj!(robj_func, screen, scene, plot::AbstractPlot)
# poll inside functions to make wait on compile less prominent
pollevents(screen)
pollevents(screen, Makie.BackendTick)
robj = get!(screen.cache, objectid(plot)) do

filtered = filter(plot.attributes) do (k, v)
Expand Down Expand Up @@ -318,13 +318,13 @@ Base.insert!(::GLMakie.Screen, ::Scene, ::Makie.PlotList) = nothing
function Base.insert!(screen::Screen, scene::Scene, @nospecialize(x::Plot))
ShaderAbstractions.switch_context!(screen.glscreen)
# poll inside functions to make wait on compile less prominent
pollevents(screen)
pollevents(screen, Makie.BackendTick)
if isempty(x.plots) # if no plots inserted, this truly is an atomic
draw_atomic(screen, scene, x)
else
foreach(x.plots) do x
# poll inside functions to make wait on compile less prominent
pollevents(screen)
pollevents(screen, Makie.BackendTick)
insert!(screen, scene, x)
end
end
Expand Down
28 changes: 27 additions & 1 deletion GLMakie/src/events.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ struct MousePositionUpdater
hasfocus::Observable{Bool}
end

function (p::MousePositionUpdater)(::Nothing)
function (p::MousePositionUpdater)(::Makie.TickState)
!p.hasfocus[] && return
nw = to_native(p.screen)
x, y = GLFW.GetCursorPos(nw)
Expand Down Expand Up @@ -294,3 +294,29 @@ end
function Makie.disconnect!(window::GLFW.Window, ::typeof(entered_window))
GLFW.SetCursorEnterCallback(window, nothing)
end

# Just for finding the relevant listener
mutable struct TickCallback
event::Observable{Makie.Tick}
start_time::UInt64
last_time::UInt64
TickCallback(tick::Observable{Makie.Tick}) = new(tick, time_ns(), time_ns())
end

function (cb::TickCallback)(x::Makie.TickState)
if x > Makie.UnknownTickState # not backend or Unknown
cb.last_time = Makie.next_tick!(cb.event, x, cb.start_time, cb.last_time)
end
return nothing
end

function Makie.frame_tick(scene::Scene, screen::Screen)
# Separating screen ticks from event ticks allows us to sanitize:
# Internal on-tick event updates happen first (mouseposition), no blocking
# listeners, set order
on(TickCallback(scene.events.tick), scene, screen.render_tick, priority = typemin(Int))
end
function Makie.disconnect!(screen::Screen, ::typeof(Makie.frame_tick))
connections = filter(x -> x[2] isa TickCallback, screen.render_tick.listeners)
foreach(x -> off(screen.render_tick, x[2]), connections)
end
27 changes: 15 additions & 12 deletions GLMakie/src/screen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ mutable struct Screen{GLWindow} <: MakieScreen
cache::Dict{UInt64, RenderObject}
cache2plot::Dict{UInt32, AbstractPlot}
framecache::Matrix{RGB{N0f8}}
render_tick::Observable{Nothing} # listeners must not Consume(true)
render_tick::Observable{Makie.TickState} # listeners must not Consume(true)
window_open::Observable{Bool}
scalefactor::Observable{Float32}

Expand Down Expand Up @@ -205,7 +205,7 @@ mutable struct Screen{GLWindow} <: MakieScreen
config, stop_renderloop, rendertask,
Observable(0f0), screen2scene,
screens, renderlist, postprocessors, cache, cache2plot,
Matrix{RGB{N0f8}}(undef, s), Observable(nothing),
Matrix{RGB{N0f8}}(undef, s), Observable(Makie.UnknownTickState),
Observable(true), Observable(0f0), nothing, reuse, true, false
)
push!(ALL_SCREENS, screen) # track all created screens
Expand Down Expand Up @@ -444,10 +444,10 @@ function Screen(scene::Scene, config::ScreenConfig, ::Makie.ImageStorageFormat;
return screen
end

function pollevents(screen::Screen)
function pollevents(screen::Screen, frame_state::Makie.TickState)
ShaderAbstractions.switch_context!(screen.glscreen)
GLFW.PollEvents()
notify(screen.render_tick)
screen.render_tick[] = frame_state
return
end

Expand Down Expand Up @@ -727,7 +727,7 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma
ctex = screen.framebuffer.buffers[:color]
# polling may change window size, when its bigger than monitor!
# we still need to poll though, to get all the newest events!
pollevents(screen)
pollevents(screen, Makie.BackendTick)
# keep current buffer size to allows larger-than-window renders
render_frame(screen, resize_buffers=false) # let it render
if screen.config.visible
Expand Down Expand Up @@ -860,7 +860,7 @@ function set_framerate!(screen::Screen, fps=30)
end

function refreshwindowcb(screen, window)
screen.render_tick[] = nothing
screen.render_tick[] = Makie.BackendTick
render_frame(screen)
GLFW.SwapBuffers(window)
return
Expand All @@ -886,14 +886,13 @@ end
scalechangeobs(screen) = scalefactor -> scalechangeobs(screen, scalefactor)


# TODO add render_tick event to scene events
function vsynced_renderloop(screen)
while isopen(screen) && !screen.stop_renderloop
if screen.config.pause_renderloop
pollevents(screen); sleep(0.1)
pollevents(screen, Makie.PausedRenderTick); sleep(0.1)
continue
end
pollevents(screen) # GLFW poll
pollevents(screen, Makie.RegularRenderTick) # GLFW poll
render_frame(screen)
GLFW.SwapBuffers(to_native(screen))
yield()
Expand All @@ -903,12 +902,12 @@ end
function fps_renderloop(screen::Screen)
while isopen(screen) && !screen.stop_renderloop
if screen.config.pause_renderloop
pollevents(screen); sleep(0.1)
pollevents(screen, Makie.PausedRenderTick); sleep(0.1)
continue
end
time_per_frame = 1.0 / screen.config.framerate
t = time_ns()
pollevents(screen) # GLFW poll
pollevents(screen, Makie.RegularRenderTick) # GLFW poll
render_frame(screen)
GLFW.SwapBuffers(to_native(screen))
t_elapsed = (time_ns() - t) / 1e9
Expand All @@ -931,14 +930,18 @@ function requires_update(screen::Screen)
end

function on_demand_renderloop(screen::Screen)
tick_state = Makie.UnknownTickState
while isopen(screen) && !screen.stop_renderloop
t = time_ns()
time_per_frame = 1.0 / screen.config.framerate
pollevents(screen) # GLFW poll
pollevents(screen, tick_state) # GLFW poll

if !screen.config.pause_renderloop && requires_update(screen)
tick_state = Makie.RegularRenderTick
render_frame(screen)
GLFW.SwapBuffers(to_native(screen))
else
tick_state = ifelse(screen.config.pause_renderloop, Makie.PausedRenderTick, Makie.SkippedRenderTick)
end

t_elapsed = (time_ns() - t) / 1e9
Expand Down
78 changes: 78 additions & 0 deletions GLMakie/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,81 @@ include("unit_tests.jl")
GLMakie.closeall()
GC.gc(true) # make sure no finalizers act up!
end

@testset "Tick Events" begin
function check_tick(tick, state, count)
@test tick.state == state
@test tick.count == count
@test tick.time > 1e-9
@test tick.delta_time > 1e-9
end

f, a, p = scatter(rand(10));
@test events(f).tick[] == Makie.Tick()

filename = "$(tempname()).png"
try
save(filename, f)
tick = events(f).tick[]
@test tick.state == Makie.OneTimeRenderTick
@test tick.count == 0
@test tick.time == 0.0
@test tick.delta_time == 0.0
finally
rm(filename)
end

filename = "$(tempname()).mp4"
try
tick_record = Makie.Tick[]
on(tick -> push!(tick_record, tick), events(f).tick)
record(_ -> nothing, f, filename, 1:10, framerate = 30)
dt = 1.0 / 30.0

i = 1
for tick in tick_record
if tick.state == Makie.OneTimeRenderTick
@test tick.count == i
@test tick.time ≈ dt * i
@test tick.delta_time ≈ dt
i += 1
end
end
finally
rm(filename)
end

GLMakie.closeall()

f, a, p = scatter(rand(10));
tick_record = Makie.Tick[]
on(t -> push!(tick_record, t), events(f).tick)
screen = GLMakie.Screen(render_on_demand = true, framerate = 30.0, pause_rendering = false, visible = false)
display(screen, f.scene)
sleep(0.15)
GLMakie.pause_renderloop!(screen)
sleep(0.1)
GLMakie.closeall()

# Why does it start with a skipped tick?
i = 1
while tick_record[i].state == Makie.SkippedRenderTick
check_tick(tick_record[1], Makie.SkippedRenderTick, i)
i += 1
end

check_tick(tick_record[i], Makie.RegularRenderTick, i)
i += 1

while tick_record[i].state == Makie.SkippedRenderTick
check_tick(tick_record[i], Makie.SkippedRenderTick, i)
i += 1
end

while (i <= length(tick_record)) && (tick_record[i].state == Makie.PausedRenderTick)
check_tick(tick_record[i], Makie.PausedRenderTick, i)
i += 1
end

@test i == length(tick_record)+1
end
5 changes: 3 additions & 2 deletions ReferenceTests/src/tests/examples3d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -424,12 +424,13 @@ end

@reference_test "Normals of a Cat" begin
x = loadasset("cat.obj")
mesh(x, color=:black)
f, a, p = mesh(x, color=:black)
pos = map(decompose(Point3f, x), GeometryBasics.normals(x)) do p, n
p => p .+ Point(normalize(n) .* 0.05f0)
end
linesegments!(pos, color=:blue)
current_figure()
Makie.update_state_before_display!(f)
f
end

@reference_test "Sphere Mesh" begin
Expand Down
19 changes: 19 additions & 0 deletions ReferenceTests/src/tests/updating.jl
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,22 @@ end
ax.title = "identity"
Makie.step!(st)
end


@reference_test "event ticks in record" begin
# Checks whether record calculates and triggers event.tick by drawing a
# Point at y = 1 for each frame where it does. The animation is irrelevant
# here, so we can just check the final image.
# The first point maybe at 0 depending on when the backend sets up it's
# reference time
ps = Observable(Point2f[])
f, a, p = scatter(ps)
xlims!(a, 0, 61)
ylims!(a, -0.1, 1.1)
Record(f, 1:60, framerate = 30) do i
push!(ps.val, Point2f(i, f.scene.events.tick[].delta_time > 1e-6))
notify(ps)
f.scene.events.tick[] = Makie.Tick(Makie.UnknownTickState, 0, 0.0, 0.0)
end
f
end
4 changes: 3 additions & 1 deletion WGLMakie/src/display.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ mutable struct Screen <: Makie.MakieScreen
displayed_scenes::Set{String}
config::ScreenConfig
canvas::Union{Nothing,Bonito.HTMLElement}
start_time::UInt64
last_time::UInt64
function Screen(scene::Union{Nothing,Scene}, config::ScreenConfig)
return new(Channel{Bool}(1), nothing, scene, Set{String}(), config, nothing)
return new(Channel{Bool}(1), nothing, scene, Set{String}(), config, nothing, time_ns(), time_ns())
end
end

Expand Down
7 changes: 6 additions & 1 deletion WGLMakie/src/events.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function code_to_keyboard(code::String)
end
end

function connect_scene_events!(scene::Scene, comm::Observable)
function connect_scene_events!(screen::Screen, scene::Scene, comm::Observable)
e = events(scene)
on(comm) do msg
@async try
Expand Down Expand Up @@ -105,10 +105,15 @@ function connect_scene_events!(scene::Scene, comm::Observable)
@handle msg.resize begin
resize!(scene, tuple(resize...))
end
@handle msg.tick begin
screen.last_time = Makie.next_tick!(
e.tick, Makie.RegularRenderTick, screen.start_time, screen.last_time)
end
catch err
@warn "Error in window event callback" exception=(err, Base.catch_backtrace())
end
return
end

return
end
2 changes: 1 addition & 1 deletion WGLMakie/src/three_plot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ function three_display(screen::Screen, session::Session, scene::Scene)
on(session, done_init) do val
window_open[] = true
end
connect_scene_events!(scene, comm)
connect_scene_events!(screen, scene, comm)
return wrapper, done_init
end
Loading
Loading