From a5d8e8461348e61a92427b344c52946136e0d2d7 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 31 May 2022 23:09:26 +0200 Subject: [PATCH 01/56] Only rasterize if the backend is not itself raster --- CairoMakie/src/infrastructure.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index d9db6a40631..bfef309eb2e 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -161,6 +161,10 @@ function cairo_draw(screen::CairoScreen, scene::Scene) zvals = Makie.zvalue2d.(allplots) permute!(allplots, sortperm(zvals)) + # 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 Cairo.save(screen.context) @@ -182,7 +186,7 @@ function cairo_draw(screen::CairoScreen, scene::Scene) # 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.) - if to_value(get(p, :rasterize, false)) != false + 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) From 88091a9bd82adb50421078242cd113c8f8006890 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Wed, 1 Jun 2022 00:12:18 +0200 Subject: [PATCH 02/56] Define width of CairoScreen --- CairoMakie/src/infrastructure.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index bfef309eb2e..69cbbed5ca1 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -128,6 +128,8 @@ function CairoScreen(scene::Scene, path::Union{String, IO}, mode::Symbol; device return CairoScreen(scene, surf, ctx, nothing) end +GeometryBasics.widths(screen::CairoScreen) = round.(Int, (screen.surface.width, screen.surface.height)) + function Base.delete!(screen::CairoScreen, scene::Scene, plot::AbstractPlot) # Currently, we rerender every time, so nothing needs From 54672644223cb6e07d11d4044cb29641ca668f1b Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Wed, 1 Jun 2022 00:15:03 +0200 Subject: [PATCH 03/56] Make colorbuffer use the size of the screen, not the scene This allows arbitrary scaling in CairoMakie, remains constant in GLMakie, and probably breaks WGLMakie. --- CairoMakie/src/infrastructure.jl | 11 +++++++++-- src/display.jl | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index 69cbbed5ca1..472e70fbf96 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -409,15 +409,22 @@ function Makie.colorbuffer(screen::CairoScreen) # extract scene scene = screen.scene # get resolution - w, h = size(scene) + w, h = GeometryBasics.widths(screen) + scene_w, scene_h = size(scene) + @assert w/scene_w ≈ h/scene_h + + device_scaling_factor = w/scene_w # preallocate an image matrix img = Matrix{ARGB32}(undef, w, h) # create an image surface to draw onto the image surf = Cairo.CairoImageSurface(img) + ccall((:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), + surf.ptr, device_scaling_factor, device_scaling_factor) + # draw the scene onto the image matrix ctx = Cairo.CairoContext(surf) ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, 2.0) - + scr = CairoScreen(scene, surf, ctx, nothing) cairo_draw(scr, scene) diff --git a/src/display.jl b/src/display.jl index e3b2d3759b7..8752122c233 100644 --- a/src/display.jl +++ b/src/display.jl @@ -328,7 +328,8 @@ function VideoStream(scene::Scene; framerate::Integer = 24) path = joinpath(dir, "$(gensym(:video)).mkv") screen = backend_display(current_backend[], scene) push_screen!(scene, screen) - _xdim, _ydim = size(scene) + + _xdim, _ydim = GeometryBasics.widths(screen) xdim = iseven(_xdim) ? _xdim : _xdim + 1 ydim = iseven(_ydim) ? _ydim : _ydim + 1 process = @ffmpeg_env open(`$(FFMPEG.ffmpeg) -framerate $(framerate) -loglevel quiet -f rawvideo -pixel_format rgb24 -r $framerate -s:v $(xdim)x$(ydim) -i pipe:0 -vf vflip -y $path`, "w") From b561377339bc9279d036d61e738245dc5a934375 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Wed, 22 Jun 2022 01:58:25 +0800 Subject: [PATCH 04/56] Add GeometryBasics.widths for ThreeDisplay --- WGLMakie/src/three_plot.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/WGLMakie/src/three_plot.jl b/WGLMakie/src/three_plot.jl index 29fee1250c6..2f5e43d21aa 100644 --- a/WGLMakie/src/three_plot.jl +++ b/WGLMakie/src/three_plot.jl @@ -4,6 +4,14 @@ end JSServe.session(td::ThreeDisplay) = td.session +function GeometryBasics.widths(screen::ThreeDisplay) + # look at d.qs().clientWidth for displayed width + width = Int(WGLMakie.JSServe.evaljs_value(screen.session, WGLMakie.JSServe.js"document.querySelector('canvas').width"; time_out=100)) + height = Int(WGLMakie.JSServe.evaljs_value(screen.session, WGLMakie.JSServe.js"document.querySelector('canvas').height"; time_out=100)) + + return (width, height) +end + # We use objectid to find objects on the js side js_uuid(object) = string(objectid(object)) From 241128eaf4f1157277002324cbd04f85be168ad1 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Wed, 22 Jun 2022 02:00:15 +0800 Subject: [PATCH 05/56] Add GeometryBasics.widths for WebDisplay --- WGLMakie/src/display.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WGLMakie/src/display.jl b/WGLMakie/src/display.jl index b2eedc7880a..48f7c5de8cc 100644 --- a/WGLMakie/src/display.jl +++ b/WGLMakie/src/display.jl @@ -71,6 +71,8 @@ struct WebDisplay <: Makie.AbstractScreen three::Base.RefValue{ThreeDisplay} display::Any end + +GeometryBasics.widths(screen::WebDisplay) = GeometryBasics.widths(screen.three[]) function Makie.backend_display(::WGLBackend, scene::Scene; kw...) # Reference to three object which gets set once we serve this to a browser From ff7edcf5d024b4058697cf54ed61556011cb2954 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sun, 10 Jul 2022 18:56:59 +0530 Subject: [PATCH 06/56] Fix inadvertent bug --- src/display.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/display.jl b/src/display.jl index 95e000c2afc..6f631be6494 100644 --- a/src/display.jl +++ b/src/display.jl @@ -333,6 +333,7 @@ function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, conn #codec = `-codec:v libvpx -quality good -cpu-used 0 -b:v 500k -qmin 10 -qmax 42 -maxrate 500k -bufsize 1000k -threads 8` dir = mktempdir() path = joinpath(dir, "$(gensym(:video)).mkv") + scene = get_scene(fig) screen = backend_display(scene; start_renderloop=false, visible=visible, connect=connect) push_screen!(scene, screen) From c2082fcd9722e4f9625f091a2fa4846556e57e48 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Mon, 11 Jul 2022 15:55:27 +0530 Subject: [PATCH 07/56] Add news entry --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index a611f37cd32..bacdb1e4303 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ - Fixed regression where `Block` alignments could not be specified as numbers anymore [#2108](https://github.com/JuliaPlots/Makie.jl/pull/2108). - Added the option to show mirrored ticks on the other side of an Axis using the attributes `xticksmirrored` and `yticksmirrored` [#2105](https://github.com/JuliaPlots/Makie.jl/pull/2105). - Fixed a bug where a set of `Axis` wouldn't be correctly linked together if they were only linked in pairs instead of all at the same time [#2116](https://github.com/JuliaPlots/Makie.jl/pull/2116). +- Recording and colorbuffers use screen size instead of scene size, allowing e.g. the `px_per_unit` argument in CairoMakie to take effect. It is sufficient to set the global `px_per_unit` setting by calling `CairoMakie.activate!(px_per_unit=3)` or whichever scaling factor is desired. GLMakie and WGLMakie are currently unaffected by this. [#2012](https://github.com/JuliaPlots/Makie.jl/pull/2116). ## v0.17.7 From 713a8d6ea21f262a74a639e4d26c7a433118786e Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Mon, 11 Jul 2022 22:09:08 +0530 Subject: [PATCH 08/56] Decrease the number of evaljs_value calls --- WGLMakie/src/three_plot.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WGLMakie/src/three_plot.jl b/WGLMakie/src/three_plot.jl index 2f5e43d21aa..7ab5d35a16c 100644 --- a/WGLMakie/src/three_plot.jl +++ b/WGLMakie/src/three_plot.jl @@ -6,8 +6,7 @@ JSServe.session(td::ThreeDisplay) = td.session function GeometryBasics.widths(screen::ThreeDisplay) # look at d.qs().clientWidth for displayed width - width = Int(WGLMakie.JSServe.evaljs_value(screen.session, WGLMakie.JSServe.js"document.querySelector('canvas').width"; time_out=100)) - height = Int(WGLMakie.JSServe.evaljs_value(screen.session, WGLMakie.JSServe.js"document.querySelector('canvas').height"; time_out=100)) + width, height = Int(WGLMakie.JSServe.evaljs_value(screen.session, WGLMakie.JSServe.js"[document.querySelector('canvas').width, document.querySelector('canvas').height]"; time_out=100)) return (width, height) end From 96ee55b45689e2b7d5bf91cc966a722cae77ee87 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 12 Jul 2022 06:49:52 +0530 Subject: [PATCH 09/56] broadcast in WGLMakie method --- WGLMakie/src/three_plot.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WGLMakie/src/three_plot.jl b/WGLMakie/src/three_plot.jl index 7ab5d35a16c..599e0153866 100644 --- a/WGLMakie/src/three_plot.jl +++ b/WGLMakie/src/three_plot.jl @@ -6,7 +6,7 @@ JSServe.session(td::ThreeDisplay) = td.session function GeometryBasics.widths(screen::ThreeDisplay) # look at d.qs().clientWidth for displayed width - width, height = Int(WGLMakie.JSServe.evaljs_value(screen.session, WGLMakie.JSServe.js"[document.querySelector('canvas').width, document.querySelector('canvas').height]"; time_out=100)) + width, height = round.(Int, WGLMakie.JSServe.evaljs_value(screen.session, WGLMakie.JSServe.js"[document.querySelector('canvas').width, document.querySelector('canvas').height]"; time_out=100)) return (width, height) end From 14e45a629cb736201af8df4d87f0fa1e9a8f09f0 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 12 Jul 2022 16:56:05 +0530 Subject: [PATCH 10/56] Pass kwargs along to backend_show in backend_display for CairoMakie --- CairoMakie/src/infrastructure.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index 0db6d3c2591..6f099d3a736 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -315,7 +315,7 @@ end function Makie.backend_display(x::CairoBackend, scene::Scene; kw...) return open(x.path, "w") do io - Makie.backend_show(x, io, to_mime(x), scene) + Makie.backend_show(x, IOContext(io, kw), to_mime(x), scene) end end From 16b7409bdfe1e4dd62eee6ff41d3ef3ef65bf677 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 12 Jul 2022 16:59:41 +0530 Subject: [PATCH 11/56] Pass arbitrary kwargs along --- src/display.jl | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/display.jl b/src/display.jl index 6f631be6494..69db00adc8f 100644 --- a/src/display.jl +++ b/src/display.jl @@ -320,7 +320,7 @@ struct VideoStream end """ - VideoStream(scene::Scene; framerate = 24, visible=false, connect=false) + VideoStream(scene::Scene; framerate = 24, visible=false, connect=false, backend_kw...) Returns a stream and a buffer that you can use, which don't allocate for new frames. Use [`recordframe!(stream)`](@ref) to add new video frames to the stream, and @@ -329,12 +329,12 @@ Use [`recordframe!(stream)`](@ref) to add new video frames to the stream, and * visible=false: make window visible or not * connect=false: connect window events or not """ -function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, connect=false) +function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, connect=false, backend_kw...) #codec = `-codec:v libvpx -quality good -cpu-used 0 -b:v 500k -qmin 10 -qmax 42 -maxrate 500k -bufsize 1000k -threads 8` dir = mktempdir() path = joinpath(dir, "$(gensym(:video)).mkv") scene = get_scene(fig) - screen = backend_display(scene; start_renderloop=false, visible=visible, connect=connect) + screen = backend_display(scene; start_renderloop=false, visible=visible, connect=connect, backend_kw...) push_screen!(scene, screen) _xdim, _ydim = GeometryBasics.widths(screen) @@ -465,7 +465,8 @@ If you want a simpler interface, consider using [`record`](@ref). function save( path::String, io::VideoStream; framerate::Int = 24, compression = 20, profile = "high422", - pixel_format = profile == "high444" ? "yuv444p" : "yuv420p" + pixel_format = profile == "high444" ? "yuv444p" : "yuv420p", + kwargs... ) close(io.process) @@ -579,24 +580,24 @@ end only applies to `.mp4`. Defaults to `yuv444p` for `profile = high444`. """ -function record(func, scene, path; framerate::Int = 24, kwargs...) - io = Record(func, scene, framerate = framerate) - save(path, io, framerate = framerate; kwargs...) +function record(func, scene, path; framerate::Int = 24, backend_kw...) + io = Record(func, scene, framerate = framerate, backend_kw...) + save(path, io, framerate = framerate; backend_kw...) end -function Record(func, scene; framerate=24) - io = VideoStream(scene; framerate = framerate) +function Record(func, scene; framerate=24, backend_kw...) + io = VideoStream(scene; framerate = framerate, backend_kw...) func(io) return io end -function record(func, scene, path, iter; framerate::Int = 24, kwargs...) - io = Record(func, scene, iter; framerate=framerate) - save(path, io, framerate = framerate; kwargs...) +function record(func, scene, path, iter; framerate::Int = 24, backend_kw...) + io = Record(func, scene, iter; framerate=framerate, backend_kw...) + save(path, io, framerate = framerate; backend_kw...) end -function Record(func, scene, iter; framerate::Int = 24) - io = VideoStream(scene; framerate=framerate) +function Record(func, scene, iter; framerate::Int = 24, backend_kw...) + io = VideoStream(scene; framerate=framerate, backend_kw...) for i in iter func(i) recordframe!(io) From 521b4b038140fd083dcd5b01efe3be5af7b1aae3 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Wed, 13 Jul 2022 08:26:07 +0530 Subject: [PATCH 12/56] convert Iterators.Pairs to namedtuple in backend_display for CairoMakie --- CairoMakie/src/infrastructure.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index 6f099d3a736..81f76112f0c 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -315,7 +315,7 @@ end function Makie.backend_display(x::CairoBackend, scene::Scene; kw...) return open(x.path, "w") do io - Makie.backend_show(x, IOContext(io, kw), to_mime(x), scene) + Makie.backend_show(x, IOContext(io, (; kw...)), to_mime(x), scene) end end From 7f4652ff25d0361d5c08cfa8f1cb189235436af5 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Wed, 13 Jul 2022 10:35:48 +0530 Subject: [PATCH 13/56] Splat kwargs as a vector of pairs --- CairoMakie/src/infrastructure.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index 81f76112f0c..6118b1e7a41 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -315,7 +315,7 @@ end function Makie.backend_display(x::CairoBackend, scene::Scene; kw...) return open(x.path, "w") do io - Makie.backend_show(x, IOContext(io, (; kw...)), to_mime(x), scene) + Makie.backend_show(x, IOContext(io, collect(kw)...), to_mime(x), scene) end end From da036f4ce8177421a2abcdda9dce909d9c2adcb0 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Mon, 26 Sep 2022 10:10:56 +0200 Subject: [PATCH 14/56] refactor display code --- .gitignore | 1 - CairoMakie/README.md | 2 +- CairoMakie/src/CairoMakie.jl | 19 --- CairoMakie/src/display.jl | 62 +++++++ CairoMakie/src/infrastructure.jl | 259 +--------------------------- CairoMakie/src/overrides.jl | 20 +-- CairoMakie/src/primitives.jl | 14 +- CairoMakie/src/screen.jl | 159 ++++++++++++++++++ GLMakie/src/GLMakie.jl | 11 +- RPRMakie/src/RPRMakie.jl | 5 +- WGLMakie/src/WGLMakie.jl | 16 +- src/Makie.jl | 1 + src/display.jl | 279 +++++++++++++------------------ src/event-recorder.jl | 60 +++++++ 14 files changed, 429 insertions(+), 479 deletions(-) create mode 100644 CairoMakie/src/display.jl create mode 100644 CairoMakie/src/screen.jl create mode 100644 src/event-recorder.jl diff --git a/.gitignore b/.gitignore index 1df708581d4..413c3a4b6d8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ Manifest.toml /build \.DS_Store -CairoMakie/src/display.* WGLMakie/test/recorded_reference_images/ GLMakie/test/recorded_reference_images/ diff --git a/CairoMakie/README.md b/CairoMakie/README.md index 29e0fac8933..2f199b50853 100644 --- a/CairoMakie/README.md +++ b/CairoMakie/README.md @@ -38,7 +38,7 @@ function drawonto(canvas, figure) @guarded draw(canvas) do _ scene = figure.scene resize!(scene, Gtk.width(canvas), Gtk.height(canvas)) - screen = CairoMakie.CairoScreen(scene, Gtk.cairo_surface(canvas), getgc(canvas), nothing) + screen = CairoMakie.Screen(scene, Gtk.cairo_surface(canvas), getgc(canvas), nothing) CairoMakie.cairo_draw(screen, scene) end end diff --git a/CairoMakie/src/CairoMakie.jl b/CairoMakie/src/CairoMakie.jl index b169e8726de..ebcf5eec85d 100644 --- a/CairoMakie/src/CairoMakie.jl +++ b/CairoMakie/src/CairoMakie.jl @@ -32,7 +32,6 @@ include("overrides.jl") function __init__() activate!() - Makie.register_backend!(Makie.current_backend[]) end function display_path(type::String) @@ -42,24 +41,6 @@ function display_path(type::String) return joinpath(@__DIR__, "display." * type) end -const _last_inline = Ref(true) -const _last_type = Ref("png") -const _last_px_per_unit = Ref(1.0) -const _last_pt_per_unit = Ref(0.75) -const _last_antialias = Ref(Cairo.ANTIALIAS_BEST) - -function activate!(; inline = _last_inline[], type = _last_type[], px_per_unit=_last_px_per_unit[], pt_per_unit=_last_pt_per_unit[], antialias = _last_antialias[]) - backend = CairoBackend(display_path(type); px_per_unit=px_per_unit, pt_per_unit=pt_per_unit, antialias = antialias) - Makie.current_backend[] = backend - Makie.use_display[] = !inline - _last_inline[] = inline - _last_type[] = type - _last_px_per_unit[] = px_per_unit - _last_pt_per_unit[] = pt_per_unit - _last_antialias[] = antialias - return -end - include("precompiles.jl") diff --git a/CairoMakie/src/display.jl b/CairoMakie/src/display.jl new file mode 100644 index 00000000000..5db479d3c01 --- /dev/null +++ b/CairoMakie/src/display.jl @@ -0,0 +1,62 @@ + +######################################### +# Backend interface to Makie # +######################################### + +function Makie.backend_display(screen::Screen, scene::Scene) + return open(x.path, "w") do io + Makie.backend_show(screen, io, to_mime(x), scene) + end +end + +function Makie.backend_show(screen::Screen, io::IO, ::MIME"image/svg+xml", scene::Scene) + + cairo_draw(screen, scene) + Cairo.flush(screen.surface) + Cairo.finish(screen.surface) + + svg = String(take!(screen.io)) + + # for some reason, in the svg, surfaceXXX ids keep counting up, + # even with the very same figure drawn again and again + # so we need to reset them to counting up from 1 + # so that the same figure results in the same svg and in the same salt + surfaceids = sort(unique(collect(m.match for m in eachmatch(r"surface\d+", svg)))) + + for (i, id) in enumerate(surfaceids) + svg = replace(svg, id => "surface$i") + end + + # salt svg ids with the first 8 characters of the base64 encoded + # sha512 hash to avoid collisions across svgs when embedding them on + # websites. the hash and therefore the salt will always be the same for the same file + # so the output is deterministic + salt = String(Base64.base64encode(SHA.sha512(svg)))[1:8] + + ids = sort(unique(collect(m[1] for m in eachmatch(r"id\s*=\s*\"([^\"]*)\"", svg)))) + + for id in ids + svg = replace(svg, id => "$id-$salt") + end + + print(io, svg) + return screen +end + +function Makie.backend_show(screen::Screen, io::IO, ::MIME"application/pdf", scene::Scene) + cairo_draw(screen, scene) + Cairo.finish(screen.surface) + return screen +end + +function Makie.backend_show(screen::Screen, io::IO, ::MIME"application/postscript", scene::Scene) + cairo_draw(screen, scene) + Cairo.finish(screen.surface) + return screen +end + +function Makie.backend_show(screen::Screen, io::IO, ::MIME"image/png", scene::Scene) + cairo_draw(screen, scene) + Cairo.write_to_png(screen.surface, io) + return screen +end diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index 6f9511a704c..00a07037160 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -6,79 +6,6 @@ # Types # ################################################################################ -@enum RenderType SVG PNG PDF EPS - -"The Cairo backend object. Used to dispatch to CairoMakie methods." -struct CairoBackend <: Makie.AbstractBackend - typ::RenderType - path::String - px_per_unit::Float64 - pt_per_unit::Float64 - antialias::Int # cairo_antialias_t -end - -""" - struct CairoScreen{S} <: AbstractScreen -A "screen" type for CairoMakie, which encodes a surface -and a context which are used to draw a Scene. -""" -struct CairoScreen{S} <: Makie.AbstractScreen - scene::Scene - surface::S - context::Cairo.CairoContext - pane::Nothing # TODO: GtkWindowLeaf -end - - -function CairoBackend(path::String; px_per_unit=1, pt_per_unit=1, antialias = Cairo.ANTIALIAS_BEST) - ext = splitext(path)[2] - typ = if ext == ".png" - PNG - elseif ext == ".svg" - SVG - elseif ext == ".pdf" - PDF - elseif ext == ".eps" - EPS - else - error("Unsupported extension: $ext") - end - CairoBackend(typ, path, px_per_unit, pt_per_unit, antialias) -end - -# we render the scene directly, since we have -# no screen dependent state like in e.g. opengl -Base.insert!(screen::CairoScreen, scene::Scene, plot) = nothing - -function Base.show(io::IO, ::MIME"text/plain", screen::CairoScreen{S}) where S - println(io, "CairoScreen{$S} with surface:") - println(io, screen.surface) -end - -# Default to ARGB Surface as backing device -# TODO: integrate Gtk into this, so we can have an interactive display -""" - CairoScreen(scene::Scene; antialias = Cairo.ANTIALIAS_BEST) -Create a CairoScreen backed by an image surface. -""" -function CairoScreen(scene::Scene; device_scaling_factor = 1, antialias = Cairo.ANTIALIAS_BEST) - w, h = round.(Int, scene.camera.resolution[] .* device_scaling_factor) - surf = Cairo.CairoARGBSurface(w, h) - - # this sets a scaling factor on the lowest level that is "hidden" so its even - # enabled when the drawing space is reset for strokes - # that means it can be used to increase or decrease the image resolution - ccall((:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), - surf.ptr, device_scaling_factor, device_scaling_factor) - - ctx = Cairo.CairoContext(surf) - Cairo.set_antialias(ctx, antialias) - # Set the miter limit (when miter transitions to bezel) to mimic GLMakie behaviour - ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, 2.0) - - return CairoScreen(scene, surf, ctx, nothing) -end - function get_type(surface::Cairo.CairoSurface) return ccall((:cairo_surface_get_type, Cairo.libcairo), Cint, (Ptr{Nothing},), surface.ptr) end @@ -90,53 +17,6 @@ function is_vector_backend(surf::Cairo.CairoSurface) return typ in (Cairo.CAIRO_SURFACE_TYPE_PDF, Cairo.CAIRO_SURFACE_TYPE_PS, Cairo.CAIRO_SURFACE_TYPE_SVG) end -""" - CairoScreen( - scene::Scene, path::Union{String, IO}, mode::Symbol; - antialias = Cairo.ANTIALIAS_BEST - ) -Creates a CairoScreen pointing to a given output path, with some rendering type defined by `mode`. -""" -function CairoScreen(scene::Scene, path::Union{String, IO}, mode::Symbol; device_scaling_factor = 1, antialias = Cairo.ANTIALIAS_BEST) - - # the surface size is the scene size scaled by the device scaling factor - w, h = round.(Int, scene.camera.resolution[] .* device_scaling_factor) - - if mode == :svg - surf = Cairo.CairoSVGSurface(path, w, h) - elseif mode == :pdf - surf = Cairo.CairoPDFSurface(path, w, h) - elseif mode == :eps - surf = Cairo.CairoEPSSurface(path, w, h) - elseif mode == :png - surf = Cairo.CairoARGBSurface(w, h) - else - error("No available Cairo surface for mode $mode") - end - - # this sets a scaling factor on the lowest level that is "hidden" so its even - # enabled when the drawing space is reset for strokes - # that means it can be used to increase or decrease the image resolution - ccall((:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), - surf.ptr, device_scaling_factor, device_scaling_factor) - - ctx = Cairo.CairoContext(surf) - Cairo.set_antialias(ctx, antialias) - # Set the miter limit (when miter transitions to bezel) to mimic GLMakie behaviour - ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, 2.0) - - return CairoScreen(scene, surf, ctx, nothing) -end - -GeometryBasics.widths(screen::CairoScreen) = round.(Int, (screen.surface.width, screen.surface.height)) - - -function Base.delete!(screen::CairoScreen, scene::Scene, plot::AbstractPlot) - # Currently, we rerender every time, so nothing needs - # to happen here. However, in the event that changes, - # e.g. if we integrate a Gtk window, we may need to - # do something here. -end "Convert a rendering type to a MIME type" function to_mime(x::RenderType) @@ -156,7 +36,7 @@ to_mime(x::CairoBackend) = to_mime(x.typ) ######################################## # The main entry point into the drawing pipeline -function cairo_draw(screen::CairoScreen, scene::Scene) +function cairo_draw(screen::Screen, scene::Scene) draw_background(screen, scene) allplots = get_all_plots(scene) @@ -207,7 +87,7 @@ function get_all_plots(scene, plots = AbstractPlot[]) plots end -function prepare_for_scene(screen::CairoScreen, scene::Scene) +function prepare_for_scene(screen::Screen, scene::Scene) # get the root area to correct for its pixel size when translating root_area = Makie.root(scene).px_area[] @@ -232,7 +112,7 @@ function prepare_for_scene(screen::CairoScreen, scene::Scene) return end -function draw_background(screen::CairoScreen, scene::Scene) +function draw_background(screen::Screen, scene::Scene) cr = screen.context Cairo.save(cr) if scene.clear[] @@ -246,7 +126,7 @@ function draw_background(screen::CairoScreen, scene::Scene) foreach(child_scene-> draw_background(screen, child_scene), scene.children) end -function draw_plot(scene::Scene, screen::CairoScreen, primitive::Combined) +function draw_plot(scene::Scene, screen::Screen, primitive::Combined) if to_value(get(primitive, :visible, true)) if isempty(primitive.plots) Cairo.save(screen.context) @@ -266,14 +146,14 @@ end # instead of the whole Scene # - Recognize when a screen is an image surface, and set scale to render the plot # at the scale of the device pixel -function draw_plot_as_image(scene::Scene, screen::CairoScreen, primitive::Combined, scale::Number = 1) +function draw_plot_as_image(scene::Scene, screen::Screen, primitive::Combined, scale::Number = 1) # you can provide `p.rasterize = scale::Int` or `p.rasterize = true`, both of which are numbers # Extract scene width in pixels w, h = Int.(scene.px_area[].widths) # Create a new Screen which renders directly to an image surface, # specifically for the plot's parent scene. - scr = CairoScreen(scene; device_scaling_factor = scale) + scr = Screen(scene; device_scaling_factor = scale) # Draw the plot to the screen, in the normal way draw_plot(scene, scr, primitive) @@ -296,11 +176,11 @@ function draw_plot_as_image(scene::Scene, screen::CairoScreen, primitive::Combin return end -function draw_atomic(::Scene, ::CairoScreen, x) +function draw_atomic(::Scene, ::Screen, x) @warn "$(typeof(x)) is not supported by cairo right now" end -function clear(screen::CairoScreen) +function clear(screen::Screen) ctx = screen.ctx Cairo.save(ctx) Cairo.set_operator(ctx, Cairo.OPERATOR_SOURCE) @@ -308,126 +188,3 @@ function clear(screen::CairoScreen) Cairo.paint(ctx) Cairo.restore(ctx) end - -######################################### -# Backend interface to Makie # -######################################### - -function Makie.backend_display(x::CairoBackend, scene::Scene; pt_per_unit=x.pt_per_unit, antialias=x.antialias) - return open(x.path, "w") do io - Makie.backend_show(x, IOContext(io, collect(kw)...), to_mime(x), scene) - end -end - -Makie.backend_showable(x::CairoBackend, ::MIME"image/svg+xml", scene::Scene) = x.typ == SVG -Makie.backend_showable(x::CairoBackend, ::MIME"application/pdf", scene::Scene) = x.typ == PDF -Makie.backend_showable(x::CairoBackend, ::MIME"application/postscript", scene::Scene) = x.typ == EPS -Makie.backend_showable(x::CairoBackend, ::MIME"image/png", scene::Scene) = x.typ == PNG - -function Makie.backend_show(x::CairoBackend, io::IO, ::MIME"image/svg+xml", scene::Scene) - proxy_io = IOBuffer() - pt_per_unit = get(io, :pt_per_unit, x.pt_per_unit) - antialias = get(io, :antialias, x.antialias) - - screen = CairoScreen(scene, proxy_io, :svg; device_scaling_factor = pt_per_unit, antialias = antialias) - cairo_draw(screen, scene) - Cairo.flush(screen.surface) - Cairo.finish(screen.surface) - svg = String(take!(proxy_io)) - - # for some reason, in the svg, surfaceXXX ids keep counting up, - # even with the very same figure drawn again and again - # so we need to reset them to counting up from 1 - # so that the same figure results in the same svg and in the same salt - surfaceids = sort(unique(collect(m.match for m in eachmatch(r"surface\d+", svg)))) - - for (i, id) in enumerate(surfaceids) - svg = replace(svg, id => "surface$i") - end - - # salt svg ids with the first 8 characters of the base64 encoded - # sha512 hash to avoid collisions across svgs when embedding them on - # websites. the hash and therefore the salt will always be the same for the same file - # so the output is deterministic - salt = String(Base64.base64encode(SHA.sha512(svg)))[1:8] - - ids = sort(unique(collect(m[1] for m in eachmatch(r"id\s*=\s*\"([^\"]*)\"", svg)))) - - for id in ids - svg = replace(svg, id => "$id-$salt") - end - - print(io, svg) - return screen -end - -function Makie.backend_show(x::CairoBackend, io::IO, ::MIME"application/pdf", scene::Scene) - - pt_per_unit = get(io, :pt_per_unit, x.pt_per_unit) - antialias = get(io, :antialias, x.antialias) - - screen = CairoScreen(scene, io, :pdf; device_scaling_factor = pt_per_unit, antialias = antialias) - cairo_draw(screen, scene) - Cairo.finish(screen.surface) - return screen -end - - -function Makie.backend_show(x::CairoBackend, io::IO, ::MIME"application/postscript", scene::Scene) - - pt_per_unit = get(io, :pt_per_unit, x.pt_per_unit) - antialias = get(io, :antialias, x.antialias) - - screen = CairoScreen(scene, io, :eps; device_scaling_factor = pt_per_unit, antialias = antialias) - - cairo_draw(screen, scene) - Cairo.finish(screen.surface) - return screen -end - -function Makie.backend_show(x::CairoBackend, io::IO, ::MIME"image/png", scene::Scene) - - # multiply the resolution of the png with this factor for more or less detail - # while relative line and font sizes are unaffected - px_per_unit = get(io, :px_per_unit, x.px_per_unit) - antialias = get(io, :antialias, x.antialias) - - # create an ARGB surface, to speed up drawing ops. - screen = CairoScreen(scene; device_scaling_factor = px_per_unit, antialias = antialias) - cairo_draw(screen, scene) - Cairo.write_to_png(screen.surface, io) - return screen -end - - -######################################## -# Fast colorbuffer for recording # -######################################## - -function Makie.colorbuffer(screen::CairoScreen) - # extract scene - scene = screen.scene - # get resolution - w, h = GeometryBasics.widths(screen) - scene_w, scene_h = size(scene) - @assert w/scene_w ≈ h/scene_h - - device_scaling_factor = w/scene_w - # preallocate an image matrix - img = Matrix{ARGB32}(undef, w, h) - # create an image surface to draw onto the image - surf = Cairo.CairoImageSurface(img) - ccall((:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), - surf.ptr, device_scaling_factor, device_scaling_factor) - - # draw the scene onto the image matrix - ctx = Cairo.CairoContext(surf) - ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, 2.0) - - scr = CairoScreen(scene, surf, ctx, nothing) - - cairo_draw(scr, scene) - - # x and y are flipped - return the transpose - return permutedims(img) -end diff --git a/CairoMakie/src/overrides.jl b/CairoMakie/src/overrides.jl index ef66d306fa4..b6512b7a739 100644 --- a/CairoMakie/src/overrides.jl +++ b/CairoMakie/src/overrides.jl @@ -7,7 +7,7 @@ Special method for polys so we don't fall back to atomic meshes, which are much more complex and slower to draw than standard paths with single color. """ -function draw_plot(scene::Scene, screen::CairoScreen, poly::Poly) +function draw_plot(scene::Scene, screen::Screen, poly::Poly) # dispatch on input arguments to poly to use smarter drawing methods than # meshes if possible draw_poly(scene, screen, poly, to_value.(poly.input_args)...) @@ -16,7 +16,7 @@ end """ Fallback method for args without special treatment. """ -function draw_poly(scene::Scene, screen::CairoScreen, poly, args...) +function draw_poly(scene::Scene, screen::Screen, poly, args...) draw_poly_as_mesh(scene, screen, poly) end @@ -27,16 +27,16 @@ end # in the rare case of per-vertex colors redirect to mesh drawing -function draw_poly(scene::Scene, screen::CairoScreen, poly, points::Vector{<:Point2}, color::AbstractArray, model, strokecolor, strokewidth) +function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}, color::AbstractArray, model, strokecolor, strokewidth) draw_poly_as_mesh(scene, screen, poly) end -function draw_poly(scene::Scene, screen::CairoScreen, poly, points::Vector{<:Point2}) +function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}) draw_poly(scene, screen, poly, points, poly.color[], poly.model[], poly.strokecolor[], poly.strokewidth[]) end # when color is a Makie.AbstractPattern, we don't need to go to Mesh -function draw_poly(scene::Scene, screen::CairoScreen, poly, points::Vector{<:Point2}, color::Union{Symbol, Colorant, Makie.AbstractPattern}, +function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}, color::Union{Symbol, Colorant, Makie.AbstractPattern}, model, strokecolor, strokewidth) space = to_value(get(poly, :space, :data)) points = project_position.(Ref(scene), space, points, Ref(model)) @@ -59,7 +59,7 @@ function draw_poly(scene::Scene, screen::CairoScreen, poly, points::Vector{<:Poi Cairo.stroke(screen.context) end -function draw_poly(scene::Scene, screen::CairoScreen, poly, points_list::Vector{<:Vector{<:Point2}}) +function draw_poly(scene::Scene, screen::Screen, poly, points_list::Vector{<:Vector{<:Point2}}) broadcast_foreach(points_list, poly.color[], poly.strokecolor[], poly.strokewidth[]) do points, color, strokecolor, strokewidth @@ -67,9 +67,9 @@ function draw_poly(scene::Scene, screen::CairoScreen, poly, points_list::Vector{ end end -draw_poly(scene::Scene, screen::CairoScreen, poly, rect::Rect2) = draw_poly(scene, screen, poly, [rect]) +draw_poly(scene::Scene, screen::Screen, poly, rect::Rect2) = draw_poly(scene, screen, poly, [rect]) -function draw_poly(scene::Scene, screen::CairoScreen, poly, rects::Vector{<:Rect2}) +function draw_poly(scene::Scene, screen::Screen, poly, rects::Vector{<:Rect2}) model = poly.model[] space = to_value(get(poly, :space, :data)) projected_rects = project_rect.(Ref(scene), space, rects, Ref(model)) @@ -124,7 +124,7 @@ function polypath(ctx, polygon) end end -function draw_poly(scene::Scene, screen::CairoScreen, poly, polygons::AbstractArray{<:Polygon}) +function draw_poly(scene::Scene, screen::Screen, poly, polygons::AbstractArray{<:Polygon}) model = poly.model[] space = to_value(get(poly, :space, :data)) projected_polys = project_polygon.(Ref(scene), space, polygons, Ref(model)) @@ -161,7 +161,7 @@ end # gradients as well via `mesh` we have to intercept the poly use # ################################################################################ -function draw_plot(scene::Scene, screen::CairoScreen, +function draw_plot(scene::Scene, screen::Screen, band::Band{<:Tuple{<:AbstractVector{<:Point2},<:AbstractVector{<:Point2}}}) if !(band.color[] isa AbstractArray) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index ec2083e8b32..12761f44285 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -2,7 +2,7 @@ # Lines, LineSegments # ################################################################################ -function draw_atomic(scene::Scene, screen::CairoScreen, @nospecialize(primitive::Union{Lines, LineSegments})) +function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Union{Lines, LineSegments})) fields = @get_attribute(primitive, (color, linewidth, linestyle)) linestyle = Makie.convert_attribute(linestyle, Makie.key"linestyle"()) ctx = screen.context @@ -173,7 +173,7 @@ end # Scatter # ################################################################################ -function draw_atomic(scene::Scene, screen::CairoScreen, @nospecialize(primitive::Scatter)) +function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Scatter)) fields = @get_attribute(primitive, (color, markersize, strokecolor, strokewidth, marker, marker_offset, rotations)) @get_attribute(primitive, (transform_marker,)) @@ -388,7 +388,7 @@ function p3_to_p2(p::Point3{T}) where T end end -function draw_atomic(scene::Scene, screen::CairoScreen, @nospecialize(primitive::Text{<:Tuple{<:Union{AbstractArray{<:Makie.GlyphCollection}, Makie.GlyphCollection}}})) +function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Text{<:Tuple{<:Union{AbstractArray{<:Makie.GlyphCollection}, Makie.GlyphCollection}}})) ctx = screen.context @get_attribute(primitive, (rotation, model, space, markerspace, offset)) position = primitive.position[] @@ -557,7 +557,7 @@ end regularly_spaced_array_to_range(arr::AbstractRange) = arr -function draw_atomic(scene::Scene, screen::CairoScreen, @nospecialize(primitive::Union{Heatmap, Image})) +function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Union{Heatmap, Image})) ctx = screen.context image = primitive[3][] xs, ys = primitive[1][], primitive[2][] @@ -688,7 +688,7 @@ end ################################################################################ -function draw_atomic(scene::Scene, screen::CairoScreen, @nospecialize(primitive::Makie.Mesh)) +function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Makie.Mesh)) mesh = primitive[1][] if Makie.cameracontrols(scene) isa Union{Camera2D, Makie.PixelCamera, Makie.EmptyCamera} draw_mesh2D(scene, screen, primitive, mesh) @@ -925,7 +925,7 @@ end ################################################################################ -function draw_atomic(scene::Scene, screen::CairoScreen, @nospecialize(primitive::Makie.Surface)) +function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Makie.Surface)) # Pretend the surface plot is a mesh plot and plot that instead mesh = surface2mesh(primitive[1][], primitive[2][], primitive[3][]) old = primitive[:color] @@ -954,7 +954,7 @@ end ################################################################################ -function draw_atomic(scene::Scene, screen::CairoScreen, @nospecialize(primitive::Makie.MeshScatter)) +function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Makie.MeshScatter)) @get_attribute(primitive, (color, model, marker, markersize, rotations)) if color isa AbstractArray{<: Number} diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl new file mode 100644 index 00000000000..205a15824d7 --- /dev/null +++ b/CairoMakie/src/screen.jl @@ -0,0 +1,159 @@ +@enum RenderType SVG PNG PDF EPS + +const SCREEN_CONFIG = Ref(( + type = "png", + px_per_unit = 1.0, + pt_per_unit = 0.75, + antialias = Cairo.ANTIALIAS_BEST +)) + +function activate!(; screen_config...) + Makie.set_screen_config!(SCREEN_CONFIG, screen_config) + Makie.register_backend!(CairoMakie) + return +end + +""" + struct Screen{S} <: AbstractScreen +A "screen" type for CairoMakie, which encodes a surface +and a context which are used to draw a Scene. +""" +struct Screen{S} <: Makie.AbstractScreen + scene::Scene + surface::S + context::Cairo.CairoContext + pane::Nothing # TODO: GtkWindowLeaf + + px_per_unit::Float64 + pt_per_unit::Float64 + antialias::Int # cairo_antialias_t +end + +function CairoBackend(path::String; px_per_unit=1, pt_per_unit=1, antialias = Cairo.ANTIALIAS_BEST) + ext = splitext(path)[2] + typ = if ext == ".png" + PNG + elseif ext == ".svg" + SVG + elseif ext == ".pdf" + PDF + elseif ext == ".eps" + EPS + else + error("Unsupported extension: $ext") + end + CairoBackend(typ, path, px_per_unit, pt_per_unit, antialias) +end + +# we render the scene directly, since we have +# no screen dependent state like in e.g. opengl +Base.insert!(screen::Screen, scene::Scene, plot) = nothing + +function Base.show(io::IO, ::MIME"text/plain", screen::Screen{S}) where S + println(io, "Screen{$S} with surface:") + println(io, screen.surface) +end + +# Default to ARGB Surface as backing device +# TODO: integrate Gtk into this, so we can have an interactive display +""" + Screen(scene::Scene; antialias = Cairo.ANTIALIAS_BEST) +Create a Screen backed by an image surface. +""" +function Screen(scene::Scene; device_scaling_factor = 1, antialias = Cairo.ANTIALIAS_BEST) + w, h = round.(Int, scene.camera.resolution[] .* device_scaling_factor) + surf = Cairo.CairoARGBSurface(w, h) + + # this sets a scaling factor on the lowest level that is "hidden" so its even + # enabled when the drawing space is reset for strokes + # that means it can be used to increase or decrease the image resolution + ccall((:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), + surf.ptr, device_scaling_factor, device_scaling_factor) + + ctx = Cairo.CairoContext(surf) + Cairo.set_antialias(ctx, antialias) + # Set the miter limit (when miter transitions to bezel) to mimic GLMakie behaviour + ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, 2.0) + + return Screen(scene, surf, ctx, nothing, px_per_unit, pt_per_unit, antialias) +end + +""" + Screen( + scene::Scene, path::Union{String, IO}, mode::Symbol; + antialias = Cairo.ANTIALIAS_BEST + ) +Creates a Screen pointing to a given output path, with some rendering type defined by `mode`. +""" +function Screen(scene::Scene, path::Union{String, IO}, mode::Symbol; device_scaling_factor = 1, antialias = Cairo.ANTIALIAS_BEST) + + # the surface size is the scene size scaled by the device scaling factor + w, h = round.(Int, scene.camera.resolution[] .* device_scaling_factor) + + if mode == :svg + surf = Cairo.CairoSVGSurface(path, w, h) + elseif mode == :pdf + surf = Cairo.CairoPDFSurface(path, w, h) + elseif mode == :eps + surf = Cairo.CairoEPSSurface(path, w, h) + elseif mode == :png + surf = Cairo.CairoARGBSurface(w, h) + else + error("No available Cairo surface for mode $mode") + end + + # this sets a scaling factor on the lowest level that is "hidden" so its even + # enabled when the drawing space is reset for strokes + # that means it can be used to increase or decrease the image resolution + ccall((:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), + surf.ptr, device_scaling_factor, device_scaling_factor) + + ctx = Cairo.CairoContext(surf) + Cairo.set_antialias(ctx, antialias) + # Set the miter limit (when miter transitions to bezel) to mimic GLMakie behaviour + ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, 2.0) + + return Screen(scene, surf, ctx, nothing) +end + +GeometryBasics.widths(screen::Screen) = round.(Int, (screen.surface.width, screen.surface.height)) + +function Base.delete!(screen::Screen, scene::Scene, plot::AbstractPlot) + # Currently, we rerender every time, so nothing needs + # to happen here. However, in the event that changes, + # e.g. if we integrate a Gtk window, we may need to + # do something here. +end + + +######################################## +# Fast colorbuffer for recording # +######################################## + +function Makie.colorbuffer(screen::Screen) + # extract scene + scene = screen.scene + # get resolution + w, h = GeometryBasics.widths(screen) + scene_w, scene_h = size(scene) + @assert w/scene_w ≈ h/scene_h + + device_scaling_factor = w/scene_w + # preallocate an image matrix + img = Matrix{ARGB32}(undef, w, h) + # create an image surface to draw onto the image + surf = Cairo.CairoImageSurface(img) + ccall((:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), + surf.ptr, device_scaling_factor, device_scaling_factor) + + # draw the scene onto the image matrix + ctx = Cairo.CairoContext(surf) + ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, 2.0) + + scr = Screen(scene, surf, ctx, nothing) + + cairo_draw(scr, scene) + + # x and y are flipped - return the transpose + return permutedims(img) +end diff --git a/GLMakie/src/GLMakie.jl b/GLMakie/src/GLMakie.jl index 5e3ed8bce83..f24345646a1 100644 --- a/GLMakie/src/GLMakie.jl +++ b/GLMakie/src/GLMakie.jl @@ -37,13 +37,9 @@ for name in names(Makie, all=true) end end -export inline! import ShaderAbstractions: Sampler, Buffer export Sampler, Buffer -struct GLBackend <: Makie.AbstractBackend -end - const GL_ASSET_DIR = RelocatableFolders.@path joinpath(@__DIR__, "..", "assets") const SHADER_DIR = RelocatableFolders.@path joinpath(GL_ASSET_DIR, "shader") loadshader(name) = joinpath(SHADER_DIR, name) @@ -51,12 +47,9 @@ loadshader(name) = joinpath(SHADER_DIR, name) # don't put this into try catch, to not mess with normal errors include("gl_backend.jl") -function activate!(use_display=true) - b = GLBackend() - Makie.register_backend!(b) +function activate!() + Makie.register_backend!(GLMakie) Makie.set_glyph_resolution!(Makie.High) - Makie.current_backend[] = b - Makie.inline!(!use_display) end function __init__() diff --git a/RPRMakie/src/RPRMakie.jl b/RPRMakie/src/RPRMakie.jl index 8e983f15a24..347d41f2f26 100644 --- a/RPRMakie/src/RPRMakie.jl +++ b/RPRMakie/src/RPRMakie.jl @@ -37,10 +37,7 @@ function activate!(; iterations=200, resource=RENDER_RESOURCE[], plugin=RENDER_P NUM_ITERATIONS[] = iterations RENDER_RESOURCE[] = resource RENDER_PLUGIN[] = plugin - b = RPRBackend() - Makie.register_backend!(b) - Makie.current_backend[] = b - Makie.inline!(true) + Makie.register_backend!(RPRMakie) return end diff --git a/WGLMakie/src/WGLMakie.jl b/WGLMakie/src/WGLMakie.jl index 20909f4ef40..d2bfcb0e4ad 100644 --- a/WGLMakie/src/WGLMakie.jl +++ b/WGLMakie/src/WGLMakie.jl @@ -45,9 +45,10 @@ include("meshes.jl") include("imagelike.jl") include("display.jl") -const CONFIG = ( - fps = Ref(30), -) + +const SCREEN_CONFIG = Ref(( + fps = 30, +)) """ activate!(; fps=30) @@ -55,10 +56,8 @@ const CONFIG = ( Set fps (frames per second) to a higher number for smoother animations, or to a lower to use less resources. """ function activate!(; fps=30) - CONFIG.fps[] = fps - b = WGLBackend() - Makie.register_backend!(b) - Makie.current_backend[] = b + SCREEN_CONFIG[] = merge(SCREEN_CONFIG[], (fps=fps,)) + Makie.register_backend!(WGLMakie) Makie.set_glyph_resolution!(Makie.Low) return end @@ -68,8 +67,6 @@ const TEXTURE_ATLAS_CHANGED = Ref(false) function __init__() # Activate WGLMakie as backend! activate!() - browser_display = JSServe.BrowserDisplay() in Base.Multimedia.displays - Makie.inline!(!browser_display) # We need to update the texture atlas whenever it changes! # We do this in three_plot! Makie.font_render_callback!() do sd, uv @@ -84,7 +81,6 @@ for name in names(Makie, all=true) @eval export $(name) end end -export inline! include("precompiles.jl") diff --git a/src/Makie.jl b/src/Makie.jl index e47b0be98f7..e96f3b1c167 100644 --- a/src/Makie.jl +++ b/src/Makie.jl @@ -167,6 +167,7 @@ include("interaction/inspector.jl") # documentation and help functions include("documentation/documentation.jl") include("display.jl") +include("event-recorder.jl") # bezier paths export BezierPath, MoveTo, LineTo, CurveTo, EllipticalArc, ClosePath diff --git a/src/display.jl b/src/display.jl index 2f398f1b892..595f37f264a 100644 --- a/src/display.jl +++ b/src/display.jl @@ -1,27 +1,23 @@ -abstract type AbstractBackend end +@enum ImageStorageFormat JuliaNative GLNative + +update_state_before_display!(_) = nothing + function backend_display end +function backend_show end -@enum ImageStorageFormat JuliaNative GLNative """ Current backend """ -const current_backend = Ref{Union{Missing,AbstractBackend}}(missing) -const use_display = Ref{Bool}(true) +const current_backend = Ref{Union{Missing, Module}}(missing) -function inline!(inline = true) - use_display[] = !inline - return -end - -function register_backend!(backend::AbstractBackend) +function register_backend!(backend::Module) current_backend[] = backend return end function push_screen!(scene::Scene, display) - # Ok lets leave a warning here until we fix CairoMakie! - @debug("Backend doesn't return screen from show methods. This needs fixing!") + error("$(display) not a valid Makie display.") end function push_screen!(scene::Scene, display::AbstractDisplay) @@ -46,12 +42,26 @@ function delete_screen!(scene::Scene, display::AbstractDisplay) return end -function backend_display(s::FigureLike; kw...) - update_state_before_display!(s) - backend_display(current_backend[], get_scene(s); kw...) +function set_screen_config!(config::RefValue, new_values) + config_attributes = propertynames(config) + for (k, v) in pairs(new_values) + if !(k in config_attributes) + error("$k is not a valid screen config. Applicable options: $(config_attributes)") + end + end + config[] = merge(config[], new_values) +end + +function backend_display(figlike::FigureLike; screen_kw...) + update_state_before_display!(figlike) + Backend = current_backend[] + scene = get_scene(figlike) + screen = Backend.Screen(scene; screen_kw...) + backend_display(screen, scene) + return screen end -function backend_display(::Missing, ::Scene; kw...) +function backend_display(::Missing, ::Scene; screen_kw...) error(""" No backend available! Make sure to also `import/using` a backend (GLMakie, CairoMakie, WGLMakie). @@ -61,7 +71,6 @@ function backend_display(::Missing, ::Scene; kw...) """) end -update_state_before_display!(_) = nothing """ @@ -77,35 +86,28 @@ pt_per_unit=x.pt_per_unit px_per_unit=x.px_per_unit antialias=x.antialias """ -function Base.display(fig::FigureLike; display_attributes...) - scene = get_scene(fig) - if !use_display[] - update_state_before_display!(fig) - return Core.invoke(display, Tuple{Any}, scene) - else - screen = backend_display(fig; display_attributes...) - push_screen!(scene, screen) - return screen - end +function Base.display(fig::FigureLike; screen_kw...) + return backend_display(fig; screen_kw...) end function Base.showable(mime::MIME{M}, scene::Scene) where M - backend_showable(current_backend[], mime, scene) + backend_showable(current_backend[].Screen, mime, scene) end + # ambig function Base.showable(mime::MIME"application/json", scene::Scene) - backend_showable(current_backend[], mime, scene) + backend_showable(current_backend[].Screen, mime, scene) end function Base.showable(mime::MIME{M}, fig::FigureLike) where M - backend_showable(current_backend[], mime, get_scene(fig)) + backend_showable(current_backend[].Screen, mime, get_scene(fig)) end # ambig function Base.showable(mime::MIME"application/json", fig::FigureLike) - backend_showable(current_backend[], mime, get_scene(fig)) + backend_showable(current_backend[].Screen, mime, get_scene(fig)) end -function backend_showable(::Backend, ::Mime, ::Scene) where {Backend, Mime <: MIME} - hasmethod(backend_show, Tuple{Backend, IO, Mime, Scene}) +function backend_showable(::Type{Screen}, ::Mime, ::Scene) where {Screen, Mime <: MIME} + hasmethod(backend_show, Tuple{Screen, IO, Mime, Scene}) end # fallback show when no backend is selected @@ -121,14 +123,15 @@ function backend_show(backend, io::IO, ::MIME"text/plain", scene::Scene) return end -function Base.show(io::IO, ::MIME"text/plain", scene::Scene; kw...) - show(io, scene; kw...) +function Base.show(io::IO, ::MIME"text/plain", scene::Scene) + show(io, scene) end function Base.show(io::IO, m::MIME, figlike::FigureLike) ioc = IOContext(io, :full_fidelity => true) update_state_before_display!(figlike) - backend_show(current_backend[], ioc, m, get_scene(figlike)) + scene = get_scene(figlike) + backend_show(current_backend[].Screen(scene), ioc, m, scene) return end @@ -192,20 +195,19 @@ function FileIO.save(dir::String, s::RamStepper) end end -format2mime(::Type{FileIO.format"PNG"}) = MIME("image/png") -format2mime(::Type{FileIO.format"SVG"}) = MIME("image/svg+xml") +format2mime(::Type{FileIO.format"PNG"}) = MIME("image/png") +format2mime(::Type{FileIO.format"SVG"}) = MIME("image/svg+xml") format2mime(::Type{FileIO.format"JPEG"}) = MIME("image/jpeg") format2mime(::Type{FileIO.format"TIFF"}) = MIME("image/tiff") format2mime(::Type{FileIO.format"BMP"}) = MIME("image/bmp") -format2mime(::Type{FileIO.format"PDF"}) = MIME("application/pdf") -format2mime(::Type{FileIO.format"TEX"}) = MIME("application/x-tex") -format2mime(::Type{FileIO.format"EPS"}) = MIME("application/postscript") +format2mime(::Type{FileIO.format"PDF"}) = MIME("application/pdf") +format2mime(::Type{FileIO.format"TEX"}) = MIME("application/x-tex") +format2mime(::Type{FileIO.format"EPS"}) = MIME("application/postscript") format2mime(::Type{FileIO.format"HTML"}) = MIME("text/html") filetype(::FileIO.File{F}) where F = F # Allow format to be overridden with first argument - """ FileIO.save(filename, scene; resolution = size(scene), pt_per_unit = 0.75, px_per_unit = 1.0) @@ -240,7 +242,9 @@ function FileIO.save( pt_per_unit = 0.75, px_per_unit = 1.0, ) + scene = get_scene(fig) + if resolution != size(scene) resize!(scene, resolution) end @@ -268,67 +272,6 @@ end raw_io(io::IO) = io raw_io(io::IOContext) = raw_io(io.io) -""" - record_events(f, scene::Scene, path::String) - -Records all window events that happen while executing function `f` -for `scene` and serializes them to `path`. -""" -function record_events(f, scene::Scene, path::String) - display(scene) - result = Vector{Pair{Float64, Pair{Symbol, Any}}}() - for field in fieldnames(Events) - # These are not Observables - (field == :mousebuttonstate || field == :keyboardstate) && continue - on(getfield(scene.events, field), priority = typemax(Int)) do value - value = isa(value, Set) ? copy(value) : value - push!(result, time() => (field => value)) - return Consume(false) - end - end - f() - open(path, "w") do io - serialize(io, result) - end -end - - -""" - replay_events(f, scene::Scene, path::String) - replay_events(scene::Scene, path::String) - -Replays the serialized events recorded with `record_events` in `path` in `scene`. -""" -replay_events(scene::Scene, path::String) = replay_events(()-> nothing, scene, path) -function replay_events(f, scene::Scene, path::String) - events = open(io-> deserialize(io), path) - sort!(events, by = first) - for i in 1:length(events) - t1, (field, value) = events[i] - (field == :mousebuttonstate || field == :keyboardstate) && continue - Base.invokelatest() do - getfield(scene.events, field)[] = value - end - f() - if i < length(events) - t2, (field, value) = events[i + 1] - # min sleep time 0.001 - if (t2 - t1 > 0.001) - sleep(t2 - t1) - else - yield() - end - end - end -end - -struct RecordEvents - scene::Scene - path::String -end - -Base.display(re::RecordEvents) = display(re.scene) - struct VideoStream io process @@ -359,70 +302,6 @@ function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, conn return VideoStream(process.in, process, screen, abspath(path)) end -# This has to be overloaded by the backend for its screen type. -function colorbuffer(x::AbstractScreen) - error("colorbuffer not implemented for screen $(typeof(x))") -end - -function jl_to_gl_format(image) - @static if VERSION < v"1.6" - d1, d2 = size(image) - bufc = Array{eltype(image)}(undef, d2, d1) #permuted - ind1, ind2 = axes(image) - n = first(ind1) + last(ind1) - for i in ind1 - @simd for j in ind2 - @inbounds bufc[j, n-i] = image[i, j] - end - end - return bufc - else - reverse!(image; dims=1) - return collect(PermutedDimsArray(image, (2, 1))) - end -end - -# less specific for overloading by backends -function colorbuffer(screen::Any, format::ImageStorageFormat = JuliaNative) - image = colorbuffer(screen) - if format == GLNative - if string(typeof(screen)) == "GLMakie.Screen" - @warn "Inefficient re-conversion back to GLNative buffer format. Update GLMakie to support direct buffer access" maxlog=1 - end - return jl_to_gl_format(image) - elseif format == JuliaNative - return image - end -end - -""" - colorbuffer(scene, format::ImageStorageFormat = JuliaNative) - colorbuffer(screen, format::ImageStorageFormat = JuliaNative) - -Returns the content of the given scene or screen rasterised to a Matrix of -Colors. The return type is backend-dependent, but will be some form of RGB -or RGBA. - -- `format = JuliaNative` : Returns a buffer in the format of standard julia images (dims permuted and one reversed) -- `format = GLNative` : Returns a more efficient format buffer for GLMakie which can be directly - used in FFMPEG without conversion -""" -function colorbuffer(fig::FigureLike, format::ImageStorageFormat = JuliaNative) - scene = get_scene(fig) - screen = getscreen(scene) - if isnothing(screen) - if ismissing(current_backend[]) - error(""" - You have not loaded a backend. Please load one (`using GLMakie` or `using CairoMakie`) - before trying to render a Scene. - """) - else - return colorbuffer(backend_display(fig; visible=false, start_renderloop=false, connect=false), format) - end - end - return colorbuffer(screen, format) -end - """ recordframe!(io::VideoStream) @@ -626,3 +505,69 @@ function Base.show(io::IO, ::MIME"text/html", vs::VideoStream) ) end end + + + +# This has to be overloaded by the backend for its screen type. +function colorbuffer(x::AbstractScreen) + error("colorbuffer not implemented for screen $(typeof(x))") +end + +function jl_to_gl_format(image) + @static if VERSION < v"1.6" + d1, d2 = size(image) + bufc = Array{eltype(image)}(undef, d2, d1) #permuted + ind1, ind2 = axes(image) + n = first(ind1) + last(ind1) + for i in ind1 + @simd for j in ind2 + @inbounds bufc[j, n-i] = image[i, j] + end + end + return bufc + else + reverse!(image; dims=1) + return collect(PermutedDimsArray(image, (2, 1))) + end +end + +# less specific for overloading by backends +function colorbuffer(screen::Any, format::ImageStorageFormat = JuliaNative) + image = colorbuffer(screen) + if format == GLNative + if string(typeof(screen)) == "GLMakie.Screen" + @warn "Inefficient re-conversion back to GLNative buffer format. Update GLMakie to support direct buffer access" maxlog=1 + end + return jl_to_gl_format(image) + elseif format == JuliaNative + return image + end +end + +""" + colorbuffer(scene, format::ImageStorageFormat = JuliaNative) + colorbuffer(screen, format::ImageStorageFormat = JuliaNative) + +Returns the content of the given scene or screen rasterised to a Matrix of +Colors. The return type is backend-dependent, but will be some form of RGB +or RGBA. + +- `format = JuliaNative` : Returns a buffer in the format of standard julia images (dims permuted and one reversed) +- `format = GLNative` : Returns a more efficient format buffer for GLMakie which can be directly + used in FFMPEG without conversion +""" +function colorbuffer(fig::FigureLike, format::ImageStorageFormat = JuliaNative) + scene = get_scene(fig) + screen = getscreen(scene) + if isnothing(screen) + if ismissing(current_backend[]) + error(""" + You have not loaded a backend. Please load one (`using GLMakie` or `using CairoMakie`) + before trying to render a Scene. + """) + else + return colorbuffer(backend_display(fig; visible=false, start_renderloop=false, connect=false), format) + end + end + return colorbuffer(screen, format) +end diff --git a/src/event-recorder.jl b/src/event-recorder.jl new file mode 100644 index 00000000000..4fe6eab44d7 --- /dev/null +++ b/src/event-recorder.jl @@ -0,0 +1,60 @@ + +""" +record_events(f, scene::Scene, path::String) + +Records all window events that happen while executing function `f` +for `scene` and serializes them to `path`. +""" +function record_events(f, scene::Scene, path::String) + display(scene) + result = Vector{Pair{Float64,Pair{Symbol,Any}}}() + for field in fieldnames(Events) + # These are not Observables + (field == :mousebuttonstate || field == :keyboardstate) && continue + on(getfield(scene.events, field); priority=typemax(Int)) do value + value = isa(value, Set) ? copy(value) : value + push!(result, time() => (field => value)) + return Consume(false) + end + end + f() + open(path, "w") do io + return serialize(io, result) + end +end + +""" +replay_events(f, scene::Scene, path::String) +replay_events(scene::Scene, path::String) + +Replays the serialized events recorded with `record_events` in `path` in `scene`. +""" +replay_events(scene::Scene, path::String) = replay_events(() -> nothing, scene, path) +function replay_events(f, scene::Scene, path::String) + events = open(io -> deserialize(io), path) + sort!(events; by=first) + for i in 1:length(events) + t1, (field, value) = events[i] + (field == :mousebuttonstate || field == :keyboardstate) && continue + Base.invokelatest() do + return getfield(scene.events, field)[] = value + end + f() + if i < length(events) + t2, (field, value) = events[i + 1] + # min sleep time 0.001 + if (t2 - t1 > 0.001) + sleep(t2 - t1) + else + yield() + end + end + end +end + +struct RecordEvents + scene::Scene + path::String +end + +Base.display(re::RecordEvents) = display(re.scene) From 84efb7884f66f583f4835be6196c1c8904791211 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Mon, 26 Sep 2022 17:30:06 +0200 Subject: [PATCH 15/56] new screen constructors --- CairoMakie/src/CairoMakie.jl | 6 +- CairoMakie/src/cairo-extension.jl | 70 +++++++++++ CairoMakie/src/fonts.jl | 46 -------- CairoMakie/src/infrastructure.jl | 12 -- CairoMakie/src/primitives.jl | 19 --- CairoMakie/src/screen.jl | 185 ++++++++++++++++-------------- CairoMakie/src/utils.jl | 16 +++ GLMakie/src/GLMakie.jl | 2 +- GLMakie/src/screen.jl | 32 ++---- MakieCore/src/types.jl | 1 - RPRMakie/src/RPRMakie.jl | 2 +- RPRMakie/src/scene.jl | 2 +- WGLMakie/src/WGLMakie.jl | 2 +- WGLMakie/src/display.jl | 4 +- WGLMakie/src/three_plot.jl | 2 +- src/Makie.jl | 4 +- src/display.jl | 31 +++-- src/interaction/events.jl | 4 +- src/scenes.jl | 8 +- 19 files changed, 236 insertions(+), 212 deletions(-) create mode 100644 CairoMakie/src/cairo-extension.jl delete mode 100644 CairoMakie/src/fonts.jl diff --git a/CairoMakie/src/CairoMakie.jl b/CairoMakie/src/CairoMakie.jl index ebcf5eec85d..13a51cfcd18 100644 --- a/CairoMakie/src/CairoMakie.jl +++ b/CairoMakie/src/CairoMakie.jl @@ -22,11 +22,12 @@ for name in names(Makie, all=true) @eval export $(name) end end -export inline! +include("cairo-extension.jl") +include("screen.jl") +include("display.jl") include("infrastructure.jl") include("utils.jl") -include("fonts.jl") include("primitives.jl") include("overrides.jl") @@ -43,5 +44,4 @@ end include("precompiles.jl") - end diff --git a/CairoMakie/src/cairo-extension.jl b/CairoMakie/src/cairo-extension.jl new file mode 100644 index 00000000000..51c7584a81e --- /dev/null +++ b/CairoMakie/src/cairo-extension.jl @@ -0,0 +1,70 @@ +# TODO, move those to Cairo? + +function set_font_matrix(ctx, matrix) + ccall((:cairo_set_font_matrix, Cairo.libcairo), Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), ctx.ptr, Ref(matrix)) +end + +function get_font_matrix(ctx) + matrix = Cairo.CairoMatrix() + ccall((:cairo_get_font_matrix, Cairo.libcairo), Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), ctx.ptr, Ref(matrix)) + return matrix +end + +function cairo_font_face_destroy(font_face) + ccall( + (:cairo_font_face_destroy, Cairo.libcairo), + Cvoid, (Ptr{Cvoid},), + font_face + ) +end + +function set_ft_font(ctx, font) + + font_face = ccall( + (:cairo_ft_font_face_create_for_ft_face, Cairo.libcairo), + Ptr{Cvoid}, (Makie.FreeTypeAbstraction.FT_Face, Cint), + font, 0 + ) + + ccall((:cairo_set_font_face, Cairo.libcairo), Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), ctx.ptr, font_face) + + return font_face +end + +struct CairoGlyph + index::Culong + x::Cdouble + y::Cdouble +end + +function show_glyph(ctx, glyph, x, y) + cg = Ref(CairoGlyph(glyph, x, y)) + ccall((:cairo_show_glyphs, Cairo.libcairo), + Nothing, (Ptr{Nothing}, Ptr{CairoGlyph}, Cint), + ctx.ptr, cg, 1) +end + +function glyph_path(ctx, glyph::Culong, x, y) + cg = Ref(CairoGlyph(glyph, x, y)) + ccall((:cairo_glyph_path, Cairo.libcairo), + Nothing, (Ptr{Nothing}, Ptr{CairoGlyph}, Cint), + ctx.ptr, cg, 1) +end + +function surface_set_device_scale(surf, device_x_scale, device_y_scale=device_x_scale) + # this sets a scaling factor on the lowest level that is "hidden" so its even + # enabled when the drawing space is reset for strokes + # that means it can be used to increase or decrease the image resolution + ccall( + (:cairo_surface_set_device_scale, Cairo.libcairo), + Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), + surf.ptr, device_x_scale, device_scaling_factor) +end + +function set_miter_limit(ctx, limit) + ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, limit) +end + +function get_type(surface::Cairo.CairoSurface) + return ccall((:cairo_surface_get_type, Cairo.libcairo), Cint, (Ptr{Nothing},), surface.ptr) +end diff --git a/CairoMakie/src/fonts.jl b/CairoMakie/src/fonts.jl deleted file mode 100644 index 254ff6fb6b0..00000000000 --- a/CairoMakie/src/fonts.jl +++ /dev/null @@ -1,46 +0,0 @@ -function set_font_matrix(ctx, matrix) - ccall((:cairo_set_font_matrix, Cairo.libcairo), Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), ctx.ptr, Ref(matrix)) -end - -function get_font_matrix(ctx) - matrix = Cairo.CairoMatrix() - ccall((:cairo_get_font_matrix, Cairo.libcairo), Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), ctx.ptr, Ref(matrix)) - return matrix -end - -function cairo_font_face_destroy(font_face) - ccall( - (:cairo_font_face_destroy, Cairo.libcairo), - Cvoid, (Ptr{Cvoid},), - font_face - ) -end - -function set_ft_font(ctx, font) - - font_face = ccall( - (:cairo_ft_font_face_create_for_ft_face, Cairo.libcairo), - Ptr{Cvoid}, (Makie.FreeTypeAbstraction.FT_Face, Cint), - font, 0 - ) - - ccall((:cairo_set_font_face, Cairo.libcairo), Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), ctx.ptr, font_face) - - return font_face -end - -""" -Finds a font that can represent the unicode character! -Returns Makie.defaultfont() if not representable! -""" -function best_font(c::Char, font = Makie.defaultfont()) - if Makie.FreeType.FT_Get_Char_Index(font, c) == 0 - for afont in Makie.alternativefonts() - if Makie.FreeType.FT_Get_Char_Index(afont, c) != 0 - return afont - end - end - return Makie.defaultfont() - end - return font -end diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index 00a07037160..37a41df81a1 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -6,9 +6,6 @@ # Types # ################################################################################ -function get_type(surface::Cairo.CairoSurface) - return ccall((:cairo_surface_get_type, Cairo.libcairo), Cint, (Ptr{Nothing},), surface.ptr) -end is_vector_backend(ctx::Cairo.CairoContext) = is_vector_backend(ctx.surface) @@ -18,15 +15,6 @@ function is_vector_backend(surf::Cairo.CairoSurface) end -"Convert a rendering type to a MIME type" -function to_mime(x::RenderType) - x == SVG && return MIME("image/svg+xml") - x == PDF && return MIME("application/pdf") - x == EPS && return MIME("application/postscript") - return MIME("image/png") -end -to_mime(x::CairoBackend) = to_mime(x.typ) - ################################################################################ # Rendering pipeline # ################################################################################ diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 12761f44285..f93cf3ca245 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -512,25 +512,6 @@ function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation, return end -struct CairoGlyph - index::Culong - x::Cdouble - y::Cdouble -end - -function show_glyph(ctx, glyph, x, y) - cg = Ref(CairoGlyph(glyph, x, y)) - ccall((:cairo_show_glyphs, Cairo.libcairo), - Nothing, (Ptr{Nothing}, Ptr{CairoGlyph}, Cint), - ctx.ptr, cg, 1) -end -function glyph_path(ctx, glyph::Culong, x, y) - cg = Ref(CairoGlyph(glyph, x, y)) - ccall((:cairo_glyph_path, Cairo.libcairo), - Nothing, (Ptr{Nothing}, Ptr{CairoGlyph}, Cint), - ctx.ptr, cg, 1) -end - ################################################################################ # Heatmap, Image # ################################################################################ diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 205a15824d7..1c02ca8cae0 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -9,123 +9,139 @@ const SCREEN_CONFIG = Ref(( function activate!(; screen_config...) Makie.set_screen_config!(SCREEN_CONFIG, screen_config) - Makie.register_backend!(CairoMakie) + Makie.set_active_backend!(CairoMakie) return end """ - struct Screen{S} <: AbstractScreen + struct Screen{S} <: MakieScreen A "screen" type for CairoMakie, which encodes a surface and a context which are used to draw a Scene. """ -struct Screen{S} <: Makie.AbstractScreen +struct Screen{S} <: Makie.MakieScreen scene::Scene surface::S context::Cairo.CairoContext + device_scaling_factor::Float64 + antialias::Int # cairo_antialias_t + image::Union{Nothing, Matrix{<: Colorant}} pane::Nothing # TODO: GtkWindowLeaf +end - px_per_unit::Float64 - pt_per_unit::Float64 - antialias::Int # cairo_antialias_t +Base.size(screen::Screen) = round.(Int, (screen.surface.width, screen.surface.height)) +# we render the scene directly, since we have +# no screen dependent state like in e.g. opengl +Base.insert!(screen::Screen, scene::Scene, plot) = nothing +function Base.delete!(screen::Screen, scene::Scene, plot::AbstractPlot) + # Currently, we rerender every time, so nothing needs + # to happen here. However, in the event that changes, + # e.g. if we integrate a Gtk window, we may need to + # do something here. end -function CairoBackend(path::String; px_per_unit=1, pt_per_unit=1, antialias = Cairo.ANTIALIAS_BEST) +function Base.show(io::IO, ::MIME"text/plain", screen::Screen{S}) where S + println(io, "Screen{$S} with surface:") + println(io, screen.surface) +end + +function path_to_type(path) ext = splitext(path)[2] - typ = if ext == ".png" - PNG + if ext == ".png" + return PNG elseif ext == ".svg" - SVG + return SVG elseif ext == ".pdf" - PDF + return PDF elseif ext == ".eps" - EPS + return EPS else error("Unsupported extension: $ext") end - CairoBackend(typ, path, px_per_unit, pt_per_unit, antialias) end -# we render the scene directly, since we have -# no screen dependent state like in e.g. opengl -Base.insert!(screen::Screen, scene::Scene, plot) = nothing +"Convert a rendering type to a MIME type" +function to_mime(type::RenderType) + type == SVG && return MIME("image/svg+xml") + type == PDF && return MIME("application/pdf") + type == EPS && return MIME("application/postscript") + return MIME("image/png") +end +to_mime(screen::Screen) = to_mime(screen.typ) + +"convert a mime to a RenderType" +function mime_to_rendertype(mime::Symbol)::RenderType + if mime == Symbol("image/png") + return PNG + elseif mime == Symbol("image/svg+xml") + return SVG + elseif mime == Symbol("application/pdf") + return PDF + elseif mime == Symbol("application/postscript") + return EPS + else + error("Unsupported mime: $mime") + end +end -function Base.show(io::IO, ::MIME"text/plain", screen::Screen{S}) where S - println(io, "Screen{$S} with surface:") - println(io, screen.surface) +function surface_from_output_type(mime::MIME{M}, io, w, h) where M + surface_from_output_type(M, io, w, h) end -# Default to ARGB Surface as backing device -# TODO: integrate Gtk into this, so we can have an interactive display -""" - Screen(scene::Scene; antialias = Cairo.ANTIALIAS_BEST) -Create a Screen backed by an image surface. -""" -function Screen(scene::Scene; device_scaling_factor = 1, antialias = Cairo.ANTIALIAS_BEST) - w, h = round.(Int, scene.camera.resolution[] .* device_scaling_factor) - surf = Cairo.CairoARGBSurface(w, h) +function surface_from_output_type(mime::Symbol, io, w, h) + surface_from_output_type(mime_to_rendertype(mime, io, w, h)) +end - # this sets a scaling factor on the lowest level that is "hidden" so its even - # enabled when the drawing space is reset for strokes - # that means it can be used to increase or decrease the image resolution - ccall((:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), - surf.ptr, device_scaling_factor, device_scaling_factor) +function surface_from_output_type(type::RenderType, io, w, h) + if type === SVG + return Cairo.CairoSVGSurface(io, w, h) + elseif type === PDF + return Cairo.CairoPDFSurface(io, w, h) + elseif type === EPS + return Cairo.CairoEPSSurface(io, w, h) + elseif type === PNG + return Cairo.CairoARGBSurface(w, h) + else + error("No available Cairo surface for mode $type") + end +end - ctx = Cairo.CairoContext(surf) - Cairo.set_antialias(ctx, antialias) - # Set the miter limit (when miter transitions to bezel) to mimic GLMakie behaviour - ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, 2.0) +######################################## +# Constructor # +######################################## - return Screen(scene, surf, ctx, nothing, px_per_unit, pt_per_unit, antialias) -end +# Default to ARGB Surface as backing device +# TODO: integrate Gtk into this, so we can have an interactive display """ - Screen( - scene::Scene, path::Union{String, IO}, mode::Symbol; - antialias = Cairo.ANTIALIAS_BEST - ) -Creates a Screen pointing to a given output path, with some rendering type defined by `mode`. + Screen(scene::Scene; screen_attributes...) + +Create a Screen backed by an image surface. """ -function Screen(scene::Scene, path::Union{String, IO}, mode::Symbol; device_scaling_factor = 1, antialias = Cairo.ANTIALIAS_BEST) +Screen(scene::Scene; screen_attributes...) = Screen(scene, nothing, PNG; screen_attributes...) +function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::Union{MIME, Symbol, RenderType}; screen_attributes...) # the surface size is the scene size scaled by the device scaling factor - w, h = round.(Int, scene.camera.resolution[] .* device_scaling_factor) - - if mode == :svg - surf = Cairo.CairoSVGSurface(path, w, h) - elseif mode == :pdf - surf = Cairo.CairoPDFSurface(path, w, h) - elseif mode == :eps - surf = Cairo.CairoEPSSurface(path, w, h) - elseif mode == :png - surf = Cairo.CairoARGBSurface(w, h) - else - error("No available Cairo surface for mode $mode") - end + w, h = round.(Int, size(scene) .* device_scaling_factor) + surface = surface_from_output_type(typ, io_or_path, w, h) + return Screen(scene, surface; screen_attributes...) +end - # this sets a scaling factor on the lowest level that is "hidden" so its even - # enabled when the drawing space is reset for strokes - # that means it can be used to increase or decrease the image resolution - ccall((:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), - surf.ptr, device_scaling_factor, device_scaling_factor) +function Screen(scene::Scene, image::Matrix{<: Colorant}; screen_attributes...) + img = Matrix{ARGB32}(undef, reverse(size(image))...) + # create an image surface to draw onto the image + surf = Cairo.CairoImageSurface(img) + return Screen(scene, surf) +end +function Screen(scene::Scene, surface::Cairo.CairoSurface; screen_attributes...) + # the surface size is the scene size scaled by the device scaling factor + surface_set_device_scale(surf, device_scaling_factor) ctx = Cairo.CairoContext(surf) Cairo.set_antialias(ctx, antialias) - # Set the miter limit (when miter transitions to bezel) to mimic GLMakie behaviour - ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, 2.0) - - return Screen(scene, surf, ctx, nothing) -end - -GeometryBasics.widths(screen::Screen) = round.(Int, (screen.surface.width, screen.surface.height)) - -function Base.delete!(screen::Screen, scene::Scene, plot::AbstractPlot) - # Currently, we rerender every time, so nothing needs - # to happen here. However, in the event that changes, - # e.g. if we integrate a Gtk window, we may need to - # do something here. + set_miter_limit(ctx, 2.0) + return Screen(scene, surf, ctx, nothing, px_per_unit, pt_per_unit, antialias) end - ######################################## # Fast colorbuffer for recording # ######################################## @@ -134,25 +150,18 @@ function Makie.colorbuffer(screen::Screen) # extract scene scene = screen.scene # get resolution - w, h = GeometryBasics.widths(screen) + w, h = size(screen) scene_w, scene_h = size(scene) - @assert w/scene_w ≈ h/scene_h + @assert w/scene_w ≈ h/scene_h device_scaling_factor = w/scene_w # preallocate an image matrix img = Matrix{ARGB32}(undef, w, h) # create an image surface to draw onto the image surf = Cairo.CairoImageSurface(img) - ccall((:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), - surf.ptr, device_scaling_factor, device_scaling_factor) - + screen = Screen(scene, surf) # draw the scene onto the image matrix - ctx = Cairo.CairoContext(surf) - ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, 2.0) - - scr = Screen(scene, surf, ctx, nothing) - - cairo_draw(scr, scene) + cairo_draw(screen, scene) # x and y are flipped - return the transpose return permutedims(img) diff --git a/CairoMakie/src/utils.jl b/CairoMakie/src/utils.jl index ab19f43d40c..4cf210f7f66 100644 --- a/CairoMakie/src/utils.jl +++ b/CairoMakie/src/utils.jl @@ -255,3 +255,19 @@ function Cairo.CairoPattern(color::Makie.AbstractPattern) cairopattern = Cairo.CairoPattern(cairoimage) return cairopattern end + +""" +Finds a font that can represent the unicode character! +Returns Makie.defaultfont() if not representable! +""" +function best_font(c::Char, font = Makie.defaultfont()) + if Makie.FreeType.FT_Get_Char_Index(font, c) == 0 + for afont in Makie.alternativefonts() + if Makie.FreeType.FT_Get_Char_Index(afont, c) != 0 + return afont + end + end + return Makie.defaultfont() + end + return font +end diff --git a/GLMakie/src/GLMakie.jl b/GLMakie/src/GLMakie.jl index f24345646a1..c120ef3c9a5 100644 --- a/GLMakie/src/GLMakie.jl +++ b/GLMakie/src/GLMakie.jl @@ -48,7 +48,7 @@ loadshader(name) = joinpath(SHADER_DIR, name) include("gl_backend.jl") function activate!() - Makie.register_backend!(GLMakie) + Makie.set_active_backend!(GLMakie) Makie.set_glyph_resolution!(Makie.High) end diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 65050ef0e8b..c22e75d8a48 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -3,10 +3,10 @@ const ZIndex = Int # ID, Area, clear, is visible, background color const ScreenArea = Tuple{ScreenID, Scene} -abstract type GLScreen <: AbstractScreen end +abstract type GLScreen <: MakieScreen end -mutable struct Screen <: GLScreen - glscreen::GLFW.Window +mutable struct Screen{GLWindow} <: GLScreen + glscreen::GLWindow shader_cache::GLAbstraction.ShaderCache framebuffer::GLFramebuffer rendertask::RefValue{Task} @@ -21,7 +21,7 @@ mutable struct Screen <: GLScreen window_open::Observable{Bool} function Screen( - glscreen::GLFW.Window, + glscreen::GLWindow, shader_cache::GLAbstraction.ShaderCache, framebuffer::GLFramebuffer, rendertask::RefValue{Task}, @@ -31,7 +31,7 @@ mutable struct Screen <: GLScreen postprocessors::Vector{PostProcessor}, cache::Dict{UInt64, RenderObject}, cache2plot::Dict{UInt32, AbstractPlot}, - ) + ) where {GLWindow} s = size(framebuffer) return new( glscreen, shader_cache, framebuffer, rendertask, screen2scene, @@ -42,16 +42,19 @@ mutable struct Screen <: GLScreen end end -GeometryBasics.widths(x::Screen) = size(x.framebuffer) pollevents(::GLScreen) = nothing function pollevents(screen::Screen) ShaderAbstractions.switch_context!(screen.glscreen) notify(screen.render_tick) GLFW.PollEvents() end + Base.wait(x::Screen) = isassigned(x.rendertask) && wait(x.rendertask[]) Base.wait(scene::Scene) = wait(Makie.getscreen(scene)) + Base.show(io::IO, screen::Screen) = print(io, "GLMakie.Screen(...)") + +Base.isopen(x::Screen) = isopen(x.glscreen) Base.size(x::Screen) = size(x.framebuffer) function Makie.insertplots!(screen::GLScreen, scene::Scene) @@ -104,7 +107,6 @@ function Base.delete!(screen::Screen, scene::Scene) end end end - return end @@ -276,7 +278,6 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma end -Base.isopen(x::Screen) = isopen(x.glscreen) function Base.push!(screen::GLScreen, scene::Scene, robj) # filter out gc'ed elements filter!(screen.screen2scene) do (k, v) @@ -352,9 +353,10 @@ function display_loading_image(screen::Screen) end end - function Screen(; - resolution = (10, 10), visible = true, title = WINDOW_CONFIG.title[], + resolution = (10, 10), + visible = true, + title = WINDOW_CONFIG.title[], start_renderloop = true, kw_args... ) @@ -441,16 +443,6 @@ function Screen(; return screen end -function Screen(f, resolution) - screen = Screen(resolution = resolution, visible = false, start_renderloop=false) - try - return f(screen) - finally - destroy!(screen) - end -end - - function refreshwindowcb(window, screen) ShaderAbstractions.switch_context!(screen.glscreen) screen.render_tick[] = nothing diff --git a/MakieCore/src/types.jl b/MakieCore/src/types.jl index bcc5a1778ce..11cf3f68be3 100644 --- a/MakieCore/src/types.jl +++ b/MakieCore/src/types.jl @@ -9,7 +9,6 @@ abstract type Transformable end abstract type AbstractPlot{Typ} <: Transformable end abstract type AbstractScene <: Transformable end abstract type ScenePlot{Typ} <: AbstractPlot{Typ} end -abstract type AbstractScreen <: AbstractDisplay end const SceneLike = Union{AbstractScene, ScenePlot} diff --git a/RPRMakie/src/RPRMakie.jl b/RPRMakie/src/RPRMakie.jl index 347d41f2f26..5907ed9da81 100644 --- a/RPRMakie/src/RPRMakie.jl +++ b/RPRMakie/src/RPRMakie.jl @@ -37,7 +37,7 @@ function activate!(; iterations=200, resource=RENDER_RESOURCE[], plugin=RENDER_P NUM_ITERATIONS[] = iterations RENDER_RESOURCE[] = resource RENDER_PLUGIN[] = plugin - Makie.register_backend!(RPRMakie) + Makie.set_active_backend!(RPRMakie) return end diff --git a/RPRMakie/src/scene.jl b/RPRMakie/src/scene.jl index fbd52b1cf8a..f8fee724fdd 100644 --- a/RPRMakie/src/scene.jl +++ b/RPRMakie/src/scene.jl @@ -142,7 +142,7 @@ end struct RPRBackend <: Makie.AbstractBackend end -mutable struct RPRScreen <: Makie.AbstractScreen +mutable struct RPRScreen <: Makie.MakieScreen context::RPR.Context matsys::RPR.MaterialSystem framebuffer1::RPR.FrameBuffer diff --git a/WGLMakie/src/WGLMakie.jl b/WGLMakie/src/WGLMakie.jl index d2bfcb0e4ad..a11d7bc9851 100644 --- a/WGLMakie/src/WGLMakie.jl +++ b/WGLMakie/src/WGLMakie.jl @@ -57,7 +57,7 @@ Set fps (frames per second) to a higher number for smoother animations, or to a """ function activate!(; fps=30) SCREEN_CONFIG[] = merge(SCREEN_CONFIG[], (fps=fps,)) - Makie.register_backend!(WGLMakie) + Makie.set_active_backend!(WGLMakie) Makie.set_glyph_resolution!(Makie.Low) return end diff --git a/WGLMakie/src/display.jl b/WGLMakie/src/display.jl index 4f640de69cb..802c075895a 100644 --- a/WGLMakie/src/display.jl +++ b/WGLMakie/src/display.jl @@ -68,11 +68,11 @@ function Makie.backend_showable(::WGLBackend, ::T, scene::Scene) where {T<:MIME} return T in WEB_MIMES end -struct WebDisplay <: Makie.AbstractScreen +struct WebDisplay <: Makie.MakieScreen three::Base.RefValue{ThreeDisplay} display::Any end - + GeometryBasics.widths(screen::WebDisplay) = GeometryBasics.widths(screen.three[]) function Makie.backend_display(::WGLBackend, scene::Scene; kw...) diff --git a/WGLMakie/src/three_plot.jl b/WGLMakie/src/three_plot.jl index 599e0153866..6521e489196 100644 --- a/WGLMakie/src/three_plot.jl +++ b/WGLMakie/src/three_plot.jl @@ -1,4 +1,4 @@ -struct ThreeDisplay <: Makie.AbstractScreen +struct ThreeDisplay <: Makie.MakieScreen session::JSServe.Session end diff --git a/src/Makie.jl b/src/Makie.jl index e96f3b1c167..98d80744e21 100644 --- a/src/Makie.jl +++ b/src/Makie.jl @@ -65,7 +65,7 @@ using Base.Iterators: repeated, drop import Base: getindex, setindex!, push!, append!, parent, get, get!, delete!, haskey using Observables: listeners, to_value, notify -using MakieCore: SceneLike, AbstractScreen, ScenePlot, AbstractScene, AbstractPlot, Transformable, Attributes, Combined, Theme, Plot +using MakieCore: SceneLike, MakieScreen, ScenePlot, AbstractScene, AbstractPlot, Transformable, Attributes, Combined, Theme, Plot using MakieCore: Heatmap, Image, Lines, LineSegments, Mesh, MeshScatter, Scatter, Surface, Text, Volume using MakieCore: ConversionTrait, NoConversion, PointBased, SurfaceLike, ContinuousSurface, DiscreteSurface, VolumeLike using MakieCore: Key, @key_str, Automatic, automatic, @recipe @@ -176,7 +176,7 @@ export BezierPath, MoveTo, LineTo, CurveTo, EllipticalArc, ClosePath export help, help_attributes, help_arguments # Abstract/Concrete scene + plot types -export AbstractScene, SceneLike, Scene, AbstractScreen +export AbstractScene, SceneLike, Scene, MakieScreen export AbstractPlot, Combined, Atomic, OldAxis # Theming, working with Plots diff --git a/src/display.jl b/src/display.jl index 595f37f264a..0ded702eba7 100644 --- a/src/display.jl +++ b/src/display.jl @@ -1,17 +1,34 @@ @enum ImageStorageFormat JuliaNative GLNative +""" + MakieScreen(scene::Scene; screen_attributes...) + MakieScreen(scene::Scene, io::IO; screen_attributes...) + +Interface: +```julia +# Needs to be overload: +size(screen) # Size in pixel + +# Optional +wait(screen) # waits as long window is open + +# Provided by Makie: +push_screen!(scene, screen) +``` +""" +abstract type MakieScreen <: AbstractDisplay end + update_state_before_display!(_) = nothing function backend_display end function backend_show end - """ Current backend """ const current_backend = Ref{Union{Missing, Module}}(missing) -function register_backend!(backend::Module) +function set_active_backend!(backend::Module) current_backend[] = backend return end @@ -72,7 +89,6 @@ function backend_display(::Missing, ::Scene; screen_kw...) end - """ @@ -128,10 +144,9 @@ function Base.show(io::IO, ::MIME"text/plain", scene::Scene) end function Base.show(io::IO, m::MIME, figlike::FigureLike) - ioc = IOContext(io, :full_fidelity => true) update_state_before_display!(figlike) scene = get_scene(figlike) - backend_show(current_backend[].Screen(scene), ioc, m, scene) + backend_show(current_backend[].Screen(scene, io), m, scene) return end @@ -280,7 +295,7 @@ struct VideoStream end """ - VideoStream(scene::Scene; framerate = 24, visible=false, connect=false, backend_kw...) + VideoStream(scene::Scene; framerate = 24, visible=false, connect=false, screen_kw...) Returns a stream and a buffer that you can use, which don't allocate for new frames. Use [`recordframe!(stream)`](@ref) to add new video frames to the stream, and @@ -289,7 +304,7 @@ Use [`recordframe!(stream)`](@ref) to add new video frames to the stream, and * visible=false: make window visible or not * connect=false: connect window events or not """ -function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, connect=false, backend_kw...) +function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, connect=false, screen_kw...) #codec = `-codec:v libvpx -quality good -cpu-used 0 -b:v 500k -qmin 10 -qmax 42 -maxrate 500k -bufsize 1000k -threads 8` dir = mktempdir() path = joinpath(dir, "$(gensym(:video)).mkv") @@ -509,7 +524,7 @@ end # This has to be overloaded by the backend for its screen type. -function colorbuffer(x::AbstractScreen) +function colorbuffer(x::MakieScreen) error("colorbuffer not implemented for screen $(typeof(x))") end diff --git a/src/interaction/events.jl b/src/interaction/events.jl index abd97bf4589..c50db19b3e6 100644 --- a/src/interaction/events.jl +++ b/src/interaction/events.jl @@ -34,8 +34,8 @@ function connect_screen(scene::Scene, screen) return end -to_native(window::AbstractScreen) = error("to_native(window) not implemented for $(typeof(window)).") -disconnect!(window::AbstractScreen, signal) = disconnect!(to_native(window), signal) +to_native(window::MakieScreen) = error("to_native(window) not implemented for $(typeof(window)).") +disconnect!(window::MakieScreen, signal) = disconnect!(to_native(window), signal) function disconnect_screen(scene::Scene, screen) delete_screen!(scene, screen) diff --git a/src/scenes.jl b/src/scenes.jl index 04a0c909f6c..6143e286ed1 100644 --- a/src/scenes.jl +++ b/src/scenes.jl @@ -106,7 +106,7 @@ mutable struct Scene <: AbstractScene """ The Screens which the Scene is displayed to. """ - current_screens::Vector{AbstractScreen} + current_screens::Vector{MakieScreen} # Attributes backgroundcolor::Observable{RGBAf} @@ -154,7 +154,7 @@ function Scene(; plots::Vector{AbstractPlot} = AbstractPlot[], theme::Attributes = Attributes(), children::Vector{Scene} = Scene[], - current_screens::Vector{AbstractScreen} = AbstractScreen[], + current_screens::Vector{MakieScreen} = MakieScreen[], parent = nothing, visible = Observable(true), ssao = SSAO(), @@ -410,10 +410,10 @@ function Base.push!(scene::Scene, plot::AbstractPlot) end end -function Base.delete!(screen::AbstractScreen, ::Scene, ::AbstractPlot) +function Base.delete!(screen::MakieScreen, ::Scene, ::AbstractPlot) @warn "Deleting plots not implemented for backend: $(typeof(screen))" end -function Base.delete!(screen::AbstractScreen, ::Scene) +function Base.delete!(screen::MakieScreen, ::Scene) # This may not be necessary for every backed @debug "Deleting scenes not implemented for backend: $(typeof(screen))" end From 6545947c579c9201381b5910b3dce8d1dc478416 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 27 Sep 2022 22:37:17 +0200 Subject: [PATCH 16/56] get CairoMakie & GLMakie to work --- CairoMakie/src/CairoMakie.jl | 12 +-- CairoMakie/src/cairo-extension.jl | 11 ++- CairoMakie/src/display.jl | 59 ++++++++++-- CairoMakie/src/infrastructure.jl | 17 +--- CairoMakie/src/precompiles.jl | 3 +- CairoMakie/src/screen.jl | 97 +++++++++++-------- GLMakie/src/GLMakie.jl | 11 +-- GLMakie/src/display.jl | 43 +-------- GLMakie/src/precompiles.jl | 3 +- GLMakie/src/rendering.jl | 50 ++-------- GLMakie/src/screen.jl | 58 +++++++++-- GLMakie/test/glmakie_refimages.jl | 4 +- GLMakie/test/runtests.jl | 8 +- GLMakie/test/unit_tests.jl | 5 +- MakieCore/src/types.jl | 24 +++++ RPRMakie/examples/sea_cables.jl | 1 - RPRMakie/src/scene.jl | 10 +- ReferenceTests/src/tests/updating.jl | 3 + WGLMakie/src/WGLMakie.jl | 1 - WGLMakie/src/display.jl | 65 +++---------- gallery.jl | 1 - metrics/ttfp/benchmark-ttfp.jl | 2 - src/Makie.jl | 2 +- src/deprecated.jl | 11 +++ src/display.jl | 138 ++++++++++++++------------- 25 files changed, 329 insertions(+), 310 deletions(-) create mode 100644 src/deprecated.jl diff --git a/CairoMakie/src/CairoMakie.jl b/CairoMakie/src/CairoMakie.jl index 13a51cfcd18..b670147c1c2 100644 --- a/CairoMakie/src/CairoMakie.jl +++ b/CairoMakie/src/CairoMakie.jl @@ -8,9 +8,8 @@ import Cairo using Makie: Scene, Lines, Text, Image, Heatmap, Scatter, @key_str, broadcast_foreach using Makie: convert_attribute, @extractvalue, LineSegments, to_ndim, NativeFont -using Makie: @info, @get_attribute, Combined +using Makie: @info, @get_attribute, Combined, MakieScreen using Makie: to_value, to_colormap, extrema_nan -using Makie: inline! using Makie.Observables using Makie: spaces, is_data_space, is_pixel_space, is_relative_space, is_clip_space using Makie: numbers_to_colors @@ -35,13 +34,6 @@ function __init__() activate!() end -function display_path(type::String) - if !(type in ("svg", "png", "pdf", "eps")) - error("Only \"svg\", \"png\", \"eps\" and \"pdf\" are allowed for `type`. Found: $(type)") - end - return joinpath(@__DIR__, "display." * type) -end - -include("precompiles.jl") +# include("precompiles.jl") end diff --git a/CairoMakie/src/cairo-extension.jl b/CairoMakie/src/cairo-extension.jl index 51c7584a81e..6f96a926d3c 100644 --- a/CairoMakie/src/cairo-extension.jl +++ b/CairoMakie/src/cairo-extension.jl @@ -58,13 +58,18 @@ function surface_set_device_scale(surf, device_x_scale, device_y_scale=device_x_ ccall( (:cairo_surface_set_device_scale, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble, Cdouble), - surf.ptr, device_x_scale, device_scaling_factor) + surf.ptr, device_x_scale, device_y_scale) end function set_miter_limit(ctx, limit) ccall((:cairo_set_miter_limit, Cairo.libcairo), Cvoid, (Ptr{Nothing}, Cdouble), ctx.ptr, limit) end -function get_type(surface::Cairo.CairoSurface) - return ccall((:cairo_surface_get_type, Cairo.libcairo), Cint, (Ptr{Nothing},), surface.ptr) +function get_render_type(surface::Cairo.CairoSurface) + typ = ccall((:cairo_surface_get_type, Cairo.libcairo), Cint, (Ptr{Nothing},), surface.ptr) + typ == Cairo.CAIRO_SURFACE_TYPE_PDF && return PDF + typ == Cairo.CAIRO_SURFACE_TYPE_PS && return EPS + typ == Cairo.CAIRO_SURFACE_TYPE_SVG && return SVG + typ == Cairo.CAIRO_SURFACE_TYPE_IMAGE && return IMAGE + error("Unsupported surface type: $(typ)") end diff --git a/CairoMakie/src/display.jl b/CairoMakie/src/display.jl index 5db479d3c01..3c9936bd8d3 100644 --- a/CairoMakie/src/display.jl +++ b/CairoMakie/src/display.jl @@ -3,19 +3,57 @@ # Backend interface to Makie # ######################################### -function Makie.backend_display(screen::Screen, scene::Scene) - return open(x.path, "w") do io - Makie.backend_show(screen, io, to_mime(x), scene) +""" + tryrun(cmd::Cmd) +Try to run a command. Return `true` if `cmd` runs and is successful (exits with a code of `0`). +Return `false` otherwise. +""" +function tryrun(cmd::Cmd) + try + return success(cmd) + catch e + return false end end -function Makie.backend_show(screen::Screen, io::IO, ::MIME"image/svg+xml", scene::Scene) +function openurl(url::String) + if Sys.isapple() + tryrun(`open $url`) && return + elseif Sys.iswindows() + tryrun(`powershell.exe start $url`) && return + elseif Sys.isunix() + tryrun(`xdg-open $url`) && return + tryrun(`gnome-open $url`) && return + end + tryrun(`python -mwebbrowser $(url)`) && return + # our last hope + tryrun(`python3 -mwebbrowser $(url)`) && return + @warn("Can't find a way to open a browser, open $(url) manually!") +end + +function display_path(type::String) + if !(type in ("svg", "png", "pdf", "eps")) + error("Only \"svg\", \"png\", \"eps\" and \"pdf\" are allowed for `type`. Found: $(type)") + end + return abspath(joinpath(@__DIR__, "display." * type)) +end + +function Base.display(screen::Screen{IMAGE}, scene::Scene; connect=false) + path = display_path("png") + cairo_draw(screen, scene) + Cairo.write_to_png(screen.surface, path) + if screen.visible + openurl("file:///" * path) + end +end + +function Makie.backend_show(screen::Screen{SVG}, io::IO, ::MIME"image/svg+xml", scene::Scene) cairo_draw(screen, scene) Cairo.flush(screen.surface) Cairo.finish(screen.surface) - svg = String(take!(screen.io)) + svg = String(take!(Makie.raw_io(screen.surface.stream))) # for some reason, in the svg, surfaceXXX ids keep counting up, # even with the very same figure drawn again and again @@ -43,20 +81,25 @@ function Makie.backend_show(screen::Screen, io::IO, ::MIME"image/svg+xml", scene return screen end -function Makie.backend_show(screen::Screen, io::IO, ::MIME"application/pdf", scene::Scene) +function Makie.backend_show(screen::Screen{PDF}, io::IO, ::MIME"application/pdf", scene::Scene) cairo_draw(screen, scene) Cairo.finish(screen.surface) return screen end -function Makie.backend_show(screen::Screen, io::IO, ::MIME"application/postscript", scene::Scene) +function Makie.backend_show(screen::Screen{EPS}, io::IO, ::MIME"application/postscript", scene::Scene) cairo_draw(screen, scene) Cairo.finish(screen.surface) return screen end -function Makie.backend_show(screen::Screen, io::IO, ::MIME"image/png", scene::Scene) +function Makie.backend_show(screen::Screen{IMAGE}, io::IO, ::MIME"image/png", scene::Scene) cairo_draw(screen, scene) Cairo.write_to_png(screen.surface, io) return screen end + +Makie.backend_showable(::Type{Screen}, ::MIME"image/svg+xml", scene::Scene) = true +Makie.backend_showable(::Type{Screen}, ::MIME"application/pdf", scene::Scene) = true +Makie.backend_showable(::Type{Screen}, ::MIME"application/postscript", scene::Scene) = true +Makie.backend_showable(::Type{Screen}, ::MIME"image/png", scene::Scene) = true diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index 37a41df81a1..49cd685ffc4 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -7,12 +7,6 @@ ################################################################################ -is_vector_backend(ctx::Cairo.CairoContext) = is_vector_backend(ctx.surface) - -function is_vector_backend(surf::Cairo.CairoSurface) - typ = get_type(surf) - return typ in (Cairo.CAIRO_SURFACE_TYPE_PDF, Cairo.CAIRO_SURFACE_TYPE_PS, Cairo.CAIRO_SURFACE_TYPE_SVG) -end ################################################################################ @@ -104,7 +98,7 @@ function draw_background(screen::Screen, scene::Scene) cr = screen.context Cairo.save(cr) if scene.clear[] - bg = to_color(theme(scene, :backgroundcolor)[]) + bg = scene.backgroundcolor[] Cairo.set_source_rgba(cr, red(bg), green(bg), blue(bg), alpha(bg)); r = pixelarea(scene)[] Cairo.rectangle(cr, origin(r)..., widths(r)...) # background @@ -167,12 +161,3 @@ end function draw_atomic(::Scene, ::Screen, x) @warn "$(typeof(x)) is not supported by cairo right now" end - -function clear(screen::Screen) - ctx = screen.ctx - Cairo.save(ctx) - Cairo.set_operator(ctx, Cairo.OPERATOR_SOURCE) - Cairo.set_source_rgba(ctx, rgbatuple(screen.scene[:backgroundcolor])...); - Cairo.paint(ctx) - Cairo.restore(ctx) -end diff --git a/CairoMakie/src/precompiles.jl b/CairoMakie/src/precompiles.jl index 42a65f8cb25..e530dcb09e4 100644 --- a/CairoMakie/src/precompiles.jl +++ b/CairoMakie/src/precompiles.jl @@ -3,7 +3,7 @@ using SnoopPrecompile macro compile(block) return quote figlike = $(esc(block)) - screen = Makie.backend_display(Makie.get_scene(figlike)) + screen = display(Makie.get_scene(figlike)) Makie.colorbuffer(screen) end end @@ -11,7 +11,6 @@ end let @precompile_all_calls begin CairoMakie.activate!() - CairoMakie.inline!(false) base_path = normpath(joinpath(dirname(pathof(Makie)), "..", "precompile")) shared_precompile = joinpath(base_path, "shared-precompile.jl") include(shared_precompile) diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 1c02ca8cae0..810e45fcba1 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -1,14 +1,24 @@ -@enum RenderType SVG PNG PDF EPS +@enum RenderType SVG IMAGE PDF EPS +""" + Screen(; + type = IMAGE, + px_per_unit = 1.0, + pt_per_unit = 0.75, + antialias = Cairo.ANTIALIAS_BEST + ) + + +""" const SCREEN_CONFIG = Ref(( - type = "png", + type = IMAGE, px_per_unit = 1.0, pt_per_unit = 0.75, antialias = Cairo.ANTIALIAS_BEST )) -function activate!(; screen_config...) - Makie.set_screen_config!(SCREEN_CONFIG, screen_config) +function activate!(; screen_attributes...) + Makie.set_screen_config!(SCREEN_CONFIG, screen_attributes) Makie.set_active_backend!(CairoMakie) return end @@ -18,14 +28,13 @@ end A "screen" type for CairoMakie, which encodes a surface and a context which are used to draw a Scene. """ -struct Screen{S} <: Makie.MakieScreen +struct Screen{SurfaceRenderType} <: Makie.MakieScreen scene::Scene - surface::S + surface::Cairo.CairoSurface context::Cairo.CairoContext device_scaling_factor::Float64 antialias::Int # cairo_antialias_t - image::Union{Nothing, Matrix{<: Colorant}} - pane::Nothing # TODO: GtkWindowLeaf + visible::Bool end Base.size(screen::Screen) = round.(Int, (screen.surface.width, screen.surface.height)) @@ -40,25 +49,28 @@ function Base.delete!(screen::Screen, scene::Scene, plot::AbstractPlot) end function Base.show(io::IO, ::MIME"text/plain", screen::Screen{S}) where S - println(io, "Screen{$S} with surface:") - println(io, screen.surface) + println(io, "CairoMakie.Screen{$S}") end -function path_to_type(path) - ext = splitext(path)[2] - if ext == ".png" - return PNG - elseif ext == ".svg" +function Base.convert(::Type{RenderType}, type::String) + if type == "png" + return IMAGE + elseif type == "svg" return SVG - elseif ext == ".pdf" + elseif type == "pdf" return PDF - elseif ext == ".eps" + elseif type == "eps" return EPS else - error("Unsupported extension: $ext") + error("Unsupported cairo render type: $type") end end +function path_to_type(path) + type = splitext(path)[2][2:end] + return convert(RenderType, type) +end + "Convert a rendering type to a MIME type" function to_mime(type::RenderType) type == SVG && return MIME("image/svg+xml") @@ -71,7 +83,7 @@ to_mime(screen::Screen) = to_mime(screen.typ) "convert a mime to a RenderType" function mime_to_rendertype(mime::Symbol)::RenderType if mime == Symbol("image/png") - return PNG + return IMAGE elseif mime == Symbol("image/svg+xml") return SVG elseif mime == Symbol("application/pdf") @@ -88,7 +100,7 @@ function surface_from_output_type(mime::MIME{M}, io, w, h) where M end function surface_from_output_type(mime::Symbol, io, w, h) - surface_from_output_type(mime_to_rendertype(mime, io, w, h)) + surface_from_output_type(mime_to_rendertype(mime), io, w, h) end function surface_from_output_type(type::RenderType, io, w, h) @@ -98,7 +110,7 @@ function surface_from_output_type(type::RenderType, io, w, h) return Cairo.CairoPDFSurface(io, w, h) elseif type === EPS return Cairo.CairoEPSSurface(io, w, h) - elseif type === PNG + elseif type === IMAGE return Cairo.CairoARGBSurface(w, h) else error("No available Cairo surface for mode $type") @@ -117,9 +129,9 @@ end Create a Screen backed by an image surface. """ -Screen(scene::Scene; screen_attributes...) = Screen(scene, nothing, PNG; screen_attributes...) +Screen(scene::Scene; screen_attributes...) = Screen(scene, nothing, IMAGE; screen_attributes...) -function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::Union{MIME, Symbol, RenderType}; screen_attributes...) +function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::Union{MIME, Symbol, RenderType}; device_scaling_factor=1.0, screen_attributes...) # the surface size is the scene size scaled by the device scaling factor w, h = round.(Int, size(scene) .* device_scaling_factor) surface = surface_from_output_type(typ, io_or_path, w, h) @@ -129,17 +141,26 @@ end function Screen(scene::Scene, image::Matrix{<: Colorant}; screen_attributes...) img = Matrix{ARGB32}(undef, reverse(size(image))...) # create an image surface to draw onto the image - surf = Cairo.CairoImageSurface(img) - return Screen(scene, surf) + surface = Cairo.CairoImageSurface(img) + screen = Screen(scene, surface; screen_attributes...) + screen.surface_image = img + return screen +end + +function Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_attributes...) + # create an image surface to draw onto the image + img = Matrix{ARGB32}(undef, size(scene)...) + surface = Cairo.CairoImageSurface(img) + return Screen(scene, surface; screen_attributes...) end -function Screen(scene::Scene, surface::Cairo.CairoSurface; screen_attributes...) +function Screen(scene::Scene, surface::Cairo.CairoSurface; device_scaling_factor=1.0, antialias=Cairo.ANTIALIAS_BEST, visible=true, start_renderloop=false) # the surface size is the scene size scaled by the device scaling factor - surface_set_device_scale(surf, device_scaling_factor) - ctx = Cairo.CairoContext(surf) + surface_set_device_scale(surface, device_scaling_factor) + ctx = Cairo.CairoContext(surface) Cairo.set_antialias(ctx, antialias) set_miter_limit(ctx, 2.0) - return Screen(scene, surf, ctx, nothing, px_per_unit, pt_per_unit, antialias) + return Screen{get_render_type(surface)}(scene, surface, ctx, device_scaling_factor, antialias, visible) end ######################################## @@ -151,18 +172,18 @@ function Makie.colorbuffer(screen::Screen) scene = screen.scene # get resolution w, h = size(screen) - scene_w, scene_h = size(scene) - - @assert w/scene_w ≈ h/scene_h - device_scaling_factor = w/scene_w # preallocate an image matrix img = Matrix{ARGB32}(undef, w, h) # create an image surface to draw onto the image surf = Cairo.CairoImageSurface(img) - screen = Screen(scene, surf) - # draw the scene onto the image matrix - cairo_draw(screen, scene) + screen = Screen(scene, surf; device_scaling_factor=screen.device_scaling_factor, antialias=screen.antialias) + return colorbuffer(screen) +end - # x and y are flipped - return the transpose - return permutedims(img) +function Makie.colorbuffer(screen::Screen{IMAGE}) + cairo_draw(screen, screen.scene) + return permutedims(screen.surface.data) end + +is_vector_backend(ctx::Cairo.CairoContext) = is_vector_backend(ctx.surface) +is_vector_backend(surf::Cairo.CairoSurface) = get_render_type(surf) in (PDF, EPS, SVG) diff --git a/GLMakie/src/GLMakie.jl b/GLMakie/src/GLMakie.jl index c120ef3c9a5..e75537b7c2d 100644 --- a/GLMakie/src/GLMakie.jl +++ b/GLMakie/src/GLMakie.jl @@ -14,7 +14,7 @@ using Makie: Scene, Lines, Text, Image, Heatmap, Scatter using Makie: convert_attribute, @extractvalue, LineSegments using Makie: @get_attribute, to_value, to_colormap, extrema_nan using Makie: ClosedInterval, (..) -using Makie: inline!, to_native +using Makie: to_native using Makie: spaces, is_data_space, is_pixel_space, is_relative_space, is_clip_space import Makie: to_font, glyph_uv_width!, el32convert, Shape, CIRCLE, RECTANGLE, ROUNDED_RECTANGLE, DISTANCEFIELD, TRIANGLE import Makie: RelocatableFolders @@ -47,17 +47,10 @@ loadshader(name) = joinpath(SHADER_DIR, name) # don't put this into try catch, to not mess with normal errors include("gl_backend.jl") -function activate!() - Makie.set_active_backend!(GLMakie) - Makie.set_glyph_resolution!(Makie.High) -end - function __init__() activate!() end -export set_window_config! - -include("precompiles.jl") +# include("precompiles.jl") end diff --git a/GLMakie/src/display.jl b/GLMakie/src/display.jl index 7b9facd4d3c..09c0cfe5471 100644 --- a/GLMakie/src/display.jl +++ b/GLMakie/src/display.jl @@ -1,14 +1,7 @@ -function Makie.backend_display(::GLBackend, scene::Scene; start_renderloop=true, visible=true, connect=true) - screen = singleton_screen(size(scene); start_renderloop=start_renderloop, visible=visible) - ShaderAbstractions.switch_context!(screen.glscreen) - display_loading_image(screen) - Makie.backend_display(screen, scene; connect=connect) - return screen -end - -function Makie.backend_display(screen::Screen, scene::Scene; connect=true) +function Base.display(screen::Screen, scene::Scene; connect=true) ShaderAbstractions.switch_context!(screen.glscreen) empty!(screen) + resize!(screen, size(scene)...) # So, the GLFW window events are not guarantee to fire # when we close a window, so we ensure this here! window_open = events(scene).window_open @@ -21,35 +14,3 @@ function Makie.backend_display(screen::Screen, scene::Scene; connect=true) pollevents(screen) return screen end - -function Base.display(screen::Screen, fig::Makie.FigureLike) - scene = Makie.get_scene(fig) - Base.resize!(screen, size(scene)...) - Makie.backend_display(screen, scene) - return screen -end - -""" - scene2image(scene::Scene) - -Buffers the `scene` in an image buffer. -""" -function scene2image(scene::Scene) - screen = singleton_screen(size(scene), visible=false, start_renderloop=false) - ShaderAbstractions.switch_context!(screen.glscreen) - empty!(screen) - insertplots!(screen, scene) - return Makie.colorbuffer(screen) -end - -function Makie.backend_show(::GLBackend, io::IO, m::MIME"image/png", scene::Scene) - img = scene2image(scene) - FileIO.save(FileIO.Stream{FileIO.format"PNG"}(Makie.raw_io(io)), img) - return -end - -function Makie.backend_show(::GLBackend, io::IO, m::MIME"image/jpeg", scene::Scene) - img = scene2image(scene) - FileIO.save(FileIO.Stream{FileIO.format"JPEG"}(Makie.raw_io(io)), img) - return -end diff --git a/GLMakie/src/precompiles.jl b/GLMakie/src/precompiles.jl index 2f9ce6e4ad8..62b93a8236c 100644 --- a/GLMakie/src/precompiles.jl +++ b/GLMakie/src/precompiles.jl @@ -5,7 +5,7 @@ macro compile(block) let figlike = $(esc(block)) screen = Screen(visible=false) - Makie.backend_display(screen, Makie.get_scene(figlike)) + display(screen, Makie.get_scene(figlike)) Makie.colorbuffer(screen) close(screen) end @@ -15,7 +15,6 @@ end let @precompile_all_calls begin GLMakie.activate!() - GLMakie.inline!(false) base_path = normpath(joinpath(dirname(pathof(Makie)), "..", "precompile")) shared_precompile = joinpath(base_path, "shared-precompile.jl") include(shared_precompile) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 288eb064c18..77ccaf2f36a 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -1,8 +1,8 @@ # TODO add render_tick event to scene events function vsynced_renderloop(screen) - while isopen(screen) && !WINDOW_CONFIG.exit_renderloop[] + while isopen(screen) && !SCREEN_CONFIG[].exit_renderloop pollevents(screen) # GLFW poll - if WINDOW_CONFIG.pause_rendering[] + if SCREEN_CONFIG[].pause_rendering sleep(0.1) else ShaderAbstractions.switch_context!(screen.glscreen) @@ -13,12 +13,12 @@ function vsynced_renderloop(screen) end end -function fps_renderloop(screen::Screen, framerate=WINDOW_CONFIG.framerate[]) +function fps_renderloop(screen::Screen, framerate=SCREEN_CONFIG[].framerate) time_per_frame = 1.0 / framerate - while isopen(screen) && !WINDOW_CONFIG.exit_renderloop[] + while isopen(screen) && !SCREEN_CONFIG[].exit_renderloop t = time_ns() pollevents(screen) # GLFW poll - if WINDOW_CONFIG.pause_rendering[] + if SCREEN_CONFIG[].pause_rendering sleep(0.1) else ShaderAbstractions.switch_context!(screen.glscreen) @@ -35,10 +35,10 @@ function fps_renderloop(screen::Screen, framerate=WINDOW_CONFIG.framerate[]) end end -function renderloop(screen; framerate=WINDOW_CONFIG.framerate[]) +function renderloop(screen; framerate=SCREEN_CONFIG[].framerate) isopen(screen) || error("Screen most be open to run renderloop!") try - if WINDOW_CONFIG.vsync[] + if SCREEN_CONFIG[].vsync GLFW.SwapInterval(1) vsynced_renderloop(screen) else @@ -54,40 +54,10 @@ function renderloop(screen; framerate=WINDOW_CONFIG.framerate[]) end end -const WINDOW_CONFIG = (renderloop = Ref{Function}(renderloop), - vsync = Ref(false), - framerate = Ref(30.0), - float = Ref(false), - pause_rendering = Ref(false), - focus_on_show = Ref(false), - decorated = Ref(true), - title = Ref("Makie"), - exit_renderloop = Ref(false),) - -""" - set_window_config!(; - renderloop = renderloop, - vsync = false, - framerate = 30.0, - float = false, - pause_rendering = false, - focus_on_show = false, - decorated = true, - title = "Makie" - ) -Updates the screen configuration, will only go into effect after closing the current -window and opening a new one! -""" -function set_window_config!(; kw...) - for (key, value) in kw - getfield(WINDOW_CONFIG, key)[] = value - end -end - function setup!(screen) glEnable(GL_SCISSOR_TEST) if isopen(screen) - glScissor(0, 0, widths(screen)...) + glScissor(0, 0, size(screen)...) glClearColor(1, 1, 1, 1) glClear(GL_COLOR_BUFFER_BIT) for (id, scene) in screen.screens @@ -122,7 +92,7 @@ function render_frame(screen::Screen; resize_buffers=true) @debug("Current context does not match the current screen.") return end - + function sortby(x) robj = x[3] plot = screen.cache2plot[robj.id] @@ -135,7 +105,7 @@ function render_frame(screen::Screen; resize_buffers=true) # NOTE # The transparent color buffer is reused by SSAO and FXAA. Changing the # render order here may introduce artifacts because of that. - + fb = screen.framebuffer if resize_buffers wh = Int.(framebuffer_size(nw)) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index c22e75d8a48..c64a2edbde0 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -3,6 +3,40 @@ const ZIndex = Int # ID, Area, clear, is visible, background color const ScreenArea = Tuple{ScreenID, Scene} +function renderloop end + +const SCREEN_CONFIG = Ref(( + renderloop = renderloop, + vsync = false, + framerate = 30.0, + float = false, + pause_rendering = false, + focus_on_show = false, + decorated = true, + title = "Makie", + exit_renderloop = false,)) + +""" + set_window_config!(; + renderloop = renderloop, + vsync = false, + framerate = 30.0, + float = false, + pause_rendering = false, + focus_on_show = false, + decorated = true, + title = "Makie" + ) +Updates the screen configuration, will only go into effect after closing the current +window and opening a new one! +""" +function activate!(; screen_config...) + Makie.set_screen_config!(SCREEN_CONFIG, screen_config) + Makie.set_active_backend!(GLMakie) + Makie.set_glyph_resolution!(Makie.High) + return +end + abstract type GLScreen <: MakieScreen end mutable struct Screen{GLWindow} <: GLScreen @@ -33,7 +67,7 @@ mutable struct Screen{GLWindow} <: GLScreen cache2plot::Dict{UInt32, AbstractPlot}, ) where {GLWindow} s = size(framebuffer) - return new( + return new{GLWindow}( glscreen, shader_cache, framebuffer, rendertask, screen2scene, screens, renderlist, postprocessors, cache, cache2plot, Matrix{RGB{N0f8}}(undef, s), Observable(nothing), @@ -150,7 +184,7 @@ const GLFW_WINDOWS = GLFW.Window[] const SINGLETON_SCREEN = Screen[] const SINGLETON_SCREEN_NO_RENDERLOOP = Screen[] -function singleton_screen(resolution; visible=Makie.use_display[], start_renderloop=true) +function singleton_screen(resolution; visible=true, start_renderloop=true) screen_ref = if start_renderloop SINGLETON_SCREEN else @@ -356,7 +390,7 @@ end function Screen(; resolution = (10, 10), visible = true, - title = WINDOW_CONFIG.title[], + title = SCREEN_CONFIG[].title, start_renderloop = true, kw_args... ) @@ -375,9 +409,9 @@ function Screen(; (GLFW.STENCIL_BITS, 0), (GLFW.AUX_BUFFERS, 0), - (GLFW_FOCUS_ON_SHOW, WINDOW_CONFIG.focus_on_show[]), - (GLFW.DECORATED, WINDOW_CONFIG.decorated[]), - (GLFW.FLOATING, WINDOW_CONFIG.float[]), + (GLFW_FOCUS_ON_SHOW, SCREEN_CONFIG[].focus_on_show), + (GLFW.DECORATED, SCREEN_CONFIG[].decorated), + (GLFW.FLOATING, SCREEN_CONFIG[].float), # (GLFW.TRANSPARENT_FRAMEBUFFER, true) ] @@ -432,7 +466,7 @@ function Screen(; GLFW.SetWindowRefreshCallback(window, window -> refreshwindowcb(window, screen)) if start_renderloop - screen.rendertask[] = @async((WINDOW_CONFIG.renderloop[])(screen)) + screen.rendertask[] = @async((SCREEN_CONFIG[].renderloop)(screen)) end # display window if visible! if visible @@ -450,3 +484,13 @@ function refreshwindowcb(window, screen) GLFW.SwapBuffers(window) return end + +Screen(scene::Scene; screen_attributes...) = singleton_screen(size(scene); visible=true, start_renderloop=true) + +function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::MIME; screen_attributes...) + return singleton_screen(size(scene); visible=false, start_renderloop=false) +end + +function Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_attributes...) + return singleton_screen(size(scene); visible=false, start_renderloop=false) +end diff --git a/GLMakie/test/glmakie_refimages.jl b/GLMakie/test/glmakie_refimages.jl index 3dbe135df5d..7fc01947a97 100644 --- a/GLMakie/test/glmakie_refimages.jl +++ b/GLMakie/test/glmakie_refimages.jl @@ -83,7 +83,7 @@ end end end fig, ax, meshplot = meshscatter(RNG.rand(Point3f, 10^4) .* 20f0) - screen = Makie.backend_display(GLMakie.GLBackend(), fig.scene) + screen = display(GLMakie.Screen(), fig.scene) buff = RNG.rand(Point3f, 10^4) .* 20f0; update_loop(meshplot, buff, screen) set_window_config!(renderloop=GLMakie.renderloop) @@ -93,7 +93,7 @@ end @reference_test "Contour and isosurface with correct depth" begin # Make sure shaders can recompile GLMakie.closeall() - + fig = Figure() left = LScene(fig[1, 1]) contour!(left, [sin(i+j) * sin(j+k) * sin(i+k) for i in 1:10, j in 1:10, k in 1:10], enable_depth = true) diff --git a/GLMakie/test/runtests.jl b/GLMakie/test/runtests.jl index 5419944905c..0eba55434f1 100644 --- a/GLMakie/test/runtests.jl +++ b/GLMakie/test/runtests.jl @@ -11,10 +11,10 @@ Pkg.develop(PackageSpec(path = reference_tests_dir)) using ReferenceTests GLMakie.activate!() -GLMakie.set_window_config!(; - framerate = 1.0, - pause_rendering = true -) +# GLMakie.set_window_config!(; +# framerate = 1.0, +# pause_rendering = true +# ) @testset "mimes" begin f, ax, pl = scatter(1:4) diff --git a/GLMakie/test/unit_tests.jl b/GLMakie/test/unit_tests.jl index 27b277414bf..011637b6877 100644 --- a/GLMakie/test/unit_tests.jl +++ b/GLMakie/test/unit_tests.jl @@ -1,4 +1,4 @@ -using GLMakie.Makie: backend_display, getscreen +using GLMakie.Makie: getscreen function project_sp(scene, point) point_px = Makie.project(scene, point) @@ -14,14 +14,13 @@ end @test !isassigned(GLMakie.SINGLETON_SCREEN_NO_RENDERLOOP) # A raw screen should be tracked in GLFW_WINDOWS - Makie.inline!(false) screen = GLMakie.Screen(resolution = (100, 100), visible = false) @test isopen(screen) @test length(GLMakie.GLFW_WINDOWS) == 1 && (GLMakie.GLFW_WINDOWS[1] === screen.glscreen) @test !isassigned(GLMakie.SINGLETON_SCREEN) @test !isassigned(GLMakie.SINGLETON_SCREEN_NO_RENDERLOOP) - # A displayed figure should create a singleton screen and leave other + # A displayed figure should create a singleton screen and leave other # screens untouched fig, ax, splot = scatter(1:4); screen2 = display(fig) diff --git a/MakieCore/src/types.jl b/MakieCore/src/types.jl index 11cf3f68be3..b8cebd00a0f 100644 --- a/MakieCore/src/types.jl +++ b/MakieCore/src/types.jl @@ -10,6 +10,30 @@ abstract type AbstractPlot{Typ} <: Transformable end abstract type AbstractScene <: Transformable end abstract type ScenePlot{Typ} <: AbstractPlot{Typ} end +""" +Constructors: + + `MakieScreen(scene::Scene; screen_attributes...)` +Constructor aimed at showing the plot in a window. + `MakieScreen(scene::Scene, io::IO, mime; screen_attributes...)` +Screen that writes out a mime to an io + `MakieScreen(scene::Scene, img::Matrix{<: Colorant}; screen_attributes...)` +Screen optimized for `colorbuffer(screen)`. + +Interface: +```julia +# Needs to be overload: +size(screen) # Size in pixel + +# Optional +wait(screen) # waits as long window is open + +# Provided by Makie: +push_screen!(scene, screen) +``` +""" +abstract type MakieScreen <: AbstractDisplay end + const SceneLike = Union{AbstractScene, ScenePlot} """ diff --git a/RPRMakie/examples/sea_cables.jl b/RPRMakie/examples/sea_cables.jl index c27f8ff95d7..4c9a6b14a0f 100644 --- a/RPRMakie/examples/sea_cables.jl +++ b/RPRMakie/examples/sea_cables.jl @@ -47,7 +47,6 @@ for i in 1:length(toLines) end earth_img = load(Downloads.download("https://upload.wikimedia.org/wikipedia/commons/5/56/Blue_Marble_Next_Generation_%2B_topography_%2B_bathymetry.jpg")) -Makie.inline!(true) # the actual plot ! RPRMakie.activate!(; iterations=100) scene = with_theme(theme_dark()) do diff --git a/RPRMakie/src/scene.jl b/RPRMakie/src/scene.jl index f8fee724fdd..1cf29373053 100644 --- a/RPRMakie/src/scene.jl +++ b/RPRMakie/src/scene.jl @@ -203,7 +203,7 @@ end function Makie.colorbuffer(screen::RPRScreen) if !screen.setup_scene - Makie.backend_display(screen, screen.scene) + display(screen, screen.scene) end data_1d = RPR.get_data(render(screen)) r = reverse(reshape(data_1d, screen.fb_size), dims=2) @@ -213,13 +213,13 @@ function Makie.colorbuffer(screen::RPRScreen) end end -function Makie.backend_display(::RPRBackend, scene::Scene; kw...) +function display(::RPRBackend, scene::Scene; kw...) screen = RPRScreen(scene) - Makie.backend_display(screen, scene) + display(screen, scene) return screen end -function Makie.backend_display(screen::RPRScreen, scene::Scene) +function display(screen::RPRScreen, scene::Scene) screen.scene = scene rpr_scene = to_rpr_scene(screen.context, screen.matsys, scene) screen.rpr_scene = rpr_scene @@ -241,7 +241,7 @@ function Base.insert!(screen::RPRScreen, scene::Scene, plot::AbstractPlot) end function Makie.backend_show(b::RPRBackend, io::IO, ::MIME"image/png", scene::Scene) - screen = Makie.backend_display(b, scene) + screen = display(b, scene) img = Makie.colorbuffer(screen) FileIO.save(FileIO.Stream{FileIO.format"PNG"}(Makie.raw_io(io)), img) return screen diff --git a/ReferenceTests/src/tests/updating.jl b/ReferenceTests/src/tests/updating.jl index b6958ff17fc..3ed58455730 100644 --- a/ReferenceTests/src/tests/updating.jl +++ b/ReferenceTests/src/tests/updating.jl @@ -1,4 +1,7 @@ using ReferenceTests +using LinearAlgebra +using ReferenceTests: @reference_test + @reference_test "updating 2d primitives" begin fig = Figure() t = Observable(1) diff --git a/WGLMakie/src/WGLMakie.jl b/WGLMakie/src/WGLMakie.jl index a11d7bc9851..efa43c0be6b 100644 --- a/WGLMakie/src/WGLMakie.jl +++ b/WGLMakie/src/WGLMakie.jl @@ -26,7 +26,6 @@ using Makie: get_texture_atlas, glyph_uv_width!, SceneSpace, Pixel using Makie: attribute_per_char, glyph_uv_width!, layout_text using Makie: MouseButtonEvent, KeyEvent using Makie: apply_transform, transform_func_obs -using Makie: inline! using Makie: spaces, is_data_space, is_pixel_space, is_relative_space, is_clip_space struct WebGL <: ShaderAbstractions.AbstractContext end diff --git a/WGLMakie/src/display.jl b/WGLMakie/src/display.jl index 802c075895a..c88a7505144 100644 --- a/WGLMakie/src/display.jl +++ b/WGLMakie/src/display.jl @@ -15,7 +15,7 @@ const WEB_MIMES = (MIME"text/html", MIME"application/vnd.webio.application+html" for M in WEB_MIMES @eval begin - function Makie.backend_show(::WGLBackend, io::IO, m::$M, scene::Scene) + function Makie.backend_show(::Screen, io::IO, m::$M, scene::Scene) three = nothing inline_display = App() do session::Session three, canvas = three_display(session, scene) @@ -28,54 +28,18 @@ for M in WEB_MIMES end end -function scene2image(scene::Scene) - if !JSServe.has_html_display() - error(""" - There is no Display that can show HTML. - If in the REPL and you have a browser, - you can always run `JSServe.browser_display()` to show plots in the browser - """) - end - three = nothing - session = nothing - app = App() do s::Session - session = s - three, canvas = three_display(s, scene) - return canvas - end - # display in current - display(app) - done = Base.timedwait(()-> isready(session.js_fully_loaded), 30.0) - if done == :timed_out - error("JS Session not ready after 30s waiting, possibly errored while displaying") - end - return Makie.colorbuffer(three) -end - -function Makie.backend_show(::WGLBackend, io::IO, m::MIME"image/png", - scene::Scene) - img = scene2image(scene) - return FileIO.save(FileIO.Stream{FileIO.format"PNG"}(io), img) -end - -function Makie.backend_show(::WGLBackend, io::IO, m::MIME"image/jpeg", - scene::Scene) - img = scene2image(scene) - return FileIO.save(FileIO.Stream{FileIO.format"JPEG"}(io), img) -end - -function Makie.backend_showable(::WGLBackend, ::T, scene::Scene) where {T<:MIME} +function Makie.backend_showable(::Type{Screen}, ::T, scene::Scene) where {T<:MIME} return T in WEB_MIMES end -struct WebDisplay <: Makie.MakieScreen +struct Screen <: Makie.MakieScreen three::Base.RefValue{ThreeDisplay} display::Any end -GeometryBasics.widths(screen::WebDisplay) = GeometryBasics.widths(screen.three[]) +Base.size(screen::Screen) = size(screen.three[]) -function Makie.backend_display(::WGLBackend, scene::Scene; kw...) +function display(::Screen, scene::Scene; kw...) # Reference to three object which gets set once we serve this to a browser three_ref = Base.RefValue{ThreeDisplay}() app = App() do s, request @@ -84,10 +48,10 @@ function Makie.backend_display(::WGLBackend, scene::Scene; kw...) return canvas end actual_display = display(app) - return WebDisplay(three_ref, actual_display) + return Screen(three_ref, actual_display) end -function Base.delete!(td::WebDisplay, scene::Scene, plot::AbstractPlot) +function Base.delete!(td::Screen, scene::Scene, plot::AbstractPlot) delete!(get_three(td), scene, plot) end @@ -104,8 +68,13 @@ function Makie.colorbuffer(screen::ThreeDisplay) return session2image(screen) end -function get_three(screen::WebDisplay; timeout = 30)::Union{Nothing, ThreeDisplay} - # WebDisplay is not guaranteed to get displayed in the browser, so we wait a while +function Makie.colorbuffer(screen::Screen) + return session2image(get_three(screen)) +end + + +function get_three(screen::Screen; timeout = 30)::Union{Nothing, ThreeDisplay} + # Screen is not guaranteed to get displayed in the browser, so we wait a while # to see if anything gets displayed! tstart = time() while time() - tstart < timeout @@ -125,11 +94,7 @@ function get_three(screen::WebDisplay; timeout = 30)::Union{Nothing, ThreeDispla return nothing end -function Makie.colorbuffer(screen::WebDisplay) - return session2image(get_three(screen)) -end - -function Base.insert!(td::WebDisplay, scene::Scene, plot::Combined) +function Base.insert!(td::Screen, scene::Scene, plot::Combined) disp = get_three(td) disp === nothing && error("Plot needs to be displayed to insert additional plots") insert!(disp, scene, plot) diff --git a/gallery.jl b/gallery.jl index 6ffb45fc2f6..c6bf2780152 100644 --- a/gallery.jl +++ b/gallery.jl @@ -4,7 +4,6 @@ using CSV using DataFrames using GLMakie using LinearAlgebra -GLMakie.inline!(false) url = "https://raw.githubusercontent.com/plotly/datasets/master/vortex.csv" df = CSV.File(HTTP.get(url).body)|> DataFrame; diff --git a/metrics/ttfp/benchmark-ttfp.jl b/metrics/ttfp/benchmark-ttfp.jl index e8dbae7f15e..8f4fb78cb90 100644 --- a/metrics/ttfp/benchmark-ttfp.jl +++ b/metrics/ttfp/benchmark-ttfp.jl @@ -7,8 +7,6 @@ macro ctime(x) end end t_using = @ctime @eval using $Package -Makie.inline!(false) # needed for cairomakie to return a screen - if Package == :WGLMakie using ElectronDisplay diff --git a/src/Makie.jl b/src/Makie.jl index 98d80744e21..2162f347900 100644 --- a/src/Makie.jl +++ b/src/Makie.jl @@ -326,6 +326,6 @@ export heatmap!, image!, lines!, linesegments!, mesh!, meshscatter!, scatter!, s export PointLight, EnvironmentLight, AmbientLight, SSAO -include("precompiles.jl") +# include("precompiles.jl") end # module diff --git a/src/deprecated.jl b/src/deprecated.jl new file mode 100644 index 00000000000..f026ba713f3 --- /dev/null +++ b/src/deprecated.jl @@ -0,0 +1,11 @@ +function inline!() + +end + +function register_backend!() + +end + +function backend_display() + +end diff --git a/src/display.jl b/src/display.jl index 0ded702eba7..d691b77b517 100644 --- a/src/display.jl +++ b/src/display.jl @@ -1,35 +1,17 @@ @enum ImageStorageFormat JuliaNative GLNative -""" - MakieScreen(scene::Scene; screen_attributes...) - MakieScreen(scene::Scene, io::IO; screen_attributes...) - -Interface: -```julia -# Needs to be overload: -size(screen) # Size in pixel - -# Optional -wait(screen) # waits as long window is open - -# Provided by Makie: -push_screen!(scene, screen) -``` -""" -abstract type MakieScreen <: AbstractDisplay end - update_state_before_display!(_) = nothing -function backend_display end function backend_show end """ Current backend """ -const current_backend = Ref{Union{Missing, Module}}(missing) +const CURRENT_BACKEND = Ref{Union{Missing, Module}}(missing) +current_backend() = CURRENT_BACKEND[] function set_active_backend!(backend::Module) - current_backend[] = backend + CURRENT_BACKEND[] = backend return end @@ -60,7 +42,7 @@ function delete_screen!(scene::Scene, display::AbstractDisplay) end function set_screen_config!(config::RefValue, new_values) - config_attributes = propertynames(config) + config_attributes = propertynames(config[]) for (k, v) in pairs(new_values) if !(k in config_attributes) error("$k is not a valid screen config. Applicable options: $(config_attributes)") @@ -69,26 +51,6 @@ function set_screen_config!(config::RefValue, new_values) config[] = merge(config[], new_values) end -function backend_display(figlike::FigureLike; screen_kw...) - update_state_before_display!(figlike) - Backend = current_backend[] - scene = get_scene(figlike) - screen = Backend.Screen(scene; screen_kw...) - backend_display(screen, scene) - return screen -end - -function backend_display(::Missing, ::Scene; screen_kw...) - error(""" - No backend available! - Make sure to also `import/using` a backend (GLMakie, CairoMakie, WGLMakie). - - If you imported GLMakie, it may have not built correctly. - In that case, try `]build GLMakie` and watch out for any warnings. - """) -end - - """ @@ -102,28 +64,46 @@ pt_per_unit=x.pt_per_unit px_per_unit=x.px_per_unit antialias=x.antialias """ -function Base.display(fig::FigureLike; screen_kw...) - return backend_display(fig; screen_kw...) +function Base.display(figlike::FigureLike; screen_kw...) + Backend = current_backend() + if ismissing(Backend) + error(""" + No backend available! + Make sure to also `import/using` a backend (GLMakie, CairoMakie, WGLMakie). + + If you imported GLMakie, it may have not built correctly. + In that case, try `]build GLMakie` and watch out for any warnings. + """) + end + screen = Backend.Screen(get_scene(figlike); screen_kw...) + return display(screen, figlike) +end + +function Base.display(screen::MakieScreen, figlike::FigureLike; display_attributes...) + update_state_before_display!(figlike) + scene = get_scene(figlike) + display(screen, scene; display_attributes...) + return screen end function Base.showable(mime::MIME{M}, scene::Scene) where M - backend_showable(current_backend[].Screen, mime, scene) + backend_showable(current_backend().Screen, mime, scene) end # ambig function Base.showable(mime::MIME"application/json", scene::Scene) - backend_showable(current_backend[].Screen, mime, scene) + backend_showable(current_backend().Screen, mime, scene) end function Base.showable(mime::MIME{M}, fig::FigureLike) where M - backend_showable(current_backend[].Screen, mime, get_scene(fig)) + backend_showable(current_backend().Screen, mime, get_scene(fig)) end # ambig function Base.showable(mime::MIME"application/json", fig::FigureLike) - backend_showable(current_backend[].Screen, mime, get_scene(fig)) + backend_showable(current_backend().Screen, mime, get_scene(fig)) end function backend_showable(::Type{Screen}, ::Mime, ::Scene) where {Screen, Mime <: MIME} - hasmethod(backend_show, Tuple{Screen, IO, Mime, Scene}) + hasmethod(backend_show, Tuple{<: Screen, IO, Mime, Scene}) end # fallback show when no backend is selected @@ -146,7 +126,7 @@ end function Base.show(io::IO, m::MIME, figlike::FigureLike) update_state_before_display!(figlike) scene = get_scene(figlike) - backend_show(current_backend[].Screen(scene, io), m, scene) + backend_show(current_backend().Screen(scene, io, m), io, m, scene) return end @@ -164,6 +144,7 @@ exported and should be accessed by module name. """ mutable struct FolderStepper figlike::FigureLike + screen::MakieScreen folder::String format::Symbol step::Int @@ -171,16 +152,26 @@ end mutable struct RamStepper figlike::FigureLike + screen::MakieScreen images::Vector{Matrix{RGBf}} format::Symbol end -Stepper(figlike::FigureLike, path::String, step::Int; format=:png) = FolderStepper(figlike, path, format, step) -Stepper(figlike::FigureLike; format=:png) = RamStepper(figlike, Matrix{RGBf}[], format) +function Stepper(figlike::FigureLike; backend=current_backend(), format=:png, visible=false, connect=false, srceen_kw...) + screen = backend.Screen(get_scene(figlike), JuliaNative; visible=visible, start_renderloop=false, srceen_kw...) + display(screen, figlike; connect=connect) + return RamStepper(figlike, screen, Matrix{RGBf}[], format) +end -function Stepper(figlike::FigureLike, path::String; format = :png) +function Stepper(figlike::FigureLike, path::String, step::Int; format=:png, backend=current_backend(), visible=false, connect=false, screen_kw...) + screen = backend.Screen(get_scene(figlike), JuliaNative; visible=visible, start_renderloop=false, screen_kw...) + display(screen, figlike; connect=connect) + return FolderStepper(figlike, screen, path, format, step) +end + +function Stepper(figlike::FigureLike, path::String; kw...) ispath(path) || mkpath(path) - FolderStepper(figlike, path, format, 1) + return Stepper(figlike, path, 1; kw...) end """ @@ -190,13 +181,13 @@ steps through a `Makie.Stepper` and outputs a file with filename `filename-step. This is useful for generating progressive plot examples. """ function step!(s::FolderStepper) - FileIO.save(joinpath(s.folder, basename(s.folder) * "-$(s.step).$(s.format)"), s.figlike) + FileIO.save(joinpath(s.folder, basename(s.folder) * "-$(s.step).$(s.format)"), colorbuffer(s.screen)) s.step += 1 return s end function step!(s::RamStepper) - img = convert(Matrix{RGBf}, colorbuffer(s.figlike)) + img = convert(Matrix{RGBf}, colorbuffer(s.screen)) push!(s.images, img) return s end @@ -289,8 +280,8 @@ raw_io(io::IOContext) = raw_io(io.io) struct VideoStream io - process - screen + process::Base.Process + screen::MakieScreen path::String end @@ -304,13 +295,14 @@ Use [`recordframe!(stream)`](@ref) to add new video frames to the stream, and * visible=false: make window visible or not * connect=false: connect window events or not """ -function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, connect=false, screen_kw...) +function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, connect=false, backend=current_backend(), screen_kw...) #codec = `-codec:v libvpx -quality good -cpu-used 0 -b:v 500k -qmin 10 -qmax 42 -maxrate 500k -bufsize 1000k -threads 8` dir = mktempdir() path = joinpath(dir, "$(gensym(:video)).mkv") scene = get_scene(fig) - screen = backend_display(fig; start_renderloop=false, visible=visible, connect=connect) - _xdim, _ydim = GeometryBasics.widths(screen) + screen = backend.Screen(scene, GLNative; visible=visible, start_renderloop=false, screen_kw...) + display(screen, fig; connect=connect) + _xdim, _ydim = size(screen) xdim = iseven(_xdim) ? _xdim : _xdim + 1 ydim = iseven(_ydim) ? _ydim : _ydim + 1 process = @ffmpeg_env open(`$(FFMPEG.ffmpeg) -framerate $(framerate) -loglevel quiet -f rawvideo -pixel_format rgb24 -r $framerate -s:v $(xdim)x$(ydim) -i pipe:0 -vf vflip -y $path`, "w") @@ -571,18 +563,36 @@ or RGBA. - `format = GLNative` : Returns a more efficient format buffer for GLMakie which can be directly used in FFMPEG without conversion """ -function colorbuffer(fig::FigureLike, format::ImageStorageFormat = JuliaNative) +function colorbuffer(fig::FigureLike, format::ImageStorageFormat = JuliaNative; backend = current_backend()) scene = get_scene(fig) screen = getscreen(scene) if isnothing(screen) - if ismissing(current_backend[]) + if ismissing(CURRENT_BACKEND[]) error(""" You have not loaded a backend. Please load one (`using GLMakie` or `using CairoMakie`) before trying to render a Scene. """) else - return colorbuffer(backend_display(fig; visible=false, start_renderloop=false, connect=false), format) + screen = backend.Screen(scene, format; start_renderloop=false, visible=false) + display(screen, fig; connect=false) + return colorbuffer(screen, format) end end return colorbuffer(screen, format) end + + +# Fallback for any backend that will just use colorbuffer to write out an image +function backend_show(screen::MakieScreen, io::IO, m::MIME"image/png", scene::Scene) + display(screen, scene; connect=false) + img = colorbuffer(screen) + FileIO.save(FileIO.Stream{FileIO.format"PNG"}(Makie.raw_io(io)), img) + return +end + +function backend_show(screen::MakieScreen, io::IO, m::MIME"image/jpeg", scene::Scene) + display(screen, scene; connect=false) + img = colorbuffer(scene) + FileIO.save(FileIO.Stream{FileIO.format"JPEG"}(Makie.raw_io(io)), img) + return +end From a0b495531132d34cf9512c3c71c21c628f7b9320 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 27 Sep 2022 23:26:09 +0200 Subject: [PATCH 17/56] fix tests --- GLMakie/src/picking.jl | 10 +++++----- src/display.jl | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/GLMakie/src/picking.jl b/GLMakie/src/picking.jl index 7c1d88eb3fd..a444e8c7998 100644 --- a/GLMakie/src/picking.jl +++ b/GLMakie/src/picking.jl @@ -6,7 +6,7 @@ function pick_native(screen::Screen, rect::Rect2i) isopen(screen) || return Matrix{SelectionID{Int}}(undef, 0, 0) ShaderAbstractions.switch_context!(screen.glscreen) - window_size = widths(screen) + window_size = size(screen) fb = screen.framebuffer buff = fb.buffers[:objectid] glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) @@ -27,7 +27,7 @@ function pick_native(screen::Screen, xy::Vec{2, Float64}) isopen(screen) || return SelectionID{Int}(0, 0) ShaderAbstractions.switch_context!(screen.glscreen) sid = Base.RefValue{SelectionID{UInt32}}() - window_size = widths(screen) + window_size = size(screen) fb = screen.framebuffer buff = fb.buffers[:objectid] glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) @@ -65,7 +65,7 @@ end # Skips one set of allocations function Makie.pick_closest(scene::Scene, screen::Screen, xy, range) isopen(screen) || return (nothing, 0) - w, h = widths(screen) + w, h = size(screen) ((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h)) || return (nothing, 0) x0, y0 = max.(1, floor.(Int, xy .- range)) @@ -95,7 +95,7 @@ end # Skips some allocations function Makie.pick_sorted(scene::Scene, screen::Screen, xy, range) isopen(screen) || return (nothing, 0) - w, h = widths(screen) + w, h = size(screen) if !((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h)) return Tuple{AbstractPlot, Int}[] end @@ -123,4 +123,4 @@ function Makie.pick_sorted(scene::Scene, screen::Screen, xy, range) idxs = sortperm(distances) permute!(selected, idxs) return map(id -> (screen.cache2plot[id.id], id.index), selected) -end \ No newline at end of file +end diff --git a/src/display.jl b/src/display.jl index d691b77b517..9765f2f70e5 100644 --- a/src/display.jl +++ b/src/display.jl @@ -181,12 +181,14 @@ steps through a `Makie.Stepper` and outputs a file with filename `filename-step. This is useful for generating progressive plot examples. """ function step!(s::FolderStepper) + update_state_before_display!(s.figlike) FileIO.save(joinpath(s.folder, basename(s.folder) * "-$(s.step).$(s.format)"), colorbuffer(s.screen)) s.step += 1 return s end function step!(s::RamStepper) + update_state_before_display!(s.figlike) img = convert(Matrix{RGBf}, colorbuffer(s.screen)) push!(s.images, img) return s From ccbb4d6966c3f1a9ac0b3ca7e9c689bd3d29796f Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 28 Sep 2022 14:57:47 +0200 Subject: [PATCH 18/56] more context switching --- GLMakie/src/rendering.jl | 1 + GLMakie/src/screen.jl | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 77ccaf2f36a..14ef3e08661 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -37,6 +37,7 @@ end function renderloop(screen; framerate=SCREEN_CONFIG[].framerate) isopen(screen) || error("Screen most be open to run renderloop!") + ShaderAbstractions.switch_context!(screen.glscreen) try if SCREEN_CONFIG[].vsync GLFW.SwapInterval(1) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index c64a2edbde0..7d2e6d774d7 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -191,18 +191,20 @@ function singleton_screen(resolution; visible=true, start_renderloop=true) SINGLETON_SCREEN_NO_RENDERLOOP end - if length(screen_ref) == 1 && isopen(screen_ref[1]) + screen = if length(screen_ref) == 1 && isopen(screen_ref[1]) screen = screen_ref[1] resize!(screen, resolution...) - return screen + screen else if !isempty(screen_ref) closeall(screen_ref) end screen = Screen(; resolution=resolution, visible=visible, start_renderloop=start_renderloop) push!(screen_ref, screen) - return screen + screen end + ShaderAbstractions.switch_context!(screen.glscreen) + return screen end function destroy!(screen::Screen) From d8fd2138eb530c457dce0d435e375a734d7143e6 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 28 Sep 2022 16:14:27 +0200 Subject: [PATCH 19/56] try thiss --- GLMakie/src/display.jl | 2 +- GLMakie/test/glmakie_refimages.jl | 38 +++++++++++++------------- ReferenceTests/src/tests/examples2d.jl | 11 ++++---- ReferenceTests/src/tests/updating.jl | 4 --- 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/GLMakie/src/display.jl b/GLMakie/src/display.jl index 09c0cfe5471..f610f8745c3 100644 --- a/GLMakie/src/display.jl +++ b/GLMakie/src/display.jl @@ -1,5 +1,4 @@ function Base.display(screen::Screen, scene::Scene; connect=true) - ShaderAbstractions.switch_context!(screen.glscreen) empty!(screen) resize!(screen, size(scene)...) # So, the GLFW window events are not guarantee to fire @@ -9,6 +8,7 @@ function Base.display(screen::Screen, scene::Scene; connect=true) window_open[] = open end connect && connect_screen(scene, screen) + ShaderAbstractions.switch_context!(screen.glscreen) pollevents(screen) insertplots!(screen, scene) pollevents(screen) diff --git a/GLMakie/test/glmakie_refimages.jl b/GLMakie/test/glmakie_refimages.jl index 7fc01947a97..20f1f5cb876 100644 --- a/GLMakie/test/glmakie_refimages.jl +++ b/GLMakie/test/glmakie_refimages.jl @@ -70,25 +70,25 @@ end end end -@reference_test "Explicit frame rendering" begin - set_window_config!(renderloop=(screen) -> nothing) - function update_loop(m, buff, screen) - for i = 1:20 - GLFW.PollEvents() - buff .= RNG.rand.(Point3f) .* 20f0 - m[1] = buff - GLMakie.render_frame(screen) - GLFW.SwapBuffers(GLMakie.to_native(screen)) - glFinish() - end - end - fig, ax, meshplot = meshscatter(RNG.rand(Point3f, 10^4) .* 20f0) - screen = display(GLMakie.Screen(), fig.scene) - buff = RNG.rand(Point3f, 10^4) .* 20f0; - update_loop(meshplot, buff, screen) - set_window_config!(renderloop=GLMakie.renderloop) - fig -end +# @reference_test "Explicit frame rendering" begin +# set_window_config!(renderloop=(screen) -> nothing) +# function update_loop(m, buff, screen) +# for i = 1:20 +# GLFW.PollEvents() +# buff .= RNG.rand.(Point3f) .* 20f0 +# m[1] = buff +# GLMakie.render_frame(screen) +# GLFW.SwapBuffers(GLMakie.to_native(screen)) +# glFinish() +# end +# end +# fig, ax, meshplot = meshscatter(RNG.rand(Point3f, 10^4) .* 20f0) +# screen = display(GLMakie.Screen(), fig.scene) +# buff = RNG.rand(Point3f, 10^4) .* 20f0; +# update_loop(meshplot, buff, screen) +# set_window_config!(renderloop=GLMakie.renderloop) +# fig +# end @reference_test "Contour and isosurface with correct depth" begin # Make sure shaders can recompile diff --git a/ReferenceTests/src/tests/examples2d.jl b/ReferenceTests/src/tests/examples2d.jl index 8fcabc3a294..a1bc38e4b89 100644 --- a/ReferenceTests/src/tests/examples2d.jl +++ b/ReferenceTests/src/tests/examples2d.jl @@ -611,7 +611,7 @@ end @reference_test "trimspine" begin with_theme(Axis = (limits = (0.5, 5.5, 0.3, 3.4), spinewidth = 8, topspinevisible = false, rightspinevisible = false)) do f = Figure(resolution = (800, 800)) - + for (i, ts) in enumerate([(true, true), (true, false), (false, true), (false, false)]) Label(f[0, i], string(ts), tellwidth = false) Axis(f[1, i], xtrimspine = ts) @@ -619,11 +619,11 @@ end Axis(f[3, i], xtrimspine = ts, xreversed = true) Axis(f[4, i], ytrimspine = ts, yreversed = true) end - + for (i, l) in enumerate(["x", "y", "x reversed", "y reversed"]) Label(f[i, 5], l, tellheight = false) end - + f end end @@ -683,14 +683,14 @@ end x = RNG.rand(300) y = RNG.rand(300) - + for (i, cellsize) in enumerate([0.1, 0.15, 0.2, 0.25]) ax = Axis(f[fldmod1(i, 2)...], title = "cellsize = $cellsize", aspect = DataAspect()) hexbin!(ax, x, y, cellsize = cellsize) wireframe!(ax, Rect2f(Point2f.(x, y)), color = :red) scatter!(ax, x, y, color = :red, markersize = 5) end - + f end @@ -734,4 +734,3 @@ end # strokecolor = :gray30 # ) # end - diff --git a/ReferenceTests/src/tests/updating.jl b/ReferenceTests/src/tests/updating.jl index 3ed58455730..495656a8c89 100644 --- a/ReferenceTests/src/tests/updating.jl +++ b/ReferenceTests/src/tests/updating.jl @@ -1,7 +1,3 @@ -using ReferenceTests -using LinearAlgebra -using ReferenceTests: @reference_test - @reference_test "updating 2d primitives" begin fig = Figure() t = Observable(1) From 0349b5d620ca4c64a266989136f815912ed4db48 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 28 Sep 2022 17:41:40 +0200 Subject: [PATCH 20/56] hm --- GLMakie/test/unit_tests.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/GLMakie/test/unit_tests.jl b/GLMakie/test/unit_tests.jl index 011637b6877..4914f0065c2 100644 --- a/GLMakie/test/unit_tests.jl +++ b/GLMakie/test/unit_tests.jl @@ -6,6 +6,10 @@ function project_sp(scene, point) return point_px .+ offset end +GLMakie.closeall(GLMakie.GLFW_WINDOWS) +GLMakie.closeall(GLMakie.SINGLETON_SCREEN) +GLMakie.closeall(GLMakie.SINGLETON_SCREEN_NO_RENDERLOOP) + @testset "unit tests" begin @testset "Window handling" begin # Without previous windows/figures everything should be empty/unassigned @@ -60,6 +64,8 @@ end @test isopen(screen2) && (screen2 === GLMakie.SINGLETON_SCREEN[]) @test screen === screen2 @test screen2.glscreen.handle == ptr + close(screen) + close(screen2) end @testset "Pick a plot element or plot elements inside a rectangle" begin From 88219bdc6319000473fbe145b61299c88e08f1b6 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 29 Sep 2022 12:13:23 +0200 Subject: [PATCH 21/56] make sure we track context in shader compilation --- GLMakie/src/GLAbstraction/GLShader.jl | 20 +++++++++++++------- GLMakie/src/screen.jl | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/GLMakie/src/GLAbstraction/GLShader.jl b/GLMakie/src/GLAbstraction/GLShader.jl index 676f238f793..910e7bd1b7c 100644 --- a/GLMakie/src/GLAbstraction/GLShader.jl +++ b/GLMakie/src/GLAbstraction/GLShader.jl @@ -87,14 +87,16 @@ end struct ShaderCache # path --> template keys # cache for template keys per file + context::Any template_cache::Dict{String, Vector{String}} # path --> Dict{template_replacements --> Shader) shader_cache::Dict{String, Dict{Any, Shader}} program_cache::Dict{Any, GLProgram} end -function ShaderCache() +function ShaderCache(context) ShaderCache( + context, Dict{String, Vector{String}}(), Dict{String, Dict{Any, Shader}}(), Dict{Any, GLProgram}() @@ -126,28 +128,29 @@ function compile_shader(source::Vector{UInt8}, typ, name) GLAbstraction.print_with_lines(String(source)) @warn("shader $(name) didn't compile. \n$(GLAbstraction.getinfolog(shaderid))") end - Shader(name, source, typ, shaderid) + return Shader(name, source, typ, shaderid) end function compile_shader(path, source_str::AbstractString) typ = shadertype(splitext(path)[2]) source = Vector{UInt8}(source_str) name = Symbol(path) - compile_shader(source, typ, name) + return compile_shader(source, typ, name) end function get_shader!(cache::ShaderCache, path, template_replacement) # this should always be in here, since we already have the template keys shader_dict = cache.shader_cache[path] - get!(shader_dict, template_replacement) do + return get!(shader_dict, template_replacement) do template_source = read(path, String) source = mustache_replace(template_replacement, template_source) - compile_shader(path, source) + @assert cache.context === ShaderAbstractions.current_context() + return compile_shader(path, source) end::Shader end function get_template!(cache::ShaderCache, path, view, attributes) - get!(cache.template_cache, path) do + return get!(cache.template_cache, path) do _, ext = splitext(path) typ = shadertype(ext) @@ -161,7 +164,7 @@ function get_template!(cache::ShaderCache, path, view, attributes) # can't yet be in here, since we didn't even have template keys cache.shader_cache[path] = Dict(template_replacements => s) - template_keys + return template_keys end end @@ -212,6 +215,7 @@ function gl_convert(cache::ShaderCache, lazyshader::AbstractLazyShader, data) paths = lazyshader.paths if all(x-> isa(x, Shader), paths) fragdatalocation = get(kw_dict, :fragdatalocation, Tuple{Int, String}[]) + @assert cache.context === ShaderAbstractions.current_context() return compile_program([paths...], fragdatalocation) end v = get_view(kw_dict) @@ -227,8 +231,10 @@ function gl_convert(cache::ShaderCache, lazyshader::AbstractLazyShader, data) shaders = map(paths) do source_typ source, typ = source_typ src, _ = template2source(source, v, data) + @assert cache.context === ShaderAbstractions.current_context() compile_shader(Vector{UInt8}(src), typ, :from_string) end + @assert cache.context === ShaderAbstractions.current_context() return compile_program([shaders...], fragdatalocation) end if !all(x -> isa(x, AbstractString), paths) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 7d2e6d774d7..ee5596f611d 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -441,7 +441,7 @@ function Screen(; # tell GLAbstraction that we created a new context. # This is important for resource tracking, and only needed for the first context ShaderAbstractions.switch_context!(window) - shader_cache = GLAbstraction.ShaderCache() + shader_cache = GLAbstraction.ShaderCache(window) push!(GLFW_WINDOWS, window) resize_native!(window, resolution...) From b8d17af9609886b072ca5807d689e7a26ddc40c7 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 29 Sep 2022 13:12:11 +0200 Subject: [PATCH 22/56] clean up ROBJ and context switching --- GLMakie/src/GLAbstraction/GLAbstraction.jl | 2 -- GLMakie/src/GLAbstraction/GLRenderObject.jl | 23 -------------- GLMakie/src/GLAbstraction/GLShader.jl | 9 +++--- GLMakie/src/GLAbstraction/GLTypes.jl | 25 +++++++-------- GLMakie/src/display.jl | 1 - GLMakie/src/glshaders/lines.jl | 2 +- GLMakie/src/glshaders/visualize_interface.jl | 33 +++++++++----------- GLMakie/src/rendering.jl | 9 +----- GLMakie/src/screen.jl | 5 +-- 9 files changed, 34 insertions(+), 75 deletions(-) diff --git a/GLMakie/src/GLAbstraction/GLAbstraction.jl b/GLMakie/src/GLAbstraction/GLAbstraction.jl index 120e1b40479..67657c15191 100644 --- a/GLMakie/src/GLAbstraction/GLAbstraction.jl +++ b/GLMakie/src/GLAbstraction/GLAbstraction.jl @@ -54,8 +54,6 @@ export gpu_data # gets the data of a gpu array as a Julia Array export RenderObject # An object which holds all GPU handles and datastructes to ready for rendering by calling render(obj) export prerender! # adds a function to a RenderObject, which gets executed befor setting the OpenGL render state export postrender! # adds a function to a RenderObject, which gets executed after setting the OpenGL render states -export std_renderobject # creates a renderobject with standard parameters -export instanced_renderobject # simplification for creating a RenderObject which renders instances export extract_renderable export set_arg! export GLVertexArray # VertexArray wrapper object diff --git a/GLMakie/src/GLAbstraction/GLRenderObject.jl b/GLMakie/src/GLAbstraction/GLRenderObject.jl index 284695c5397..fe644f5c586 100644 --- a/GLMakie/src/GLAbstraction/GLRenderObject.jl +++ b/GLMakie/src/GLAbstraction/GLRenderObject.jl @@ -1,16 +1,7 @@ -function RenderObject( - data::Dict{Symbol}, program, pre, - bbs=Observable(Rect3f(Vec3f(0), Vec3f(1))), - main=nothing - ) - RenderObject(convert(Dict{Symbol,Any}, data), program, pre, bbs, main) -end - function Base.show(io::IO, obj::RenderObject) println(io, "RenderObject with ID: ", obj.id) end - Base.getindex(obj::RenderObject, symbol::Symbol) = obj.uniforms[symbol] Base.setindex!(obj::RenderObject, value, symbol::Symbol) = obj.uniforms[symbol] = value @@ -94,19 +85,5 @@ struct EmptyPrerender end export EmptyPrerender export prerendertype -function instanced_renderobject(data, program, bb=Observable(Rect3f(Vec3f(0), Vec3f(1))), primitive::GLenum=GL_TRIANGLES, main=nothing) - pre = StandardPrerender() - robj = RenderObject(convert(Dict{Symbol,Any}, data), program, pre, nothing, bb, main) - robj.postrenderfunction = StandardPostrenderInstanced(main, robj.vertexarray, primitive) - robj -end - -function std_renderobject(data, program, bb=Observable(Rect3f(Vec3f(0), Vec3f(1))), primitive=GL_TRIANGLES, main=nothing) - pre = StandardPrerender() - robj = RenderObject(convert(Dict{Symbol,Any}, data), program, pre, nothing, bb, main) - robj.postrenderfunction = StandardPostrender(robj.vertexarray, primitive) - robj -end - prerendertype(::Type{RenderObject{Pre}}) where {Pre} = Pre prerendertype(::RenderObject{Pre}) where {Pre} = Pre diff --git a/GLMakie/src/GLAbstraction/GLShader.jl b/GLMakie/src/GLAbstraction/GLShader.jl index 910e7bd1b7c..edbc7efff8c 100644 --- a/GLMakie/src/GLAbstraction/GLShader.jl +++ b/GLMakie/src/GLAbstraction/GLShader.jl @@ -144,7 +144,7 @@ function get_shader!(cache::ShaderCache, path, template_replacement) return get!(shader_dict, template_replacement) do template_source = read(path, String) source = mustache_replace(template_replacement, template_source) - @assert cache.context === ShaderAbstractions.current_context() + ShaderAbstractions.switch_context!(cache.context) return compile_shader(path, source) end::Shader end @@ -215,7 +215,7 @@ function gl_convert(cache::ShaderCache, lazyshader::AbstractLazyShader, data) paths = lazyshader.paths if all(x-> isa(x, Shader), paths) fragdatalocation = get(kw_dict, :fragdatalocation, Tuple{Int, String}[]) - @assert cache.context === ShaderAbstractions.current_context() + ShaderAbstractions.switch_context!(cache.context) return compile_program([paths...], fragdatalocation) end v = get_view(kw_dict) @@ -231,10 +231,10 @@ function gl_convert(cache::ShaderCache, lazyshader::AbstractLazyShader, data) shaders = map(paths) do source_typ source, typ = source_typ src, _ = template2source(source, v, data) - @assert cache.context === ShaderAbstractions.current_context() + ShaderAbstractions.switch_context!(cache.context) compile_shader(Vector{UInt8}(src), typ, :from_string) end - @assert cache.context === ShaderAbstractions.current_context() + ShaderAbstractions.switch_context!(cache.context) return compile_program([shaders...], fragdatalocation) end if !all(x -> isa(x, AbstractString), paths) @@ -257,6 +257,7 @@ function gl_convert(cache::ShaderCache, lazyshader::AbstractLazyShader, data) tr = Dict(zip(template_keys[i], replacements[i])) shaders[i] = get_shader!(cache, path, tr) end + ShaderAbstractions.switch_context!(cache.context) compile_program(shaders, fragdatalocation) end end diff --git a/GLMakie/src/GLAbstraction/GLTypes.jl b/GLMakie/src/GLAbstraction/GLTypes.jl index e6d7960ddd1..e066dc88a3b 100644 --- a/GLMakie/src/GLAbstraction/GLTypes.jl +++ b/GLMakie/src/GLAbstraction/GLTypes.jl @@ -272,7 +272,6 @@ function Base.show(io::IO, vao::GLVertexArray) println(io, "\nGLVertexArray $(vao.id) indices: ", vao.indices) end - ################################################################################## const RENDER_OBJECT_ID_COUNTER = Ref(zero(UInt32)) @@ -283,17 +282,16 @@ function pack_bool(id, bool) end mutable struct RenderObject{Pre} - main # main object + context # OpenGL context uniforms::Dict{Symbol,Any} vertexarray::GLVertexArray prerenderfunction::Pre postrenderfunction id::UInt32 - boundingbox # TODO, remove, basicaly deprecated function RenderObject{Pre}( - main, uniforms::Dict{Symbol,Any}, vertexarray::GLVertexArray, - prerenderfunctions, postrenderfunctions, - boundingbox + context, + uniforms::Dict{Symbol,Any}, vertexarray::GLVertexArray, + prerenderfunctions, postrenderfunctions ) where Pre fxaa = to_value(pop!(uniforms, :fxaa, true)) RENDER_OBJECT_ID_COUNTER[] += one(UInt32) @@ -304,20 +302,22 @@ mutable struct RenderObject{Pre} # and since this is a UUID, it shouldn't matter id = pack_bool(RENDER_OBJECT_ID_COUNTER[], fxaa) new( - main, uniforms, vertexarray, + context, + uniforms, vertexarray, prerenderfunctions, postrenderfunctions, - id, boundingbox + id ) end end - function RenderObject( data::Dict{Symbol,Any}, program, pre::Pre, post, - bbs=Observable(Rect3f(Vec3f(0), Vec3f(1))), - main=nothing + context=current_context() ) where Pre + + switch_context!(context) + targets = get(data, :gl_convert_targets, Dict()) delete!(data, :gl_convert_targets) passthrough = Dict{Symbol,Any}() # we also save a few non opengl related values in data @@ -352,12 +352,11 @@ function RenderObject( p = gl_convert(to_value(program), data) # "compile" lazyshader vertexarray = GLVertexArray(Dict(buffers), p) robj = RenderObject{Pre}( - main, + context, data, vertexarray, pre, post, - bbs ) # automatically integrate object ID, will be discarded if shader doesn't use it robj[:objectid] = robj.id diff --git a/GLMakie/src/display.jl b/GLMakie/src/display.jl index f610f8745c3..9cf6bd0a3db 100644 --- a/GLMakie/src/display.jl +++ b/GLMakie/src/display.jl @@ -8,7 +8,6 @@ function Base.display(screen::Screen, scene::Scene; connect=true) window_open[] = open end connect && connect_screen(scene, screen) - ShaderAbstractions.switch_context!(screen.glscreen) pollevents(screen) insertplots!(screen, scene) pollevents(screen) diff --git a/GLMakie/src/glshaders/lines.jl b/GLMakie/src/glshaders/lines.jl index 198b6d37ac6..aded0e784ae 100644 --- a/GLMakie/src/glshaders/lines.jl +++ b/GLMakie/src/glshaders/lines.jl @@ -118,7 +118,7 @@ function draw_linesegments(shader_cache, positions::VectorTypes{T}, data::Dict) # TODO update boundingbox transparency = false shader = GLVisualizeShader( - shader_cache, + shader_cache, "fragment_output.frag", "util.vert", "line_segment.vert", "line_segment.geom", "lines.frag", view = Dict( "buffers" => output_buffers(to_value(transparency)), diff --git a/GLMakie/src/glshaders/visualize_interface.jl b/GLMakie/src/glshaders/visualize_interface.jl index 65c48feff49..00a3e1f252a 100644 --- a/GLMakie/src/glshaders/visualize_interface.jl +++ b/GLMakie/src/glshaders/visualize_interface.jl @@ -83,42 +83,42 @@ struct GLVisualizeShader <: AbstractLazyShader new(shader_cache, map(x -> loadshader(x), paths), args) end end + function GLAbstraction.gl_convert(shader::GLVisualizeShader, data) GLAbstraction.gl_convert(shader.shader_cache, shader, data) end -function assemble_robj(data, program, bb, primitive, pre_fun, post_fun) +function assemble_shader(data) + shader = data[:shader]::GLVisualizeShader + delete!(data, :shader) + primitive = get(data, :gl_primitive, GL_TRIANGLES) + pre_fun = get(data, :prerender, nothing) + post_fun = get(data, :postrender, nothing) + transp = get(data, :transparency, Observable(false)) overdraw = get(data, :overdraw, Observable(false)) + pre = if !isnothing(pre_fun) _pre_fun = GLAbstraction.StandardPrerender(transp, overdraw) ()->(_pre_fun(); pre_fun()) else GLAbstraction.StandardPrerender(transp, overdraw) end - robj = RenderObject(data, program, pre, nothing, bb, nothing) + + robj = RenderObject(data, shader, pre, shader.shader_cache.context) + post = if haskey(data, :instances) GLAbstraction.StandardPostrenderInstanced(data[:instances], robj.vertexarray, primitive) else GLAbstraction.StandardPostrender(robj.vertexarray, primitive) end + robj.postrenderfunction = if !isnothing(post_fun) () -> (post(); post_fun()) else post end - robj -end - -function assemble_shader(data) - shader = data[:shader] - delete!(data, :shader) - glp = get(data, :gl_primitive, GL_TRIANGLES) - return assemble_robj( - data, shader, Rect3f(), glp, - get(data, :prerender, nothing), - get(data, :postrender, nothing) - ) + return robj end """ @@ -148,11 +148,6 @@ to_index_buffer(x) = error( Please choose from Int, Vector{UnitRange{Int}}, Vector{Int} or a signal of either of them" ) -function visualize(@nospecialize(main), @nospecialize(s), @nospecialize(data)) - data = _default(main, s, copy(data)) - return assemble_shader(data) -end - function output_buffers(transparency = false) if transparency """ diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index 14ef3e08661..daf822675d9 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -5,7 +5,6 @@ function vsynced_renderloop(screen) if SCREEN_CONFIG[].pause_rendering sleep(0.1) else - ShaderAbstractions.switch_context!(screen.glscreen) render_frame(screen) GLFW.SwapBuffers(to_native(screen)) yield() @@ -21,7 +20,6 @@ function fps_renderloop(screen::Screen, framerate=SCREEN_CONFIG[].framerate) if SCREEN_CONFIG[].pause_rendering sleep(0.1) else - ShaderAbstractions.switch_context!(screen.glscreen) render_frame(screen) GLFW.SwapBuffers(to_native(screen)) t_elapsed = (time_ns() - t) / 1e9 @@ -37,7 +35,6 @@ end function renderloop(screen; framerate=SCREEN_CONFIG[].framerate) isopen(screen) || error("Screen most be open to run renderloop!") - ShaderAbstractions.switch_context!(screen.glscreen) try if SCREEN_CONFIG[].vsync GLFW.SwapInterval(1) @@ -89,11 +86,7 @@ Renders a single frame of a `window` """ function render_frame(screen::Screen; resize_buffers=true) nw = to_native(screen) - if !ShaderAbstractions.is_context_active(nw) - @debug("Current context does not match the current screen.") - return - end - + ShaderAbstractions.switch_context!(nw) function sortby(x) robj = x[3] plot = screen.cache2plot[robj.id] diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index ee5596f611d..3dfbdcf5e10 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -180,7 +180,6 @@ function Base.empty!(screen::Screen) end const GLFW_WINDOWS = GLFW.Window[] - const SINGLETON_SCREEN = Screen[] const SINGLETON_SCREEN_NO_RENDERLOOP = Screen[] @@ -337,12 +336,11 @@ specific context. """ function rewrap(robj::RenderObject{Pre}) where Pre RenderObject{Pre}( - robj.main, + robj.context, robj.uniforms, GLVertexArray(robj.vertexarray), robj.prerenderfunction, robj.postrenderfunction, - robj.boundingbox, ) end @@ -480,7 +478,6 @@ function Screen(; end function refreshwindowcb(window, screen) - ShaderAbstractions.switch_context!(screen.glscreen) screen.render_tick[] = nothing render_frame(screen) GLFW.SwapBuffers(window) From 76594d1cd16b28c9a80ab5197edbd8c025ec2a80 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 29 Sep 2022 13:45:32 +0200 Subject: [PATCH 23/56] fix RPRMakie --- RPRMakie/examples/materials.jl | 2 +- RPRMakie/examples/opengl_interop.jl | 2 +- RPRMakie/src/RPRMakie.jl | 2 +- RPRMakie/src/scene.jl | 41 +++++++------------------ RPRMakie/test/runtests.jl | 2 +- docs/documentation/backends/rprmakie.md | 12 ++++---- 6 files changed, 21 insertions(+), 40 deletions(-) diff --git a/RPRMakie/examples/materials.jl b/RPRMakie/examples/materials.jl index 68160f9b9cf..65e4a92283b 100644 --- a/RPRMakie/examples/materials.jl +++ b/RPRMakie/examples/materials.jl @@ -8,7 +8,7 @@ image = begin PointLight(Vec3f(10), RGBf(radiance, radiance, radiance * 1.1))] fig = Figure(; resolution=(1500, 700)) ax = LScene(fig[1, 1]; show_axis=false, scenekw=(lights=lights,)) - screen = RPRScreen(ax.scene; plugin=RPR.Northstar, iterations=400) + screen = Screen(ax.scene; plugin=RPR.Northstar, iterations=400) matsys = screen.matsys emissive = RPR.EmissiveMaterial(matsys) diff --git a/RPRMakie/examples/opengl_interop.jl b/RPRMakie/examples/opengl_interop.jl index 2e467b7ca25..40a08cb4d7a 100644 --- a/RPRMakie/examples/opengl_interop.jl +++ b/RPRMakie/examples/opengl_interop.jl @@ -13,7 +13,7 @@ lights = [EnvironmentLight(1.0, load(RPR.assetpath("studio026.exr"))), fig = Figure(; resolution=(1500, 1000)) ax = LScene(fig[1, 1]; show_axis=false, scenekw=(lights=lights,)) -screen = RPRMakie.RPRScreen(size(ax.scene); plugin=RPR.Tahoe) +screen = RPRMakie.Screen(size(ax.scene); plugin=RPR.Tahoe) material = RPR.UberMaterial(screen.matsys) surface!(ax, f.(u, v'), g.(u, v'), h.(u, v'); ambient=Vec3f(0.5), diffuse=Vec3f(1), specular=0.5, diff --git a/RPRMakie/src/RPRMakie.jl b/RPRMakie/src/RPRMakie.jl index 5907ed9da81..75bc7c8cee6 100644 --- a/RPRMakie/src/RPRMakie.jl +++ b/RPRMakie/src/RPRMakie.jl @@ -53,6 +53,6 @@ for name in names(Makie; all=true) end end -export RPRScreen, RPR, colorbuffer +export RPR, colorbuffer end diff --git a/RPRMakie/src/scene.jl b/RPRMakie/src/scene.jl index 1cf29373053..175f9f7f52d 100644 --- a/RPRMakie/src/scene.jl +++ b/RPRMakie/src/scene.jl @@ -99,7 +99,7 @@ function to_rpr_scene(context::RPR.Context, matsys, mscene::Makie.Scene) return scene end -function replace_scene_rpr!(scene::Makie.Scene, screen=RPRScreen(scene); refresh=Observable(nothing)) +function replace_scene_rpr!(scene::Makie.Scene, screen=Screen(scene); refresh=Observable(nothing)) context = screen.context matsys = screen.matsys screen.scene = scene @@ -140,9 +140,7 @@ function replace_scene_rpr!(scene::Makie.Scene, screen=RPRScreen(scene); refresh return context, task, rpr_scene end -struct RPRBackend <: Makie.AbstractBackend end - -mutable struct RPRScreen <: Makie.MakieScreen +mutable struct Screen <: Makie.MakieScreen context::RPR.Context matsys::RPR.MaterialSystem framebuffer1::RPR.FrameBuffer @@ -155,14 +153,16 @@ mutable struct RPRScreen <: Makie.MakieScreen cleared::Bool end -function RPRScreen(scene::Scene; kw...) +function Screen(scene::Scene; kw...) fb_size = size(scene) - screen = RPRScreen(fb_size; kw...) + screen = Screen(fb_size; kw...) screen.scene = scene return screen end -function RPRScreen(fb_size::NTuple{2,<:Integer}; +Screen(scene::Scene, ::IO, ::MIME; kw...) = Screen(scene; kw...) + +function Screen(fb_size::NTuple{2,<:Integer}; iterations=NUM_ITERATIONS[], resource=RENDER_RESOURCE[], plugin=RENDER_PLUGIN[], max_recursion=10) @@ -173,7 +173,7 @@ function RPRScreen(fb_size::NTuple{2,<:Integer}; framebuffer1 = RPR.FrameBuffer(context, RGBA, fb_size) framebuffer2 = RPR.FrameBuffer(context, RGBA, fb_size) set!(context, RPR.RPR_AOV_COLOR, framebuffer1) - return RPRScreen(context, matsys, framebuffer1, framebuffer2, fb_size, nothing, false, nothing, + return Screen(context, matsys, framebuffer1, framebuffer2, fb_size, nothing, false, nothing, iterations, false) end @@ -201,7 +201,7 @@ function render(screen; clear=true, iterations=screen.iterations) return framebuffer2 end -function Makie.colorbuffer(screen::RPRScreen) +function Makie.colorbuffer(screen::Screen) if !screen.setup_scene display(screen, screen.scene) end @@ -213,13 +213,7 @@ function Makie.colorbuffer(screen::RPRScreen) end end -function display(::RPRBackend, scene::Scene; kw...) - screen = RPRScreen(scene) - display(screen, scene) - return screen -end - -function display(screen::RPRScreen, scene::Scene) +function Base.display(screen::Screen, scene::Scene; display_kw...) screen.scene = scene rpr_scene = to_rpr_scene(screen.context, screen.matsys, scene) screen.rpr_scene = rpr_scene @@ -232,23 +226,10 @@ function display(screen::RPRScreen, scene::Scene) return screen end -function Base.insert!(screen::RPRScreen, scene::Scene, plot::AbstractPlot) +function Base.insert!(screen::Screen, scene::Scene, plot::AbstractPlot) context = screen.context matsys = screen.matsys rpr_scene = screen.rpr_scene insert_plots!(context, matsys, rpr_scene, scene, plot) return screen end - -function Makie.backend_show(b::RPRBackend, io::IO, ::MIME"image/png", scene::Scene) - screen = display(b, scene) - img = Makie.colorbuffer(screen) - FileIO.save(FileIO.Stream{FileIO.format"PNG"}(Makie.raw_io(io)), img) - return screen -end - -function Base.show(io::IO, ::MIME"image/png", screen::RPRScreen) - img = Makie.colorbuffer(screen) - FileIO.save(FileIO.Stream{FileIO.format"PNG"}(Makie.raw_io(io)), img) - return screen -end diff --git a/RPRMakie/test/runtests.jl b/RPRMakie/test/runtests.jl index 8be168cd36d..15f846d28ed 100644 --- a/RPRMakie/test/runtests.jl +++ b/RPRMakie/test/runtests.jl @@ -5,5 +5,5 @@ RPRMakie.activate!(resource=RPR.RPR_CREATION_FLAGS_ENABLE_CPU, iterations=50) f, ax, pl = meshscatter(rand(Point3f, 100), color=:blue) out = joinpath(@__DIR__, "recorded") isdir(out) && rm(out) -mkdir("recorded") +mkdir(out) save(joinpath(out, "test.png"), ax.scene); diff --git a/docs/documentation/backends/rprmakie.md b/docs/documentation/backends/rprmakie.md index 7d097267c69..b3d2c3b2a55 100644 --- a/docs/documentation/backends/rprmakie.md +++ b/docs/documentation/backends/rprmakie.md @@ -42,9 +42,9 @@ lights = [ ax = LScene(fig[1, 1]; scenekw=(lights=lights,)) # to create materials, one needs access to the RPR context. -# Note, if you create an RPRScreen manually, don't display the scene or fig anymore, since that would create a new RPR context, in which resources from the manually created Context would be invalid. Since RPRs error handling is pretty bad, this usually results in Segfaults. +# Note, if you create an Screen manually, don't display the scene or fig anymore, since that would create a new RPR context, in which resources from the manually created Context would be invalid. Since RPRs error handling is pretty bad, this usually results in Segfaults. # See below how to render a picture with a manually created context -screen = RPRScreen(ax.scene; iterations=10, plugin=RPR.Northstar) +screen = Screen(ax.scene; iterations=10, plugin=RPR.Northstar) matsys = screen.matsys context = screen.context # You can use lots of materials from RPR. @@ -55,7 +55,7 @@ mat = RPR.Chrome(matsys) mesh!(ax, Sphere(Point3f, 0), material=mat) # There are three main ways to turn a Makie scene into a picture: -# Get the colorbuffer of the RPRScreen. RPRScreen also has `show` overloaded for the mime `image\png` so it should display in IJulia/Jupyter/VSCode. +# Get the colorbuffer of the Screen. Screen also has `show` overloaded for the mime `image\png` so it should display in IJulia/Jupyter/VSCode. image = colorbuffer(screen)::Matrix{RGB{N0f8}} # Replace a specific (sub) LScene with RPR, and display the whole scene interactively in GLMakie using GLMakie @@ -64,7 +64,7 @@ GLMakie.activate!(); display(fig) # Make sure to display scene first in GLMakie # Replace the scene with an interactively rendered RPR output. # See more about this in the GLMakie interop example context, task = RPRMakie.replace_scene_rpr!(ax.scene, screen; refresh=refresh) -# If one doesn't create the RPRScreen manually to create custom materials, +# If one doesn't create the Screen manually to create custom materials, # display(ax.scene), show(io, MIME"image/png", ax.scene), save("rpr.png", ax.scene) # Should work just like with other backends. # Note, that only the scene from LScene can be displayed directly, but soon, `display(fig)` should also work. @@ -92,7 +92,7 @@ lights = [EnvironmentLight(1.0, load(RPR.assetpath("studio026.exr"))), PointLight(Vec3f(10), RGBf(radiance, radiance, radiance * 1.1))] fig = Figure(; resolution=(1500, 700)) ax = LScene(fig[1, 1]; show_axis=false, scenekw=(; lights=lights)) -screen = RPRScreen(ax.scene; plugin=RPR.Northstar, iterations=400) +screen = Screen(ax.scene; plugin=RPR.Northstar, iterations=400) matsys = screen.matsys emissive = RPR.EmissiveMaterial(matsys) @@ -230,7 +230,7 @@ lights = [EnvironmentLight(1.0, load(RPR.assetpath("studio026.exr"))), fig = Figure(; resolution=(1500, 1000)) ax = LScene(fig[1, 1]; show_axis=false, scenekw=(; lights=lights)) -screen = RPRMakie.RPRScreen(size(ax.scene); plugin=RPR.Tahoe) +screen = RPRMakie.Screen(size(ax.scene); plugin=RPR.Tahoe) material = RPR.UberMaterial(screen.matsys) surface!(ax, f.(u, v'), g.(u, v'), h.(u, v'); ambient=Vec3f(0.5), diffuse=Vec3f(1), specular=0.5, From 0b0a148f16e299596bec7aab7d819262672b8932 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 29 Sep 2022 14:24:27 +0200 Subject: [PATCH 24/56] fix thy backends --- RPRMakie/src/scene.jl | 1 + WGLMakie/src/WGLMakie.jl | 3 +-- WGLMakie/src/display.jl | 53 ++++++++++++++++++++++++++------------ WGLMakie/src/three_plot.jl | 7 +++-- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/RPRMakie/src/scene.jl b/RPRMakie/src/scene.jl index 175f9f7f52d..61c952d50c4 100644 --- a/RPRMakie/src/scene.jl +++ b/RPRMakie/src/scene.jl @@ -161,6 +161,7 @@ function Screen(scene::Scene; kw...) end Screen(scene::Scene, ::IO, ::MIME; kw...) = Screen(scene; kw...) +Screen(scene::Scene, ::Makie.ImageStorageFormat; kw...) = Screen(scene; kw...) function Screen(fb_size::NTuple{2,<:Integer}; iterations=NUM_ITERATIONS[], diff --git a/WGLMakie/src/WGLMakie.jl b/WGLMakie/src/WGLMakie.jl index efa43c0be6b..63d2748fc59 100644 --- a/WGLMakie/src/WGLMakie.jl +++ b/WGLMakie/src/WGLMakie.jl @@ -29,7 +29,6 @@ using Makie: apply_transform, transform_func_obs using Makie: spaces, is_data_space, is_pixel_space, is_relative_space, is_clip_space struct WebGL <: ShaderAbstractions.AbstractContext end -struct WGLBackend <: Makie.AbstractBackend end const THREE = Dependency(:THREE, ["https://unpkg.com/three@0.136.0/build/three.js"]) const WGL = Dependency(:WGLMakie, [@path joinpath(@__DIR__, "wglmakie.js")]) @@ -81,6 +80,6 @@ for name in names(Makie, all=true) end end -include("precompiles.jl") +# include("precompiles.jl") end # module diff --git a/WGLMakie/src/display.jl b/WGLMakie/src/display.jl index c88a7505144..4235cd6d694 100644 --- a/WGLMakie/src/display.jl +++ b/WGLMakie/src/display.jl @@ -10,12 +10,20 @@ function JSServe.jsrender(session::Session, fig::Makie.FigureLike) return JSServe.jsrender(session, Makie.get_scene(fig)) end -const WEB_MIMES = (MIME"text/html", MIME"application/vnd.webio.application+html", - MIME"application/prs.juno.plotpane+html", MIME"juliavscode/html") +const WEB_MIMES = ( + MIME"text/html", + MIME"application/vnd.webio.application+html", + MIME"application/prs.juno.plotpane+html", + MIME"juliavscode/html") + +mutable struct Screen <: Makie.MakieScreen + three::Union{Nothing, ThreeDisplay} + display::Any +end for M in WEB_MIMES @eval begin - function Makie.backend_show(::Screen, io::IO, m::$M, scene::Scene) + function Makie.backend_show(screen::Screen, io::IO, m::$M, scene::Scene) three = nothing inline_display = App() do session::Session three, canvas = three_display(session, scene) @@ -23,7 +31,8 @@ for M in WEB_MIMES return canvas end Base.show(io, m, inline_display) - return three + screen.three = three + return screen end end end @@ -32,23 +41,36 @@ function Makie.backend_showable(::Type{Screen}, ::T, scene::Scene) where {T<:MIM return T in WEB_MIMES end -struct Screen <: Makie.MakieScreen - three::Base.RefValue{ThreeDisplay} - display::Any +function Base.size(screen::Screen) + return size(get_three(screen)) end -Base.size(screen::Screen) = size(screen.three[]) +function get_three(screen::Screen)::ThreeDisplay + if isnothing(screen.three) + error("WGLMakie screen not yet shown in browser.") + end + return screen.three +end + +Screen() = Screen(nothing, nothing) -function display(::Screen, scene::Scene; kw...) +# TODO, create optimized screens, forward more options to JS/WebGL +Screen(::Scene; kw...) = Screen() +Screen(::Scene, ::IO, ::MIME; kw...) = Screen() +Screen(::Scene, ::Makie.ImageStorageFormat; kw...) = Screen() + +function Base.display(screen::Screen, scene::Scene; kw...) # Reference to three object which gets set once we serve this to a browser three_ref = Base.RefValue{ThreeDisplay}() - app = App() do s, request - three, canvas = three_display(s, scene) + app = App() do session, request + three, canvas = three_display(session, scene) three_ref[] = three return canvas end actual_display = display(app) - return Screen(three_ref, actual_display) + screen.three = wait_for_three(three_ref) + screen.display = actual_display + return screen end function Base.delete!(td::Screen, scene::Scene, plot::AbstractPlot) @@ -72,14 +94,13 @@ function Makie.colorbuffer(screen::Screen) return session2image(get_three(screen)) end - -function get_three(screen::Screen; timeout = 30)::Union{Nothing, ThreeDisplay} +function wait_for_three(three_ref::Base.RefValue{ThreeDisplay}; timeout = 30)::Union{Nothing, ThreeDisplay} # Screen is not guaranteed to get displayed in the browser, so we wait a while # to see if anything gets displayed! tstart = time() while time() - tstart < timeout - if isassigned(screen.three) - three = screen.three[] + if isassigned(three_ref) + three = three_ref[] session = JSServe.session(three) if isready(session.js_fully_loaded) # Error on js during init! We can't continue like this :'( diff --git a/WGLMakie/src/three_plot.jl b/WGLMakie/src/three_plot.jl index 6521e489196..b3a5ea80190 100644 --- a/WGLMakie/src/three_plot.jl +++ b/WGLMakie/src/three_plot.jl @@ -4,10 +4,9 @@ end JSServe.session(td::ThreeDisplay) = td.session -function GeometryBasics.widths(screen::ThreeDisplay) +function Base.size(screen::ThreeDisplay) # look at d.qs().clientWidth for displayed width width, height = round.(Int, WGLMakie.JSServe.evaljs_value(screen.session, WGLMakie.JSServe.js"[document.querySelector('canvas').width, document.querySelector('canvas').height]"; time_out=100)) - return (width, height) end @@ -57,7 +56,7 @@ function JSServe.print_js_code(io::IO, plot::AbstractPlot, context) JSServe.print_js_code(io, js"$(WGL).find_plots($(uuids))", context) end -function three_display(session::Session, scene::Scene) +function three_display(session::Session, scene::Scene; framerate=30.0) serialized = serialize_scene(scene) if TEXTURE_ATLAS_CHANGED[] @@ -89,7 +88,7 @@ function three_display(session::Session, scene::Scene) if ( renderer ) { const three_scenes = scenes.map(x=> $(WGL).deserialize_scene(x, canvas)) const cam = new $(THREE).PerspectiveCamera(45, 1, 0, 100) - $(WGL).start_renderloop(renderer, three_scenes, cam, $(CONFIG.fps[])) + $(WGL).start_renderloop(renderer, three_scenes, cam, $(framerate)) JSServe.on_update($canvas_width, w_h => { // `renderer.setSize` correctly updates `canvas` dimensions const pixelRatio = renderer.getPixelRatio(); From 5ab721a0b4e20606ed0219b7e63381e7ee622050 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 29 Sep 2022 16:03:02 +0200 Subject: [PATCH 25/56] fix stepper and docs --- GLMakie/src/display.jl | 1 + GLMakie/src/screen.jl | 3 +-- GLMakie/test/runtests.jl | 4 --- RPRMakie/test/lines.jl | 2 +- WGLMakie/src/display.jl | 8 +++++- WGLMakie/src/three_plot.jl | 4 ++- WGLMakie/src/wglmakie.js | 37 +++++++++++++++----------- docs/documentation/backends/glmakie.md | 2 +- docs/misc/banner/banner.jl | 4 +-- docs/utils.jl | 7 ----- 10 files changed, 36 insertions(+), 36 deletions(-) diff --git a/GLMakie/src/display.jl b/GLMakie/src/display.jl index 9cf6bd0a3db..215cb9ad61f 100644 --- a/GLMakie/src/display.jl +++ b/GLMakie/src/display.jl @@ -1,4 +1,5 @@ function Base.display(screen::Screen, scene::Scene; connect=true) + Makie.push_screen!(scene, screen) empty!(screen) resize!(screen, size(scene)...) # So, the GLFW window events are not guarantee to fire diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 3dfbdcf5e10..2c20f5f97a4 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -17,7 +17,7 @@ const SCREEN_CONFIG = Ref(( exit_renderloop = false,)) """ - set_window_config!(; + activate!(; renderloop = renderloop, vsync = false, framerate = 30.0, @@ -312,7 +312,6 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma end end - function Base.push!(screen::GLScreen, scene::Scene, robj) # filter out gc'ed elements filter!(screen.screen2scene) do (k, v) diff --git a/GLMakie/test/runtests.jl b/GLMakie/test/runtests.jl index 0eba55434f1..4d70462f618 100644 --- a/GLMakie/test/runtests.jl +++ b/GLMakie/test/runtests.jl @@ -11,10 +11,6 @@ Pkg.develop(PackageSpec(path = reference_tests_dir)) using ReferenceTests GLMakie.activate!() -# GLMakie.set_window_config!(; -# framerate = 1.0, -# pause_rendering = true -# ) @testset "mimes" begin f, ax, pl = scatter(1:4) diff --git a/RPRMakie/test/lines.jl b/RPRMakie/test/lines.jl index 81cca9617e7..e36c4698486 100644 --- a/RPRMakie/test/lines.jl +++ b/RPRMakie/test/lines.jl @@ -2,7 +2,7 @@ using GLMakie, GeometryBasics, RPRMakie, RadeonProRender using Colors, FileIO using Colors: N0f8 RPR = RadeonProRender -GLMakie.set_window_config!(float=true, focus_on_show=false) +GLMakie.activate!(float=true, focus_on_show=false) cat = load(GLMakie.assetpath("cat.obj")) begin diff --git a/WGLMakie/src/display.jl b/WGLMakie/src/display.jl index 4235cd6d694..7e8e209c26a 100644 --- a/WGLMakie/src/display.jl +++ b/WGLMakie/src/display.jl @@ -60,6 +60,7 @@ Screen(::Scene, ::IO, ::MIME; kw...) = Screen() Screen(::Scene, ::Makie.ImageStorageFormat; kw...) = Screen() function Base.display(screen::Screen, scene::Scene; kw...) + Makie.push_screen!(scene, screen) # Reference to three object which gets set once we serve this to a browser three_ref = Base.RefValue{ThreeDisplay}() app = App() do session, request @@ -78,8 +79,13 @@ function Base.delete!(td::Screen, scene::Scene, plot::AbstractPlot) end function session2image(sessionlike) + yield() s = JSServe.session(sessionlike) - to_data = js"document.querySelector('canvas').toDataURL()" + to_data = js"""function (){ + $(WGL).current_renderloop() + return document.querySelector('canvas').toDataURL() + }() + """ picture_base64 = JSServe.evaljs_value(s, to_data; time_out=100) picture_base64 = replace(picture_base64, "data:image/png;base64," => "") bytes = JSServe.Base64.base64decode(picture_base64) diff --git a/WGLMakie/src/three_plot.jl b/WGLMakie/src/three_plot.jl index b3a5ea80190..9c444cb307a 100644 --- a/WGLMakie/src/three_plot.jl +++ b/WGLMakie/src/three_plot.jl @@ -15,7 +15,9 @@ js_uuid(object) = string(objectid(object)) function Base.insert!(td::ThreeDisplay, scene::Scene, plot::Combined) plot_data = serialize_plots(scene, [plot]) - WGL.insert_plot(td.session, js_uuid(scene), plot_data) + JSServe.evaljs_value(td.session, js""" + $(WGL).insert_plot($(js_uuid(scene)), $plot_data) + """) return end diff --git a/WGLMakie/src/wglmakie.js b/WGLMakie/src/wglmakie.js index 8e65a44a817..f050180291e 100644 --- a/WGLMakie/src/wglmakie.js +++ b/WGLMakie/src/wglmakie.js @@ -4,6 +4,9 @@ const WGLMakie = (function () { // e.g. insert!(scene, plot) / delete!(scene, plot) const scene_cache = {}; const plot_cache = {}; + let current_renderloop = () => 0; + + const exports = {current_renderloop}; function add_scene(scene_id, three_scene) { scene_cache[scene_id] = three_scene; @@ -71,7 +74,7 @@ const WGLMakie = (function () { 0.0, 0.0, 0.0, 1.0 ); const id = new THREE.Uniform(new THREE.Matrix4()); - + if (plot_data.cam_space == "data") { plot_data.uniforms.view = cam.view; plot_data.uniforms.projection = cam.projection; @@ -635,6 +638,9 @@ const WGLMakie = (function () { } // render one time before starting loop, so that we don't wait 30ms before first render render_scenes(renderer, three_scenes, cam); + exports.current_renderloop = function (){ + render_scenes(renderer, three_scenes, cam); + } renderloop(); } @@ -741,19 +747,18 @@ const WGLMakie = (function () { return renderer; } - return { - deserialize_scene, - threejs_module, - start_renderloop, - deserialize_three, - render_scenes, - delete_plots, - insert_plot, - find_plots, - delete_scene, - find_scene, - scene_cache, - plot_cache, - delete_scenes, - }; + exports.deserialize_scene = deserialize_scene; + exports.threejs_module = threejs_module; + exports.start_renderloop = start_renderloop; + exports.deserialize_three = deserialize_three; + exports.render_scenes = render_scenes; + exports.delete_plots = delete_plots; + exports.insert_plot = insert_plot; + exports.find_plots = find_plots; + exports.delete_scene = delete_scene; + exports.find_scene = find_scene; + exports.scene_cache = scene_cache; + exports.plot_cache = plot_cache; + exports.delete_scenes = delete_scenes; + return exports })(); diff --git a/docs/documentation/backends/glmakie.md b/docs/documentation/backends/glmakie.md index bce9edda209..da6718dfb18 100644 --- a/docs/documentation/backends/glmakie.md +++ b/docs/documentation/backends/glmakie.md @@ -10,7 +10,7 @@ It requires an OpenGL enabled graphics card with OpenGL version 3.3 or higher. You can set parameters of the window with the function `set_window_config!` which only takes effect when opening a new window. ```julia -set_window_config!(; +activate!(; renderloop = renderloop, vsync = false, framerate = 30.0, diff --git a/docs/misc/banner/banner.jl b/docs/misc/banner/banner.jl index 7126c80a560..f2c9e9bb6b5 100644 --- a/docs/misc/banner/banner.jl +++ b/docs/misc/banner/banner.jl @@ -1,8 +1,6 @@ using GLMakie GLMakie.activate!() -set_window_config!(pause_rendering = false) - ## function copy_scene_settings(s) @@ -71,4 +69,4 @@ end using CairoMakie CairoMakie.activate!() -save(joinpath(@__DIR__, "bannermesh.png"), s, px_per_unit = 2) \ No newline at end of file +save(joinpath(@__DIR__, "bannermesh.png"), s, px_per_unit = 2) diff --git a/docs/utils.jl b/docs/utils.jl index 543b7d6efba..9539e1a0273 100644 --- a/docs/utils.jl +++ b/docs/utils.jl @@ -10,13 +10,6 @@ using Pkg include("colormap_generation.jl") -# Pause renderloop for slow software rendering. -# This way, we only render if we actualy save e.g. an image -GLMakie.set_window_config!(; - framerate = 1.0, - pause_rendering = true -) - # copy NEWS file over to documentation cp( joinpath(@__DIR__, "..", "NEWS.md"), From b768ee60369da52f0d106ea0c1cbd318f1dad5ac Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 29 Sep 2022 16:30:44 +0200 Subject: [PATCH 26/56] remove all inline! use --- docs/documentation/backends/rprmakie.md | 1 - docs/documentation/blocks.md | 4 +- docs/documentation/theming.md | 10 ++-- docs/examples/blocks/axis.md | 50 +++++++++---------- docs/examples/blocks/axis3.md | 10 ++-- docs/examples/blocks/intervalslider.md | 4 +- docs/examples/plotting_functions/arrows.md | 6 +-- docs/examples/plotting_functions/band.md | 6 +-- docs/examples/plotting_functions/barplot.md | 6 +-- docs/examples/plotting_functions/boxplot.md | 10 ++-- docs/examples/plotting_functions/contour.md | 4 +- docs/examples/plotting_functions/contour3d.md | 6 +-- docs/examples/plotting_functions/contourf.md | 8 +-- docs/examples/plotting_functions/crossbar.md | 2 +- docs/examples/plotting_functions/density.md | 16 +++--- docs/examples/plotting_functions/ecdf.md | 8 +-- docs/examples/plotting_functions/errorbars.md | 6 +-- docs/examples/plotting_functions/heatmap.md | 22 ++++---- docs/examples/plotting_functions/hexbin.md | 16 +++--- docs/examples/plotting_functions/hist.md | 6 +-- docs/examples/plotting_functions/image.md | 2 +- docs/examples/plotting_functions/lines.md | 6 +-- .../plotting_functions/linesegments.md | 2 +- docs/examples/plotting_functions/mesh.md | 6 +-- .../plotting_functions/meshscatter.md | 2 +- docs/examples/plotting_functions/pie.md | 8 +-- docs/examples/plotting_functions/poly.md | 10 ++-- docs/examples/plotting_functions/qqplot.md | 4 +- .../examples/plotting_functions/rainclouds.md | 4 +- docs/examples/plotting_functions/rangebars.md | 4 +- docs/examples/plotting_functions/scatter.md | 28 +++++------ .../plotting_functions/scatterlines.md | 2 +- docs/examples/plotting_functions/series.md | 2 +- docs/examples/plotting_functions/spy.md | 2 +- docs/examples/plotting_functions/stairs.md | 2 +- docs/examples/plotting_functions/stem.md | 8 +-- .../examples/plotting_functions/streamplot.md | 2 +- docs/examples/plotting_functions/surface.md | 6 +-- docs/examples/plotting_functions/text.md | 16 +++--- .../plotting_functions/tricontourf.md | 10 ++-- docs/examples/plotting_functions/violin.md | 12 ++--- .../plotting_functions/volumeslices.md | 2 +- docs/examples/plotting_functions/wireframe.md | 2 +- docs/tutorials/basic-tutorial.md | 2 +- 44 files changed, 172 insertions(+), 173 deletions(-) diff --git a/docs/documentation/backends/rprmakie.md b/docs/documentation/backends/rprmakie.md index b3d2c3b2a55..cc104d135b1 100644 --- a/docs/documentation/backends/rprmakie.md +++ b/docs/documentation/backends/rprmakie.md @@ -485,7 +485,6 @@ for i in 1:length(toLines) end earth_img = load(Downloads.download("https://upload.wikimedia.org/wikipedia/commons/5/56/Blue_Marble_Next_Generation_%2B_topography_%2B_bathymetry.jpg")) -Makie.inline!(true) # the actual plot ! RPRMakie.activate!(; iterations=100) scene = with_theme(theme_dark()) do diff --git a/docs/documentation/blocks.md b/docs/documentation/blocks.md index 3f672cc93a6..e011e8caf7d 100644 --- a/docs/documentation/blocks.md +++ b/docs/documentation/blocks.md @@ -17,7 +17,7 @@ Here's one way to add a `Block`, in this case an `Axis`, to a Figure. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() ax = Axis(f[1, 1]) @@ -38,7 +38,7 @@ Here's an example where two axes are placed manually: ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f, bbox = BBox(100, 300, 100, 500), title = "Axis 1") diff --git a/docs/documentation/theming.md b/docs/documentation/theming.md index cc7820b12d7..3b7ad8bdfba 100644 --- a/docs/documentation/theming.md +++ b/docs/documentation/theming.md @@ -22,7 +22,7 @@ Let's create a plot with the default theme: ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + function example_plot() f = Figure() @@ -152,7 +152,7 @@ with_theme( Theme( palette = (color = [:red, :blue], marker = [:circle, :xcross]), Scatter = (cycle = [:color, :marker],) - )) do + )) do scatter(fill(1, 10)) scatter!(fill(2, 10)) scatter!(fill(3, 10)) @@ -192,7 +192,7 @@ For example ```julia with_theme( Theme( - palette = (color = [:red, :blue], linestyle = [:dash, :dot]), + palette = (color = [:red, :blue], linestyle = [:dash, :dot]), Lines = (cycle = Cycle([:color, :linestyle], covary = true),) )) do lines(fill(5, 10)) @@ -217,7 +217,7 @@ The cycler's internal counter is not advanced when using `Cycled` for any attrib ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -247,7 +247,7 @@ Here's an example that shows how density plots react to different palette option ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + set_theme!() # hide diff --git a/docs/examples/blocks/axis.md b/docs/examples/blocks/axis.md index b1a473c378a..fbb2acc1b95 100644 --- a/docs/examples/blocks/axis.md +++ b/docs/examples/blocks/axis.md @@ -43,7 +43,7 @@ You can also remove all plots with `empty!(ax)`. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -75,7 +75,7 @@ but they will be changed to fit the chosen ratio. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -105,7 +105,7 @@ You can set half limits by either giving one argument as `nothing` or by using t ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -143,7 +143,7 @@ The user-defined limits are stored in `ax.limits`. This can either be a tuple wi ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -166,7 +166,7 @@ The gap between title and subtitle is set with `subtitlegap` and the gap between ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -226,7 +226,7 @@ Note that the number is only a target, the actual number of ticks can be higher ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + fig = Figure() for (i, n) in enumerate([2, 4, 6]) @@ -250,7 +250,7 @@ A common scenario is plotting a trigonometric function which should be marked at ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + lines(0..20, sin, axis = (xticks = MultiplesTicks(4, pi, "π"),)) ``` @@ -262,7 +262,7 @@ lines(0..20, sin, axis = (xticks = MultiplesTicks(4, pi, "π"),)) ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + lines(0..20, sin, axis = (xticks = 0:3:18,)) ``` @@ -274,7 +274,7 @@ lines(0..20, sin, axis = (xticks = 0:3:18,)) ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + values = [0, 5, 10, 15, 20] labels = ["zero", "five", "ten", "fifteen", "twenty"] @@ -294,7 +294,7 @@ This can be useful for log plots where all powers of 10 should be shown. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + struct IntegerTicks end @@ -319,7 +319,7 @@ For example, you could append "k" for numbers larger than one thousand: ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + function k_suffix(values) map(values) do v @@ -346,7 +346,7 @@ Color, size and alignment of the mirrored ticks are the same as for the normal t ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1], @@ -375,7 +375,7 @@ Here are the same ticks with different format strings: ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() for (i, str) in enumerate(["{:.1f}", "{:.2f}", "t = {:.4f}ms"]) @@ -412,7 +412,7 @@ The idea is to not actually implement new tick finding, just to rescale the valu ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + struct TimeTicks end @@ -464,7 +464,7 @@ The default minor tick type is `IntervalsBetween(n, mirror = true)` where `n` gi ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + theme = Attributes( Axis = ( @@ -494,7 +494,7 @@ Minor ticks can also be given as an `AbstractVector` of real numbers. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + lines(1..10, sin, axis = ( yminorgridvisible = true, @@ -517,7 +517,7 @@ To hide spines, you can use `hidespines!`. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -539,7 +539,7 @@ It's common, e.g., to hide everything but the grid lines in facet plots. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -563,7 +563,7 @@ The attributes `xtrimspine` and `ytrimspine` can be used to limit the respective ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + hist(randn(100) ./ 4 .+ 5, strokewidth = 1, @@ -591,7 +591,7 @@ Take care that the axis limits always stay inside the limits appropriate for the ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + data = LinRange(0.01, 0.99, 200) @@ -618,7 +618,7 @@ Some plotting functions, like barplots or density plots, have offset parameters ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + processors = ["VAX-11/780", "Sun-4/260", "PowerPC 604", "Alpha 21164", "Intel Pentium III", "Intel Xeon"] @@ -642,7 +642,7 @@ Another option for symmetric log scales including zero is the symmetric log scal ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure(resolution = (800, 700)) @@ -678,7 +678,7 @@ it necessarily has to break the layout a little bit. using CairoMakie using FileIO CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random # hide Random.seed!(1) # hide @@ -794,7 +794,7 @@ separately. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -835,7 +835,7 @@ Note how x and y labels are misaligned in this figure due to different tick labe ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() diff --git a/docs/examples/blocks/axis3.md b/docs/examples/blocks/axis3.md index 0c95f04e9ba..504aa7f2ace 100644 --- a/docs/examples/blocks/axis3.md +++ b/docs/examples/blocks/axis3.md @@ -9,7 +9,7 @@ The two attributes `azimuth` and `elevation` control the angles from which the p using GLMakie using FileIO GLMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -52,7 +52,7 @@ You can also set it to a three-tuple, where each number gives the relative lengt using GLMakie using FileIO GLMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -88,7 +88,7 @@ On the other hand, it uses the available space most efficiently. ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -122,7 +122,7 @@ A value of 0 looks like an orthographic projection (it is only approximate to a ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure(resolution = (1200, 800), fontsize = 14) @@ -139,4 +139,4 @@ end f ``` -\end{examplefigure} \ No newline at end of file +\end{examplefigure} diff --git a/docs/examples/blocks/intervalslider.md b/docs/examples/blocks/intervalslider.md index 1ab23f9e745..79bcf559227 100644 --- a/docs/examples/blocks/intervalslider.md +++ b/docs/examples/blocks/intervalslider.md @@ -21,7 +21,7 @@ If you set the attribute `snap = false`, the slider will move continously while \begin{examplefigure}{} ```julia using CairoMakie -Makie.inline!(true) # hide + CairoMakie.activate!() # hide f = Figure() @@ -55,4 +55,4 @@ scatter!(points, color = colors, colormap = [:black, :orange], strokewidth = 0) f ``` -\end{examplefigure} \ No newline at end of file +\end{examplefigure} diff --git a/docs/examples/plotting_functions/arrows.md b/docs/examples/plotting_functions/arrows.md index e6782ab769d..07e0f621e85 100644 --- a/docs/examples/plotting_functions/arrows.md +++ b/docs/examples/plotting_functions/arrows.md @@ -40,7 +40,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure(resolution = (800, 800)) Axis(f[1, 1], backgroundcolor = "black") @@ -62,7 +62,7 @@ f ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + ps = [Point3f(x, y, z) for x in -5:2:5 for y in -5:2:5 for z in -5:2:5] ns = map(p -> 0.1 * Vec3f(p[2], p[3], p[1]), ps) @@ -79,7 +79,7 @@ arrows( ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + using LinearAlgebra ps = [Point3f(x, y, z) for x in -5:2:5 for y in -5:2:5 for z in -5:2:5] diff --git a/docs/examples/plotting_functions/band.md b/docs/examples/plotting_functions/band.md index f34c307ff8e..bacda2247c4 100644 --- a/docs/examples/plotting_functions/band.md +++ b/docs/examples/plotting_functions/band.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -29,7 +29,7 @@ f using Statistics using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -50,7 +50,7 @@ f ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + lower = fill(Point3f(0,0,0), 100) upper = [Point3f(sin(x), cos(x), 1.0) for x in range(0,2pi, length=100)] col = repeat([1:50;50:-1:1],outer=2) diff --git a/docs/examples/plotting_functions/barplot.md b/docs/examples/plotting_functions/barplot.md index c8be8c841f3..8c7c681b584 100644 --- a/docs/examples/plotting_functions/barplot.md +++ b/docs/examples/plotting_functions/barplot.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -27,7 +27,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = 1:0.2:10 ys = 0.5 .* sin.(xs) @@ -40,7 +40,7 @@ barplot(xs, ys, gap = 0, color = :gray85, strokecolor = :black, strokewidth = 1) ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + tbl = (x = [1, 1, 1, 2, 2, 2, 3, 3, 3], height = 0.1:0.1:0.9, diff --git a/docs/examples/plotting_functions/boxplot.md b/docs/examples/plotting_functions/boxplot.md index 3578347d081..5b26cbce7a6 100644 --- a/docs/examples/plotting_functions/boxplot.md +++ b/docs/examples/plotting_functions/boxplot.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = rand(1:3, 1000) ys = randn(1000) @@ -21,7 +21,7 @@ boxplot(xs, ys) ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = rand(1:3, 1000) ys = randn(1000) @@ -44,7 +44,7 @@ same color as their box, as shown above. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = rand(1:3, 1000) ys = randn(1000) @@ -60,7 +60,7 @@ boxplot(xs, ys, dodge = dodge, show_notch = true, color = map(d->d==1 ? :blue : ```julia using CairoMakie, Distributions CairoMakie.activate!() # hide -Makie.inline!(true) # hide + N = 100_000 x = rand(1:3, N) @@ -75,4 +75,4 @@ boxplot(fig[1,2], x, y, weights = w) fig ``` -\end{examplefigure} \ No newline at end of file +\end{examplefigure} diff --git a/docs/examples/plotting_functions/contour.md b/docs/examples/plotting_functions/contour.md index c22d05b8369..9b4beaa9539 100644 --- a/docs/examples/plotting_functions/contour.md +++ b/docs/examples/plotting_functions/contour.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -29,7 +29,7 @@ Omitting the `xs` and `ys` results in the indices of `zs` being used. We can als ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) diff --git a/docs/examples/plotting_functions/contour3d.md b/docs/examples/plotting_functions/contour3d.md index 6ca86f033a3..239050c7573 100644 --- a/docs/examples/plotting_functions/contour3d.md +++ b/docs/examples/plotting_functions/contour3d.md @@ -8,7 +8,7 @@ ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis3(f[1, 1], aspect=(0.5,0.5,1), perspectiveness=0.75) @@ -29,7 +29,7 @@ Omitting the `xs` and `ys` results in the indices of `zs` being used. We can als ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis3(f[1, 1], aspect=(0.5,0.5,1), perspectiveness=0.75) @@ -42,4 +42,4 @@ contour3d!(+zs, levels= .025:0.05:.475, linewidth=2, color=:red2) f ``` -\end{examplefigure} \ No newline at end of file +\end{examplefigure} diff --git a/docs/examples/plotting_functions/contourf.md b/docs/examples/plotting_functions/contourf.md index 9ec769c1bcc..bf8539a0b49 100644 --- a/docs/examples/plotting_functions/contourf.md +++ b/docs/examples/plotting_functions/contourf.md @@ -7,7 +7,7 @@ using CairoMakie using DelimitedFiles CairoMakie.activate!() # hide -Makie.inline!(true) # hide + volcano = readdlm(Makie.assetpath("volcano.csv"), ',', Float64) @@ -27,7 +27,7 @@ f using CairoMakie using DelimitedFiles CairoMakie.activate!() # hide -Makie.inline!(true) # hide + volcano = readdlm(Makie.assetpath("volcano.csv"), ',', Float64) @@ -51,7 +51,7 @@ f using CairoMakie using DelimitedFiles CairoMakie.activate!() # hide -Makie.inline!(true) # hide + volcano = readdlm(Makie.assetpath("volcano.csv"), ',', Float64) @@ -81,7 +81,7 @@ to `:relative` and specify the levels from 0 to 1, relative to the current minim using CairoMakie using DelimitedFiles CairoMakie.activate!() # hide -Makie.inline!(true) # hide + volcano = readdlm(Makie.assetpath("volcano.csv"), ',', Float64) diff --git a/docs/examples/plotting_functions/crossbar.md b/docs/examples/plotting_functions/crossbar.md index 3d82c0ae54d..0950f500584 100644 --- a/docs/examples/plotting_functions/crossbar.md +++ b/docs/examples/plotting_functions/crossbar.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = [1, 1, 2, 2, 3, 3] ys = rand(6) diff --git a/docs/examples/plotting_functions/density.md b/docs/examples/plotting_functions/density.md index 36bf9ad0323..18338ff82fb 100644 --- a/docs/examples/plotting_functions/density.md +++ b/docs/examples/plotting_functions/density.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -23,7 +23,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -38,7 +38,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -54,7 +54,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -78,7 +78,7 @@ You can color density plots with gradients by choosing `color = :x` or `:y`, dep ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", @@ -105,7 +105,7 @@ Due to technical limitations, if you color the `:vertical` dimension (or :horizo ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -123,7 +123,7 @@ f ```julia using CairoMakie, Distributions CairoMakie.activate!() # hide -Makie.inline!(true) # hide + N = 100_000 x = rand(Uniform(-2, 2), N) @@ -136,4 +136,4 @@ density(fig[1,2], x, weights = w) fig ``` -\end{examplefigure} \ No newline at end of file +\end{examplefigure} diff --git a/docs/examples/plotting_functions/ecdf.md b/docs/examples/plotting_functions/ecdf.md index 5c831afae9c..d1be0b69f59 100644 --- a/docs/examples/plotting_functions/ecdf.md +++ b/docs/examples/plotting_functions/ecdf.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -23,7 +23,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -40,13 +40,13 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) x = rand(200) -w = @. x^2 * (1 - x)^2 +w = @. x^2 * (1 - x)^2 ecdfplot!(x) ecdfplot!(x; weights = w, color=:orange) diff --git a/docs/examples/plotting_functions/errorbars.md b/docs/examples/plotting_functions/errorbars.md index b0a9ae5bfdf..ca6e04d3ccb 100644 --- a/docs/examples/plotting_functions/errorbars.md +++ b/docs/examples/plotting_functions/errorbars.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -32,7 +32,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -58,7 +58,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) diff --git a/docs/examples/plotting_functions/heatmap.md b/docs/examples/plotting_functions/heatmap.md index 2fe0e7469f4..3192fc87548 100644 --- a/docs/examples/plotting_functions/heatmap.md +++ b/docs/examples/plotting_functions/heatmap.md @@ -10,7 +10,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = range(0, 10, length = 25) @@ -27,7 +27,7 @@ heatmap(xs, ys, zs) ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + function mandelbrot(x, y) z = c = x + y*im @@ -47,7 +47,7 @@ There must be no duplicate combinations of x and y, but it is allowed to leave o ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = [1, 2, 3, 1, 2, 3, 1, 2, 3] @@ -60,15 +60,15 @@ heatmap(xs, ys, zs) ### Colorbar for single heatmap -To get a scale for what the colors represent, add a colorbar. The colorbar is -placed within the figure in the first argument, and the scale and colormap can be +To get a scale for what the colors represent, add a colorbar. The colorbar is +placed within the figure in the first argument, and the scale and colormap can be conveniently set by passing the relevant heatmap to it. \begin{examplefigure}{} ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = range(0, 2π, length=100) ys = range(0, 2π, length=100) @@ -84,20 +84,20 @@ fig ### Colorbar for multiple heatmaps When there are several heatmaps in a single figure, it can be useful -to have a single colorbar represent all of them. It is important to then +to have a single colorbar represent all of them. It is important to then have synchronized scales and colormaps for the heatmaps and colorbar. This is done by -setting the colorrange explicitly, so that it is independent of the data shown by +setting the colorrange explicitly, so that it is independent of the data shown by that particular heatmap. -Since the heatmaps in the example below have the same colorrange and colormap, any of them -can be passed to `Colorbar` to give the colorbar the same attributes. Alternativly, +Since the heatmaps in the example below have the same colorrange and colormap, any of them +can be passed to `Colorbar` to give the colorbar the same attributes. Alternativly, the colorbar attributes can be set explicitly. \begin{examplefigure}{} ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = range(0, 2π, length=100) ys = range(0, 2π, length=100) diff --git a/docs/examples/plotting_functions/hexbin.md b/docs/examples/plotting_functions/hexbin.md index 09372ef942e..76c6ad91eb1 100644 --- a/docs/examples/plotting_functions/hexbin.md +++ b/docs/examples/plotting_functions/hexbin.md @@ -13,7 +13,7 @@ The minimum number of bins in one dimension is 2. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random Random.seed!(1234) @@ -39,7 +39,7 @@ You can also pass a tuple of integers to control x and y separately. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random Random.seed!(1234) @@ -71,7 +71,7 @@ This is why setting the same size for x and y will result in uneven hexagons. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random Random.seed!(1234) @@ -99,7 +99,7 @@ Note that the visual appearance of the hexagons will only be even if the x and y ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random Random.seed!(1234) @@ -127,7 +127,7 @@ All hexagons with a count lower than `threshold` will be removed: ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random Random.seed!(1234) @@ -153,7 +153,7 @@ You can pass a scale function to via the `scale` keyword, which will be applied ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random Random.seed!(1234) @@ -178,10 +178,10 @@ In this example, we add a transparent color to the start of the colormap and str ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using DelimitedFiles CairoMakie.activate!() # hide -Makie.inline!(true) # hide + a = map(Point2f, eachrow(readdlm(assetpath("airportlocations.csv")))) diff --git a/docs/examples/plotting_functions/hist.md b/docs/examples/plotting_functions/hist.md index ea4f850f093..6df7ff2520a 100644 --- a/docs/examples/plotting_functions/hist.md +++ b/docs/examples/plotting_functions/hist.md @@ -8,7 +8,7 @@ ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + data = randn(1000) @@ -63,7 +63,7 @@ fig ```julia using CairoMakie, Distributions CairoMakie.activate!() # hide -Makie.inline!(true) # hide + N = 100_000 x = rand(Uniform(-5, 5), N) @@ -76,4 +76,4 @@ hist(fig[1,2], x, weights = w) fig ``` -\end{examplefigure} \ No newline at end of file +\end{examplefigure} diff --git a/docs/examples/plotting_functions/image.md b/docs/examples/plotting_functions/image.md index 532b151efc9..3ad2dd9815c 100644 --- a/docs/examples/plotting_functions/image.md +++ b/docs/examples/plotting_functions/image.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using FileIO img = load(assetpath("cow.png")) diff --git a/docs/examples/plotting_functions/lines.md b/docs/examples/plotting_functions/lines.md index 0b6eec27cbd..bae56aa59c3 100644 --- a/docs/examples/plotting_functions/lines.md +++ b/docs/examples/plotting_functions/lines.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -31,7 +31,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -50,4 +50,4 @@ end f ``` -\end{examplefigure} \ No newline at end of file +\end{examplefigure} diff --git a/docs/examples/plotting_functions/linesegments.md b/docs/examples/plotting_functions/linesegments.md index a674cdcfebd..783f4bde81d 100644 --- a/docs/examples/plotting_functions/linesegments.md +++ b/docs/examples/plotting_functions/linesegments.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) diff --git a/docs/examples/plotting_functions/mesh.md b/docs/examples/plotting_functions/mesh.md index 5223389aa7c..9f7941e0cc4 100644 --- a/docs/examples/plotting_functions/mesh.md +++ b/docs/examples/plotting_functions/mesh.md @@ -8,7 +8,7 @@ ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + vertices = [ 0.0 0.0; @@ -33,7 +33,7 @@ scene = mesh(vertices, faces, color = colors, shading = false) using FileIO using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + brain = load(assetpath("brain.stl")) @@ -53,7 +53,7 @@ We can also create a mesh, to specify normals, uv coordinates: ```julia:mesh using GeometryBasics, LinearAlgebra, GLMakie, FileIO GLMakie.activate!() # hide -Makie.inline!(true) # hide + # Create vertices for a Sphere r = 0.5f0 diff --git a/docs/examples/plotting_functions/meshscatter.md b/docs/examples/plotting_functions/meshscatter.md index 3abea123eeb..ff2b25f8367 100644 --- a/docs/examples/plotting_functions/meshscatter.md +++ b/docs/examples/plotting_functions/meshscatter.md @@ -8,7 +8,7 @@ ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + xs = cos.(1:0.5:20) ys = sin.(1:0.5:20) diff --git a/docs/examples/plotting_functions/pie.md b/docs/examples/plotting_functions/pie.md index fac01fbea20..78cee3f26f0 100644 --- a/docs/examples/plotting_functions/pie.md +++ b/docs/examples/plotting_functions/pie.md @@ -8,7 +8,7 @@ ### Generic -- `normalize = true` sets whether the data will be normalized to the range [0, 2π]. +- `normalize = true` sets whether the data will be normalized to the range [0, 2π]. - `color` sets the color of the pie segments. It can be given as a single named color or a vector of the same length as the input data - `strokecolor = :black` sets the color of the outline around the segments. - `strokewidth = 1` sets the width of the outline around the segments. @@ -29,7 +29,7 @@ Set the axis properties `autolimitaspect = 1` or `aspect = DataAspect()` to ensu ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + data = [36, 12, 68, 5, 42, 27] colors = [:yellow, :orange, :red, :blue, :purple, :green] @@ -40,7 +40,7 @@ f, ax, plt = pie(data, inner_radius = 2, strokecolor = :white, strokewidth = 5, - axis = (autolimitaspect = 1, ) + axis = (autolimitaspect = 1, ) ) f @@ -52,7 +52,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f, ax, plt = pie([π/2, 2π/3, π/4], normalize=false, diff --git a/docs/examples/plotting_functions/poly.md b/docs/examples/plotting_functions/poly.md index 634015c6833..2f946fcd1a3 100644 --- a/docs/examples/plotting_functions/poly.md +++ b/docs/examples/plotting_functions/poly.md @@ -9,7 +9,7 @@ using CairoMakie CairoMakie.activate!() # hide using Makie.GeometryBasics -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -25,7 +25,7 @@ f using CairoMakie CairoMakie.activate!() # hide using Makie.GeometryBasics -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -47,7 +47,7 @@ f using CairoMakie CairoMakie.activate!() # hide using Makie.GeometryBasics -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -68,7 +68,7 @@ f using CairoMakie CairoMakie.activate!() # hide using Makie.GeometryBasics -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1], aspect = DataAspect()) @@ -85,7 +85,7 @@ f using CairoMakie CairoMakie.activate!() # hide using Makie.GeometryBasics -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]; backgroundcolor = :gray15) diff --git a/docs/examples/plotting_functions/qqplot.md b/docs/examples/plotting_functions/qqplot.md index 6d605b059c7..402fe35ea37 100644 --- a/docs/examples/plotting_functions/qqplot.md +++ b/docs/examples/plotting_functions/qqplot.md @@ -11,7 +11,7 @@ Test if `xs` and `ys` follow the same distribution. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = randn(100) ys = randn(100) @@ -26,7 +26,7 @@ Test if `ys` is normally distributed. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + ys = 2 .* randn(100) .+ 3 diff --git a/docs/examples/plotting_functions/rainclouds.md b/docs/examples/plotting_functions/rainclouds.md index 15135a87e88..37d6efdade9 100644 --- a/docs/examples/plotting_functions/rainclouds.md +++ b/docs/examples/plotting_functions/rainclouds.md @@ -9,7 +9,7 @@ three together can make an appealing and informative visual, particularly for la ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random using Makie: rand_localized @@ -68,7 +68,7 @@ category_labels, data_array = mockup_categories_and_data_array(3) colors = Makie.wong_colors() rainclouds(category_labels, data_array; xlabel = "Categories of Distributions", ylabel = "Samples", title = "My Title", - plot_boxplots = false, cloud_width=0.5, clouds=hist, hist_bins=50, + plot_boxplots = false, cloud_width=0.5, clouds=hist, hist_bins=50, color = colors[indexin(category_labels, unique(category_labels))]) ``` \end{examplefigure} diff --git a/docs/examples/plotting_functions/rangebars.md b/docs/examples/plotting_functions/rangebars.md index 49ad829ce8e..42580593d28 100644 --- a/docs/examples/plotting_functions/rangebars.md +++ b/docs/examples/plotting_functions/rangebars.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -27,7 +27,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) diff --git a/docs/examples/plotting_functions/scatter.md b/docs/examples/plotting_functions/scatter.md index e333598070d..2009d2c5ebf 100644 --- a/docs/examples/plotting_functions/scatter.md +++ b/docs/examples/plotting_functions/scatter.md @@ -12,7 +12,7 @@ Scatters can be constructed by passing a list of x and y coordinates. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = range(0, 10, length = 30) ys = 0.5 .* sin.(xs) @@ -32,7 +32,7 @@ If you pass a vector of numbers for `color`, the attribute `colorrange` which is ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = range(0, 10, length = 30) ys = 0.5 .* sin.(xs) @@ -61,7 +61,7 @@ Here is an example plot showing different shapes that are accessible by `Symbol` ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + markers_labels = [ (:circle, ":circle"), @@ -118,7 +118,7 @@ For `Char` markers, `markersize` is equivalent to the font size when displaying ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f, ax, sc = scatter(1, 1, marker = 'A', markersize = 50) text!(2, 1, text = "A", textsize = 50, align = (:center, :center)) @@ -137,7 +137,7 @@ You can see that only the special markers `Circle` and `Rect` match the line wid ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f, ax, l = lines([0, 1], [1, 1], linewidth = 50, color = :gray80) for (marker, x) in zip(['X', 'x', :circle, :rect, :utriangle, Circle, Rect], range(0.1, 0.9, length = 7)) @@ -156,7 +156,7 @@ Here, we construct a hexagon polygon with radius `1`, which we can then use to t ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + hexagon = Makie.Polygon([Point2f(cos(a), sin(a)) for a in range(1/6 * pi, 13/6 * pi, length = 7)]) @@ -189,7 +189,7 @@ Here is an example with a simple arrow that is centered on its tip, built from p ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + arrow_path = BezierPath([ MoveTo(Point(0, 0)), @@ -221,7 +221,7 @@ For example, a circle with a square cut out can be made by one `EllipticalArc` t ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + circle_with_hole = BezierPath([ MoveTo(Point(1, 0)), @@ -255,7 +255,7 @@ Here's an example with an svg string that contains the bat symbol: ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + batsymbol_string = "M96.84 141.998c-4.947-23.457-20.359-32.211-25.862-13.887-11.822-22.963-37.961-16.135-22.041 6.289-3.005-1.295-5.872-2.682-8.538-4.191-8.646-5.318-15.259-11.314-19.774-17.586-3.237-5.07-4.994-10.541-4.994-16.229 0-19.774 21.115-36.758 50.861-43.694.446-.078.909-.154 1.372-.231-22.657 30.039 9.386 50.985 15.258 24.645l2.528-24.367 5.086 6.52H103.205l5.07-6.52 2.543 24.367c5.842 26.278 37.746 5.502 15.414-24.429 29.777 6.951 50.891 23.936 50.891 43.709 0 15.136-12.406 28.651-31.609 37.267 14.842-21.822-10.867-28.266-22.549-5.549-5.502-18.325-21.147-9.341-26.125 13.886z" @@ -277,7 +277,7 @@ In this example, a small circle is cut out of a larger circle: ```julia using CairoMakie, GeometryBasics CairoMakie.activate!() # hide -Makie.inline!(true) # hide + p_big = decompose(Point2f, Circle(Point2f(0), 1)) p_small = decompose(Point2f, Circle(Point2f(0), 0.5)) @@ -293,7 +293,7 @@ Markers can be rotated using the `rotations` attribute, which also allows to pas ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + points = [Point2f(x, y) for y in 1:10 for x in 1:10] rotations = range(0, 2pi, length = length(points)) @@ -310,7 +310,7 @@ You can scale x and y dimension of markers separately by passing a `Vec`. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() ax = Axis(f[1, 1]) @@ -338,7 +338,7 @@ By default marker sizes are given in pixel units. You can change this by adjusti ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() ax = Axis(f[1, 1]) @@ -362,7 +362,7 @@ f using CairoMakie using DelimitedFiles CairoMakie.activate!() # hide -Makie.inline!(true) # hide + a = readdlm(assetpath("airportlocations.csv")) diff --git a/docs/examples/plotting_functions/scatterlines.md b/docs/examples/plotting_functions/scatterlines.md index c6a95fff9d2..9e20662e60c 100644 --- a/docs/examples/plotting_functions/scatterlines.md +++ b/docs/examples/plotting_functions/scatterlines.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) diff --git a/docs/examples/plotting_functions/series.md b/docs/examples/plotting_functions/series.md index 0bf05fc7924..f071508b96e 100644 --- a/docs/examples/plotting_functions/series.md +++ b/docs/examples/plotting_functions/series.md @@ -10,7 +10,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + data = cumsum(randn(4, 101), dims = 2) diff --git a/docs/examples/plotting_functions/spy.md b/docs/examples/plotting_functions/spy.md index 255058d3b4c..00311dfb5bf 100644 --- a/docs/examples/plotting_functions/spy.md +++ b/docs/examples/plotting_functions/spy.md @@ -9,7 +9,7 @@ ```julia using CairoMakie, SparseArrays CairoMakie.activate!() # hide -Makie.inline!(true) # hide + N = 100 # dimension of the sparse matrix p = 0.1 # independent probability that an entry is zero diff --git a/docs/examples/plotting_functions/stairs.md b/docs/examples/plotting_functions/stairs.md index f69e928df03..e2192912544 100644 --- a/docs/examples/plotting_functions/stairs.md +++ b/docs/examples/plotting_functions/stairs.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() diff --git a/docs/examples/plotting_functions/stem.md b/docs/examples/plotting_functions/stem.md index 991f83a5cf4..e86e477c547 100644 --- a/docs/examples/plotting_functions/stem.md +++ b/docs/examples/plotting_functions/stem.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -25,7 +25,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -46,7 +46,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() Axis(f[1, 1]) @@ -66,7 +66,7 @@ f ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() diff --git a/docs/examples/plotting_functions/streamplot.md b/docs/examples/plotting_functions/streamplot.md index 920e09c547c..3bd5a8734d9 100644 --- a/docs/examples/plotting_functions/streamplot.md +++ b/docs/examples/plotting_functions/streamplot.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + struct FitzhughNagumo{T} ϵ::T diff --git a/docs/examples/plotting_functions/surface.md b/docs/examples/plotting_functions/surface.md index d30ede0a997..ffaa7f07926 100644 --- a/docs/examples/plotting_functions/surface.md +++ b/docs/examples/plotting_functions/surface.md @@ -8,7 +8,7 @@ ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + xs = LinRange(0, 10, 100) ys = LinRange(0, 15, 100) @@ -23,7 +23,7 @@ surface(xs, ys, zs, axis=(type=Axis3,)) using GLMakie using DelimitedFiles GLMakie.activate!() # hide -Makie.inline!(true) # hide + volcano = readdlm(Makie.assetpath("volcano.csv"), ',', Float64) @@ -40,7 +40,7 @@ using SparseArrays using LinearAlgebra using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + # This example was provided by Moritz Schauer (@mschauer). diff --git a/docs/examples/plotting_functions/text.md b/docs/examples/plotting_functions/text.md index b90a6b17e8c..774a9ca98c1 100644 --- a/docs/examples/plotting_functions/text.md +++ b/docs/examples/plotting_functions/text.md @@ -15,7 +15,7 @@ You can either plot one string with one position, or a vector of strings with a ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -47,7 +47,7 @@ This means that the boundingbox of the text in data coordinates will include eve ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() LScene(f[1, 1]) @@ -74,7 +74,7 @@ Text can be aligned with the horizontal alignments `:left`, `:center`, `:right` ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + aligns = [(h, v) for v in [:bottom, :baseline, :center, :top] for h in [:left, :center, :right]] @@ -96,7 +96,7 @@ You can override this with the `justification` attribute. ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + scene = Scene(camera = campixel!, resolution = (800, 800)) @@ -107,7 +107,7 @@ symbols = (:left, :center, :right) for ((justification, halign), point) in zip(Iterators.product(symbols, symbols), points) - t = text!(scene, + t = text!(scene, point, text = "a\nshort\nparagraph", color = (:black, 0.5), @@ -142,7 +142,7 @@ You can specify the end of the barplots in data coordinates, and then offset the ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() @@ -169,7 +169,7 @@ Makie can render LaTeX strings from the LaTeXStrings.jl package using [MathTeXEn ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + lines(0.5..20, x -> sin(x) / sqrt(x), color = :black) text!(7, 0.38, text = L"\frac{\sin(x)}{\sqrt{x}}", color = :black) @@ -184,7 +184,7 @@ You can also pass L-strings to many objects that use text, for example as labels ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + f = Figure() ax = Axis(f[1, 1]) diff --git a/docs/examples/plotting_functions/tricontourf.md b/docs/examples/plotting_functions/tricontourf.md index f9f68291260..3adfadd50df 100644 --- a/docs/examples/plotting_functions/tricontourf.md +++ b/docs/examples/plotting_functions/tricontourf.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random Random.seed!(1234) @@ -27,7 +27,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random Random.seed!(1234) @@ -48,7 +48,7 @@ f ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random Random.seed!(123) @@ -84,7 +84,7 @@ to `:relative` and specify the levels from 0 to 1, relative to the current minim ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + using Random Random.seed!(1234) @@ -97,4 +97,4 @@ scatter!(x, y, color = z, strokewidth = 1, strokecolor = :black) Colorbar(f[1, 2], tr) f ``` -\end{examplefigure} \ No newline at end of file +\end{examplefigure} diff --git a/docs/examples/plotting_functions/violin.md b/docs/examples/plotting_functions/violin.md index 6e02328083f..cea6a1db0b0 100644 --- a/docs/examples/plotting_functions/violin.md +++ b/docs/examples/plotting_functions/violin.md @@ -8,7 +8,7 @@ ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = rand(1:3, 1000) ys = randn(1000) @@ -21,7 +21,7 @@ violin(xs, ys) ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + xs = rand(1:3, 1000) ys = map(xs) do x @@ -36,7 +36,7 @@ violin(xs, ys, datalimits = extrema) ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + N = 1000 xs = rand(1:3, N) @@ -55,7 +55,7 @@ violin(xs, ys, dodge = dodge, side = side, color = color) ```julia using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + N = 1000 xs = rand(1:3, N) @@ -78,7 +78,7 @@ violin(xs, ys, side = side, color = color) ```julia using CairoMakie, Distributions CairoMakie.activate!() # hide -Makie.inline!(true) # hide + N = 100_000 x = rand(1:3, N) @@ -93,4 +93,4 @@ violin(fig[1,2], x, y, weights = w) fig ``` -\end{examplefigure} \ No newline at end of file +\end{examplefigure} diff --git a/docs/examples/plotting_functions/volumeslices.md b/docs/examples/plotting_functions/volumeslices.md index 391ea775a97..0d53a0b05de 100644 --- a/docs/examples/plotting_functions/volumeslices.md +++ b/docs/examples/plotting_functions/volumeslices.md @@ -8,7 +8,7 @@ ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + fig = Figure() ax = LScene(fig[1, 1], show_axis=false) diff --git a/docs/examples/plotting_functions/wireframe.md b/docs/examples/plotting_functions/wireframe.md index cafa93bbdb1..0b9b6d04b3c 100644 --- a/docs/examples/plotting_functions/wireframe.md +++ b/docs/examples/plotting_functions/wireframe.md @@ -8,7 +8,7 @@ ```julia using GLMakie GLMakie.activate!() # hide -Makie.inline!(true) # hide + x, y = collect(-8:0.5:8), collect(-8:0.5:8) z = [sinc(√(X^2 + Y^2) / π) for X ∈ x, Y ∈ y] diff --git a/docs/tutorials/basic-tutorial.md b/docs/tutorials/basic-tutorial.md index 036fb44f17c..8d057b48d0c 100644 --- a/docs/tutorials/basic-tutorial.md +++ b/docs/tutorials/basic-tutorial.md @@ -28,7 +28,7 @@ First, we import CairoMakie. This makes all the exported symbols from `Makie.jl` ```julia:setup using CairoMakie CairoMakie.activate!() # hide -Makie.inline!(true) # hide + nothing # hide ``` From 7110570730fc23f170557d097849635799c1cb29 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 29 Sep 2022 16:31:09 +0200 Subject: [PATCH 27/56] fix GLMakie screen deregistering --- GLMakie/src/display.jl | 2 +- GLMakie/test/unit_tests.jl | 4 ---- src/display.jl | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/GLMakie/src/display.jl b/GLMakie/src/display.jl index 215cb9ad61f..a3502819d5a 100644 --- a/GLMakie/src/display.jl +++ b/GLMakie/src/display.jl @@ -1,5 +1,4 @@ function Base.display(screen::Screen, scene::Scene; connect=true) - Makie.push_screen!(scene, screen) empty!(screen) resize!(screen, size(scene)...) # So, the GLFW window events are not guarantee to fire @@ -9,6 +8,7 @@ function Base.display(screen::Screen, scene::Scene; connect=true) window_open[] = open end connect && connect_screen(scene, screen) + Makie.push_screen!(scene, screen) pollevents(screen) insertplots!(screen, scene) pollevents(screen) diff --git a/GLMakie/test/unit_tests.jl b/GLMakie/test/unit_tests.jl index 4914f0065c2..c78c50228e6 100644 --- a/GLMakie/test/unit_tests.jl +++ b/GLMakie/test/unit_tests.jl @@ -6,10 +6,6 @@ function project_sp(scene, point) return point_px .+ offset end -GLMakie.closeall(GLMakie.GLFW_WINDOWS) -GLMakie.closeall(GLMakie.SINGLETON_SCREEN) -GLMakie.closeall(GLMakie.SINGLETON_SCREEN_NO_RENDERLOOP) - @testset "unit tests" begin @testset "Window handling" begin # Without previous windows/figures everything should be empty/unassigned diff --git a/src/display.jl b/src/display.jl index 9765f2f70e5..17ed472ac6a 100644 --- a/src/display.jl +++ b/src/display.jl @@ -28,7 +28,7 @@ function push_screen!(scene::Scene, display::AbstractDisplay) # so that's when we can remove the display if !is_open filter!(x-> x !== display, scene.current_screens) - deregister !== nothing && off(deregister) + !isnothing(deregister) && off(deregister) end return Consume(false) end From 5cb0e8e661721a079aba8c8303e9518d945fc2aa Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 29 Sep 2022 19:11:36 +0200 Subject: [PATCH 28/56] fix offset in Stepper for CairoMakie --- CairoMakie/src/infrastructure.jl | 3 ++- CairoMakie/src/screen.jl | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index 49cd685ffc4..30fbc00f2a0 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -19,6 +19,7 @@ # The main entry point into the drawing pipeline function cairo_draw(screen::Screen, scene::Scene) + Cairo.save(screen.context) draw_background(screen, scene) allplots = get_all_plots(scene) @@ -57,7 +58,7 @@ function cairo_draw(screen::Screen, scene::Scene) end Cairo.restore(screen.context) end - + Cairo.restore(screen.context) return end diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 810e45fcba1..be09c32b9f8 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -135,14 +135,15 @@ function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::Union # the surface size is the scene size scaled by the device scaling factor w, h = round.(Int, size(scene) .* device_scaling_factor) surface = surface_from_output_type(typ, io_or_path, w, h) - return Screen(scene, surface; screen_attributes...) + return Screen(scene, surface; device_scaling_factor=device_scaling_factor, screen_attributes...) end -function Screen(scene::Scene, image::Matrix{<: Colorant}; screen_attributes...) - img = Matrix{ARGB32}(undef, reverse(size(image))...) +function Screen(scene::Scene, image::Matrix{<: Colorant}; device_scaling_factor=1.0, screen_attributes...) + w, h = round.(Int, size(scene) .* device_scaling_factor) + img = Matrix{ARGB32}(undef, h, w) # create an image surface to draw onto the image surface = Cairo.CairoImageSurface(img) - screen = Screen(scene, surface; screen_attributes...) + screen = Screen(scene, surface; device_scaling_factor=device_scaling_factor, screen_attributes...) screen.surface_image = img return screen end @@ -176,8 +177,8 @@ function Makie.colorbuffer(screen::Screen) img = Matrix{ARGB32}(undef, w, h) # create an image surface to draw onto the image surf = Cairo.CairoImageSurface(img) - screen = Screen(scene, surf; device_scaling_factor=screen.device_scaling_factor, antialias=screen.antialias) - return colorbuffer(screen) + s = Screen(scene, surf; device_scaling_factor=screen.device_scaling_factor, antialias=screen.antialias) + return colorbuffer(s) end function Makie.colorbuffer(screen::Screen{IMAGE}) From 543be4cca350e1098577521a948a1401c1a864ae Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 30 Sep 2022 11:32:49 +0200 Subject: [PATCH 29/56] fix benchmarks + precompiles --- .github/workflows/compilation-benchmark.yaml | 2 +- CairoMakie/src/CairoMakie.jl | 2 +- CairoMakie/src/precompiles.jl | 3 +-- CairoMakie/src/screen.jl | 13 ++----------- GLMakie/src/GLMakie.jl | 2 +- GLMakie/src/precompiles.jl | 2 +- WGLMakie/src/WGLMakie.jl | 2 +- metrics/ttfp/benchmark-ttfp.jl | 15 +++++++++++++-- src/Makie.jl | 2 +- 9 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.github/workflows/compilation-benchmark.yaml b/.github/workflows/compilation-benchmark.yaml index ca485e9b775..a418490ffba 100644 --- a/.github/workflows/compilation-benchmark.yaml +++ b/.github/workflows/compilation-benchmark.yaml @@ -25,7 +25,7 @@ jobs: run: sudo apt-get update && sudo apt-get install -y xorg-dev mesa-utils xvfb libgl1 freeglut3-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev - uses: julia-actions/setup-julia@v1 with: - version: nightly + version: '1' arch: x64 - uses: julia-actions/cache@v1 - name: Benchmark diff --git a/CairoMakie/src/CairoMakie.jl b/CairoMakie/src/CairoMakie.jl index b670147c1c2..b5995fe1f33 100644 --- a/CairoMakie/src/CairoMakie.jl +++ b/CairoMakie/src/CairoMakie.jl @@ -34,6 +34,6 @@ function __init__() activate!() end -# include("precompiles.jl") +include("precompiles.jl") end diff --git a/CairoMakie/src/precompiles.jl b/CairoMakie/src/precompiles.jl index e530dcb09e4..dea7e5c54bd 100644 --- a/CairoMakie/src/precompiles.jl +++ b/CairoMakie/src/precompiles.jl @@ -3,8 +3,7 @@ using SnoopPrecompile macro compile(block) return quote figlike = $(esc(block)) - screen = display(Makie.get_scene(figlike)) - Makie.colorbuffer(screen) + Makie.colorbuffer(figlike) end end diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index be09c32b9f8..82fd746643c 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -111,7 +111,8 @@ function surface_from_output_type(type::RenderType, io, w, h) elseif type === EPS return Cairo.CairoEPSSurface(io, w, h) elseif type === IMAGE - return Cairo.CairoARGBSurface(w, h) + img = Matrix{ARGB32}(undef, w, h) + return Cairo.CairoImageSurface(img) else error("No available Cairo surface for mode $type") end @@ -138,16 +139,6 @@ function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::Union return Screen(scene, surface; device_scaling_factor=device_scaling_factor, screen_attributes...) end -function Screen(scene::Scene, image::Matrix{<: Colorant}; device_scaling_factor=1.0, screen_attributes...) - w, h = round.(Int, size(scene) .* device_scaling_factor) - img = Matrix{ARGB32}(undef, h, w) - # create an image surface to draw onto the image - surface = Cairo.CairoImageSurface(img) - screen = Screen(scene, surface; device_scaling_factor=device_scaling_factor, screen_attributes...) - screen.surface_image = img - return screen -end - function Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_attributes...) # create an image surface to draw onto the image img = Matrix{ARGB32}(undef, size(scene)...) diff --git a/GLMakie/src/GLMakie.jl b/GLMakie/src/GLMakie.jl index e75537b7c2d..03e2c38f9e8 100644 --- a/GLMakie/src/GLMakie.jl +++ b/GLMakie/src/GLMakie.jl @@ -51,6 +51,6 @@ function __init__() activate!() end -# include("precompiles.jl") +include("precompiles.jl") end diff --git a/GLMakie/src/precompiles.jl b/GLMakie/src/precompiles.jl index 62b93a8236c..3a27e585a24 100644 --- a/GLMakie/src/precompiles.jl +++ b/GLMakie/src/precompiles.jl @@ -5,7 +5,7 @@ macro compile(block) let figlike = $(esc(block)) screen = Screen(visible=false) - display(screen, Makie.get_scene(figlike)) + display(screen, figlike) Makie.colorbuffer(screen) close(screen) end diff --git a/WGLMakie/src/WGLMakie.jl b/WGLMakie/src/WGLMakie.jl index 63d2748fc59..4f1711cbf9a 100644 --- a/WGLMakie/src/WGLMakie.jl +++ b/WGLMakie/src/WGLMakie.jl @@ -80,6 +80,6 @@ for name in names(Makie, all=true) end end -# include("precompiles.jl") +include("precompiles.jl") end # module diff --git a/metrics/ttfp/benchmark-ttfp.jl b/metrics/ttfp/benchmark-ttfp.jl index 8f4fb78cb90..1ce72fdc0cc 100644 --- a/metrics/ttfp/benchmark-ttfp.jl +++ b/metrics/ttfp/benchmark-ttfp.jl @@ -8,6 +8,17 @@ macro ctime(x) end t_using = @ctime @eval using $Package +function get_colorbuffer(fig) + # We need to handle old versions of Makie + if isdefined(Makie, :CURRENT_BACKEND) # new version after display refactor + return Makie.colorbuffer(fig) # easy :) + else + Makie.inline!(false) + screen = display(fig; visible=false) + return Makie.colorbuffer(screen) + end +end + if Package == :WGLMakie using ElectronDisplay ElectronDisplay.CONFIG.showable = showable @@ -16,7 +27,7 @@ if Package == :WGLMakie end create_time = @ctime fig = scatter(1:4; color=1:4, colormap=:turbo, markersize=20, visible=true) -display_time = @ctime Makie.colorbuffer(display(fig)) +display_time = @ctime get_colorbuffer(fig) using BenchmarkTools using BenchmarkTools.JSON @@ -30,7 +41,7 @@ old = isfile(result) ? JSON.parse(read(result, String)) : [[], [], [], [], []] push!.(old[1:3], [t_using, create_time, display_time]) b1 = @benchmark fig = scatter(1:4; color=1:4, colormap=:turbo, markersize=20, visible=true) -b2 = @benchmark Makie.colorbuffer(display(fig)) +b2 = @benchmark get_colorbuffer(fig) using Statistics diff --git a/src/Makie.jl b/src/Makie.jl index 9f61a5d6170..902af9590bf 100644 --- a/src/Makie.jl +++ b/src/Makie.jl @@ -331,6 +331,6 @@ export heatmap!, image!, lines!, linesegments!, mesh!, meshscatter!, scatter!, s export PointLight, EnvironmentLight, AmbientLight, SSAO -# include("precompiles.jl") +include("precompiles.jl") end # module From 0f2536698ac2d8b0f9597ee8ee45cdaa81c13951 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 30 Sep 2022 13:36:37 +0200 Subject: [PATCH 30/56] implement switching of mimes --- .github/workflows/Docs.yml | 2 +- CairoMakie/src/display.jl | 8 ++--- CairoMakie/src/screen.jl | 5 ++- GLMakie/src/display.jl | 2 ++ WGLMakie/src/display.jl | 2 +- src/display.jl | 68 +++++++++++++++++++++++++++++--------- src/theming.jl | 27 ++++++++++++++- test/display.jl | 49 +++++++++++++++++++++++++++ test/runtests.jl | 1 + 9 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 test/display.jl diff --git a/.github/workflows/Docs.yml b/.github/workflows/Docs.yml index 9b96d7a8ca2..9d6603356bd 100644 --- a/.github/workflows/Docs.yml +++ b/.github/workflows/Docs.yml @@ -39,7 +39,7 @@ jobs: - name: Install Julia uses: julia-actions/setup-julia@v1 with: - version: 1.6 + version: '1' - name: Build and deploy docs env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CairoMakie/src/display.jl b/CairoMakie/src/display.jl index 3c9936bd8d3..35f898180a8 100644 --- a/CairoMakie/src/display.jl +++ b/CairoMakie/src/display.jl @@ -99,7 +99,7 @@ function Makie.backend_show(screen::Screen{IMAGE}, io::IO, ::MIME"image/png", sc return screen end -Makie.backend_showable(::Type{Screen}, ::MIME"image/svg+xml", scene::Scene) = true -Makie.backend_showable(::Type{Screen}, ::MIME"application/pdf", scene::Scene) = true -Makie.backend_showable(::Type{Screen}, ::MIME"application/postscript", scene::Scene) = true -Makie.backend_showable(::Type{Screen}, ::MIME"image/png", scene::Scene) = true +Makie.backend_showable(::Type{Screen}, ::MIME"image/svg+xml") = true +Makie.backend_showable(::Type{Screen}, ::MIME"application/pdf") = true +Makie.backend_showable(::Type{Screen}, ::MIME"application/postscript") = true +Makie.backend_showable(::Type{Screen}, ::MIME"image/png") = true diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 82fd746643c..bdbc2dbe92a 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -17,7 +17,10 @@ const SCREEN_CONFIG = Ref(( antialias = Cairo.ANTIALIAS_BEST )) -function activate!(; screen_attributes...) +function activate!(; type="png", screen_attributes...) + + Makie.set_preferred_mime!(to_mime(convert(RenderType, type))) + Makie.set_screen_config!(SCREEN_CONFIG, screen_attributes) Makie.set_active_backend!(CairoMakie) return diff --git a/GLMakie/src/display.jl b/GLMakie/src/display.jl index a3502819d5a..4c4f4246de6 100644 --- a/GLMakie/src/display.jl +++ b/GLMakie/src/display.jl @@ -14,3 +14,5 @@ function Base.display(screen::Screen, scene::Scene; connect=true) pollevents(screen) return screen end + +Makie.backend_showable(::Type{Screen}, ::Union{MIME"image/jpeg", MIME"image/png"}) = true diff --git a/WGLMakie/src/display.jl b/WGLMakie/src/display.jl index 7e8e209c26a..65e5a01b6c2 100644 --- a/WGLMakie/src/display.jl +++ b/WGLMakie/src/display.jl @@ -37,7 +37,7 @@ for M in WEB_MIMES end end -function Makie.backend_showable(::Type{Screen}, ::T, scene::Scene) where {T<:MIME} +function Makie.backend_showable(::Type{Screen}, ::T) where {T<:MIME} return T in WEB_MIMES end diff --git a/src/display.jl b/src/display.jl index 17ed472ac6a..faf68ec5ee8 100644 --- a/src/display.jl +++ b/src/display.jl @@ -10,7 +10,7 @@ Current backend const CURRENT_BACKEND = Ref{Union{Missing, Module}}(missing) current_backend() = CURRENT_BACKEND[] -function set_active_backend!(backend::Module) +function set_active_backend!(backend::Union{Missing, Module}) CURRENT_BACKEND[] = backend return end @@ -86,25 +86,61 @@ function Base.display(screen::MakieScreen, figlike::FigureLike; display_attribut return screen end -function Base.showable(mime::MIME{M}, scene::Scene) where M - backend_showable(current_backend().Screen, mime, scene) -end +const PREFERRED_MIME = Base.RefValue{Union{Automatic, String}}(automatic) -# ambig -function Base.showable(mime::MIME"application/json", scene::Scene) - backend_showable(current_backend().Screen, mime, scene) -end -function Base.showable(mime::MIME{M}, fig::FigureLike) where M - backend_showable(current_backend().Screen, mime, get_scene(fig)) -end -# ambig -function Base.showable(mime::MIME"application/json", fig::FigureLike) - backend_showable(current_backend().Screen, mime, get_scene(fig)) +""" + set_preferred_mime!(mime::Union{String, Symbol, MIME, Automatic}=automatic) + +The default is automatic, which lets the display system figure out the best mime. +If set to any other valid mime, will result in `showable(any_other_mime, figurelike)` to return false and only return true for `showable(preferred_mime, figurelike)`. +Depending on the display system used, this may result in nothing getting displayed. +""" +function set_preferred_mime!(mime::Union{String, Symbol, MIME, Automatic}=automatic) + if mime isa Automatic + PREFERRED_MIME[] = mime + return + end + # Mimes supported accross Makie backends + # TODO, make this dynamic and backends can register their mimes? + supported_mimes = Set([ + "text/html", + "application/vnd.webio.application+html", + "application/prs.juno.plotpane+html", + "juliavscode/html", + "image/svg+xml", + "application/pdf", + "application/postscript", + "image/png", + "image/jpeg"]) + + mime_str = string(mime) + + if mime_str in supported_mimes + PREFERRED_MIME[] = mime_str + else + error("Mime not supported: $(mime_str). Supported mimes: $(supported_mimes)") + end + return end -function backend_showable(::Type{Screen}, ::Mime, ::Scene) where {Screen, Mime <: MIME} - hasmethod(backend_show, Tuple{<: Screen, IO, Mime, Scene}) +function _backend_showable(mime::MIME{SYM}) where SYM + Backend = current_backend() + if ismissing(Backend) + return Symbol("text/plain") == SYM + end + backend_support = backend_showable(Backend.Screen, mime) + if PREFERRED_MIME[] isa Automatic + return backend_support + else + return backend_support && string(SYM) == PREFERRED_MIME[] + end end +Base.showable(mime::MIME, fig::FigureLike) = _backend_showable(mime) + +# need to define this to resolve ambiguoity issue +Base.showable(mime::MIME"application/json", fig::FigureLike) = _backend_showable(mime) + +backend_showable(@nospecialize(screen), @nospecialize(mime)) = false # fallback show when no backend is selected function backend_show(backend, io::IO, ::MIME"text/plain", scene::Scene) diff --git a/src/theming.jl b/src/theming.jl index 27fc6bec109..18e1f94ceeb 100644 --- a/src/theming.jl +++ b/src/theming.jl @@ -91,7 +91,32 @@ const minimal_default = Attributes( ), ambient = RGBf(0.55, 0.55, 0.55), lightposition = :eyeposition, - inspectable = true + inspectable = true, + + CairoMakie = Attributes( + type = "png", + px_per_unit = 1.0, + pt_per_unit = 0.75, + antialias = :best + ), + GLMakie = Attributes( + renderloop = automatic, + vsync = false, + framerate = 30.0, + float = false, + pause_rendering = false, + focus_on_show = false, + decorated = true, + title = "Makie", + ), + WGLMakie = Attributes( + framerate = 30.0 + ), + RPRMakie = Attributes( + iterations = 200, + resource = automatic, + plugin = automatic + ) ) const _current_default_theme = deepcopy(minimal_default) diff --git a/test/display.jl b/test/display.jl new file mode 100644 index 00000000000..06a5fb0aa76 --- /dev/null +++ b/test/display.jl @@ -0,0 +1,49 @@ +module TestBackend + using Makie + struct Screen <: MakieScreen end + Makie.backend_showable(::Type{Screen}, ::MIME"text/html") = true + Makie.backend_showable(::Type{Screen}, ::MIME"image/png") = true +end + +@testset "set_preferred_mime 'constructor'" begin + @test_throws ErrorException Makie.set_preferred_mime!("text/htmle") + @test nothing == Makie.set_preferred_mime!(MIME"text/html"()) + @test nothing == Makie.set_preferred_mime!(Symbol("text/html")) + @test nothing == Makie.set_preferred_mime!() +end + +@testset "preferred mime without backend" begin + # Only text/plain + Makie.set_active_backend!(missing) + Makie.set_preferred_mime!("text/html") + s = Scene(); + @test Base.showable("text/plain", s) + @test !Base.showable("text/html", s) +end + +@testset "with Backend + preferred mime" begin + Makie.set_active_backend!(TestBackend) + Makie.set_preferred_mime!("text/html") + @test Base.showable("text/html", s) + @test !Base.showable("image/png", s) +end + +@testset "without preferred mime, backend capabilities should be returned" begin + Makie.set_preferred_mime!() + @test Base.showable("text/html", s) + @test Base.showable("image/png", s) + @test !Base.showable("text/plain", s) + @test !Base.showable("image/jpeg", s) +end + +@testset "only plain for missing backend" begin + Makie.set_active_backend!(missing) + @test Base.showable("text/plain", s) + @test !Base.showable("image/png", s) + @test !Base.showable("text/html", s) +end + +@testset "preferred mime without backend should return false" begin + Makie.set_preferred_mime!("text/html") + @test !Base.showable("text/html", s) +end diff --git a/test/runtests.jl b/test/runtests.jl index fe12cca7f4f..8978535ee43 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -18,6 +18,7 @@ using Makie: volume @test all(hi .>= (8,8,10)) end + include("display.jl") include("scenes.jl") include("conversions.jl") include("quaternions.jl") From bfb8c2877584c9a005bc8f2e657522a71891aad1 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 4 Oct 2022 18:11:33 +0200 Subject: [PATCH 31/56] move display config to theme --- CairoMakie/src/CairoMakie.jl | 2 +- CairoMakie/src/screen.jl | 86 +++++---- GLMakie/assets/shader/fragment_output.frag | 4 +- GLMakie/src/GLMakie.jl | 2 +- GLMakie/src/gl_backend.jl | 1 - GLMakie/src/rendering.jl | 53 ------ GLMakie/src/screen.jl | 212 ++++++++++++++++++--- MakieCore/src/types.jl | 17 +- RPRMakie/src/RPRMakie.jl | 25 ++- RPRMakie/src/scene.jl | 33 ++-- WGLMakie/src/WGLMakie.jl | 10 +- WGLMakie/src/display.jl | 2 +- WGLMakie/src/three_plot.jl | 11 +- docs/documentation/lighting.md | 7 +- docs/makedocs.jl | 3 + docs/tutorials/scenes.md | 1 + src/display.jl | 42 ++-- src/scenes.jl | 2 +- src/theming.jl | 33 +++- 19 files changed, 369 insertions(+), 177 deletions(-) diff --git a/CairoMakie/src/CairoMakie.jl b/CairoMakie/src/CairoMakie.jl index b5995fe1f33..b670147c1c2 100644 --- a/CairoMakie/src/CairoMakie.jl +++ b/CairoMakie/src/CairoMakie.jl @@ -34,6 +34,6 @@ function __init__() activate!() end -include("precompiles.jl") +# include("precompiles.jl") end diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index bdbc2dbe92a..6713967a203 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -1,27 +1,25 @@ @enum RenderType SVG IMAGE PDF EPS -""" - Screen(; - type = IMAGE, - px_per_unit = 1.0, - pt_per_unit = 0.75, - antialias = Cairo.ANTIALIAS_BEST - ) - - -""" -const SCREEN_CONFIG = Ref(( - type = IMAGE, - px_per_unit = 1.0, - pt_per_unit = 0.75, - antialias = Cairo.ANTIALIAS_BEST -)) +struct ScreenConfig + type::RenderType # = IMAGE, + px_per_unit::Float64 # = 1.0, + pt_per_unit::Float64 # = 0.75, + antialias::Symbol # = Cairo.ANTIALIAS_BEST + visible::Bool + start_renderloop::Bool +end -function activate!(; type="png", screen_attributes...) +function device_scaling_factor(sc::ScreenConfig) + if is_vector_backend(sc.type) + return sc.pt_per_unit + else + return sc.px_per_unit + end +end +function activate!(; type="png", screen_config...) Makie.set_preferred_mime!(to_mime(convert(RenderType, type))) - - Makie.set_screen_config!(SCREEN_CONFIG, screen_attributes) + Makie.set_screen_config!(CairoMakie, screen_config) Makie.set_active_backend!(CairoMakie) return end @@ -125,37 +123,49 @@ end # Constructor # ######################################## +function to_cairo_antialias(sym::Symbol) + sym == :best && return Cairo.ANTIALIAS_BEST + sym == :good && return Cairo.ANTIALIAS_GOOD + sym == :subpixel && return Cairo.ANTIALIAS_SUBPIXEL + sym == :none && return Cairo.ANTIALIAS_NONE + error("Wrong antialias setting: $(sym). Allowed: :best, :good, :subpixel, :none") +end +to_cairo_antialias(aa::Int) = aa # Default to ARGB Surface as backing device -# TODO: integrate Gtk into this, so we can have an interactive display """ - Screen(scene::Scene; screen_attributes...) + Screen(scene::Scene; screen_config...) Create a Screen backed by an image surface. """ -Screen(scene::Scene; screen_attributes...) = Screen(scene, nothing, IMAGE; screen_attributes...) +Screen(scene::Scene; screen_config...) = Screen(scene, nothing, IMAGE; screen_config...) -function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::Union{MIME, Symbol, RenderType}; device_scaling_factor=1.0, screen_attributes...) +function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::Union{MIME, Symbol, RenderType}; screen_config...) + config = Makie.merge_screen_config(ScreenConfig, screen_config) # the surface size is the scene size scaled by the device scaling factor - w, h = round.(Int, size(scene) .* device_scaling_factor) + w, h = round.(Int, size(scene) .* device_scaling_factor(config)) surface = surface_from_output_type(typ, io_or_path, w, h) - return Screen(scene, surface; device_scaling_factor=device_scaling_factor, screen_attributes...) + return Screen(scene, surface, config) end -function Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_attributes...) +function Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_config...) + config = Makie.merge_screen_config(ScreenConfig, screen_config) + w, h = round.(Int, size(scene) .* device_scaling_factor(config)) # create an image surface to draw onto the image - img = Matrix{ARGB32}(undef, size(scene)...) + img = Matrix{ARGB32}(undef, w, h) surface = Cairo.CairoImageSurface(img) - return Screen(scene, surface; screen_attributes...) + return Screen(scene, surface, config) end -function Screen(scene::Scene, surface::Cairo.CairoSurface; device_scaling_factor=1.0, antialias=Cairo.ANTIALIAS_BEST, visible=true, start_renderloop=false) +function Screen(scene::Scene, surface::Cairo.CairoSurface, config::ScreenConfig) # the surface size is the scene size scaled by the device scaling factor - surface_set_device_scale(surface, device_scaling_factor) + dsf = device_scaling_factor(config) + surface_set_device_scale(surface, dsf) ctx = Cairo.CairoContext(surface) - Cairo.set_antialias(ctx, antialias) + aa = to_cairo_antialias(config.antialias) + Cairo.set_antialias(ctx, aa) set_miter_limit(ctx, 2.0) - return Screen{get_render_type(surface)}(scene, surface, ctx, device_scaling_factor, antialias, visible) + return Screen{get_render_type(surface)}(scene, surface, ctx, dsf, aa, config.visible) end ######################################## @@ -176,9 +186,17 @@ function Makie.colorbuffer(screen::Screen) end function Makie.colorbuffer(screen::Screen{IMAGE}) + ctx = screen.context + Cairo.save(ctx) + Cairo.set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0) + Cairo.set_operator(ctx, Cairo.OPERATOR_CLEAR) + Cairo.rectangle(ctx, 0, 0, size(screen)...) + Cairo.paint_with_alpha(ctx, 1.0) + Cairo.restore(ctx) cairo_draw(screen, screen.scene) - return permutedims(screen.surface.data) + return permutedims(RGBf.(screen.surface.data)) end is_vector_backend(ctx::Cairo.CairoContext) = is_vector_backend(ctx.surface) -is_vector_backend(surf::Cairo.CairoSurface) = get_render_type(surf) in (PDF, EPS, SVG) +is_vector_backend(surf::Cairo.CairoSurface) = is_vector_backend(get_render_type(surf)) +is_vector_backend(rt::RenderType) = rt in (PDF, EPS, SVG) diff --git a/GLMakie/assets/shader/fragment_output.frag b/GLMakie/assets/shader/fragment_output.frag index e17ea83e115..7fb14e78a9a 100644 --- a/GLMakie/assets/shader/fragment_output.frag +++ b/GLMakie/assets/shader/fragment_output.frag @@ -7,7 +7,7 @@ layout(location=1) out uvec2 fragment_groupid; // // if transparency == true // layout(location=2) out float coverage; -// // if transparency == false && enable_SSAO[] = true +// // if transparency == false && ssao = true // layout(location=2) out vec3 fragment_position; // layout(location=3) out vec3 fragment_normal_occlusion; @@ -31,7 +31,7 @@ void write2framebuffer(vec4 color, uvec2 id){ // fragment_color.rgb = weight * color.rgb; // fragment_color.a = weight; - // // if transparency == false && enable_SSAO[] = true + // // if transparency == false && ssao = true // fragment_color = color; // fragment_position = o_view_pos; // fragment_normal_occlusion.xyz = o_normal; diff --git a/GLMakie/src/GLMakie.jl b/GLMakie/src/GLMakie.jl index 03e2c38f9e8..e75537b7c2d 100644 --- a/GLMakie/src/GLMakie.jl +++ b/GLMakie/src/GLMakie.jl @@ -51,6 +51,6 @@ function __init__() activate!() end -include("precompiles.jl") +# include("precompiles.jl") end diff --git a/GLMakie/src/gl_backend.jl b/GLMakie/src/gl_backend.jl index f74aa49f4da..3557cf6bef7 100644 --- a/GLMakie/src/gl_backend.jl +++ b/GLMakie/src/gl_backend.jl @@ -54,7 +54,6 @@ function get_texture!(atlas) return tex end -# TODO # find a better way to handle this # enable_SSAO and FXAA adjust the rendering pipeline and are currently per screen const enable_SSAO = Ref(false) diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index daf822675d9..ecbbf64e416 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -1,56 +1,3 @@ -# TODO add render_tick event to scene events -function vsynced_renderloop(screen) - while isopen(screen) && !SCREEN_CONFIG[].exit_renderloop - pollevents(screen) # GLFW poll - if SCREEN_CONFIG[].pause_rendering - sleep(0.1) - else - render_frame(screen) - GLFW.SwapBuffers(to_native(screen)) - yield() - end - end -end - -function fps_renderloop(screen::Screen, framerate=SCREEN_CONFIG[].framerate) - time_per_frame = 1.0 / framerate - while isopen(screen) && !SCREEN_CONFIG[].exit_renderloop - t = time_ns() - pollevents(screen) # GLFW poll - if SCREEN_CONFIG[].pause_rendering - sleep(0.1) - else - render_frame(screen) - GLFW.SwapBuffers(to_native(screen)) - t_elapsed = (time_ns() - t) / 1e9 - diff = time_per_frame - t_elapsed - if diff > 0.001 # can't sleep less than 0.001 - sleep(diff) - else # if we don't sleep, we still need to yield explicitely to other tasks - yield() - end - end - end -end - -function renderloop(screen; framerate=SCREEN_CONFIG[].framerate) - isopen(screen) || error("Screen most be open to run renderloop!") - try - if SCREEN_CONFIG[].vsync - GLFW.SwapInterval(1) - vsynced_renderloop(screen) - else - GLFW.SwapInterval(0) - fps_renderloop(screen, framerate) - end - catch e - showerror(stderr, e, catch_backtrace()) - println(stderr) - rethrow(e) - finally - destroy!(screen) - end -end function setup!(screen) glEnable(GL_SCISSOR_TEST) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 2c20f5f97a4..8601f01b48c 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -5,16 +5,50 @@ const ScreenArea = Tuple{ScreenID, Scene} function renderloop end -const SCREEN_CONFIG = Ref(( - renderloop = renderloop, - vsync = false, - framerate = 30.0, - float = false, - pause_rendering = false, - focus_on_show = false, - decorated = true, - title = "Makie", - exit_renderloop = false,)) +""" + ScreenConfig( + # Renderloop + renderloop::Union{Makie.Automatic, Function} = automatic, + pause_renderloop::Bool = false, + vsync::Bool = false, + framerate::Float64 = 30.0, + + # GLFW window attributes + float::Bool = false, + focus_on_show::Bool = false, + decorated::Bool = true, + title::String = "Makie", + fullscreen::Bool = false, + debugging::Bool = false, + monitor::Union{Nothing, GLFW.Monitor} = nothing, + + # Preproccessor + oit::Bool = true, + fxaa::Bool = true, + ssao::Bool = true) + +""" +struct ScreenConfig + # Renderloop + renderloop::Union{Makie.Automatic, Function}# = automatic, + pause_renderloop::Bool + vsync::Bool# = false, + framerate::Float64# = 30.0, + + # GLFW window attributes + float::Bool# = false, + focus_on_show::Bool# = false, + decorated::Bool# = true, + title::String# = "Makie", + fullscreen::Bool# = false, + debugging::Bool# = false, + monitor::Union{Nothing, GLFW.Monitor}# = nothing, + + # Preproccessor + oit::Bool# = true, + fxaa::Bool# = true, + ssao::Bool# = true +end """ activate!(; @@ -31,7 +65,10 @@ Updates the screen configuration, will only go into effect after closing the cur window and opening a new one! """ function activate!(; screen_config...) - Makie.set_screen_config!(SCREEN_CONFIG, screen_config) + if haskey(screen_config, :pause_rendering) + error("pause_rendering got renamed to pause_renderloop.") + end + Makie.set_screen_config!(GLMakie, screen_config) Makie.set_active_backend!(GLMakie) Makie.set_glyph_resolution!(Makie.High) return @@ -43,7 +80,14 @@ mutable struct Screen{GLWindow} <: GLScreen glscreen::GLWindow shader_cache::GLAbstraction.ShaderCache framebuffer::GLFramebuffer + + renderloop::Function + stop_renderloop::Bool + pause_renderloop::Bool + vsync::Bool + framerate::Float64 rendertask::RefValue{Task} + screen2scene::Dict{WeakRef, ScreenID} screens::Vector{ScreenArea} renderlist::Vector{Tuple{ZIndex, ScreenID, RenderObject}} @@ -58,7 +102,14 @@ mutable struct Screen{GLWindow} <: GLScreen glscreen::GLWindow, shader_cache::GLAbstraction.ShaderCache, framebuffer::GLFramebuffer, + + renderloop::Function, + stop_renderloop::Bool, + pause_renderloop::Bool, + vsync::Bool, + framerate::Float64, rendertask::RefValue{Task}, + screen2scene::Dict{WeakRef, ScreenID}, screens::Vector{ScreenArea}, renderlist::Vector{Tuple{ZIndex, ScreenID, RenderObject}}, @@ -68,7 +119,10 @@ mutable struct Screen{GLWindow} <: GLScreen ) where {GLWindow} s = size(framebuffer) return new{GLWindow}( - glscreen, shader_cache, framebuffer, rendertask, screen2scene, + glscreen, shader_cache, framebuffer, + renderloop, stop_renderloop, pause_renderloop, vsync, framerate, + rendertask, + screen2scene, screens, renderlist, postprocessors, cache, cache2plot, Matrix{RGB{N0f8}}(undef, s), Observable(nothing), Observable(true) @@ -389,12 +443,15 @@ end function Screen(; resolution = (10, 10), visible = true, - title = SCREEN_CONFIG[].title, start_renderloop = true, - kw_args... + screen_config... ) + # Screen config is managed by the current active theme, so managed by Makie + config = Makie.merge_screen_config(ScreenConfig, screen_config) + # Somehow this constant isn't wrapped by glfw GLFW_FOCUS_ON_SHOW = 0x0002000C + windowhints = [ (GLFW.SAMPLES, 0), (GLFW.DEPTH_BITS, 0), @@ -408,19 +465,23 @@ function Screen(; (GLFW.STENCIL_BITS, 0), (GLFW.AUX_BUFFERS, 0), - (GLFW_FOCUS_ON_SHOW, SCREEN_CONFIG[].focus_on_show), - (GLFW.DECORATED, SCREEN_CONFIG[].decorated), - (GLFW.FLOATING, SCREEN_CONFIG[].float), + (GLFW_FOCUS_ON_SHOW, config.focus_on_show), + (GLFW.DECORATED, config.decorated), + (GLFW.FLOATING, config.float), # (GLFW.TRANSPARENT_FRAMEBUFFER, true) ] window = try GLFW.Window( - name = title, resolution = resolution, + resolution = resolution, windowhints = windowhints, visible = false, - focus = false, - kw_args... + # from config + name = config.title, + focus = config.focus_on_show, + fullscreen = config.fullscreen, + debugging = config.debugging, + monitor = config.monitor ) catch e @warn(""" @@ -444,17 +505,23 @@ function Screen(; resize_native!(window, resolution...) fb = GLFramebuffer(resolution) - postprocessors = [ - enable_SSAO[] ? ssao_postprocessor(fb, shader_cache) : empty_postprocessor(), - OIT_postprocessor(fb, shader_cache), - enable_FXAA[] ? fxaa_postprocessor(fb, shader_cache) : empty_postprocessor(), + config.ssao ? ssao_postprocessor(fb, shader_cache) : empty_postprocessor(), + config.oit ? OIT_postprocessor(fb, shader_cache) : empty_postprocessor(), + config.fxaa ? fxaa_postprocessor(fb, shader_cache) : empty_postprocessor(), to_screen_postprocessor(fb, shader_cache) ] screen = Screen( window, shader_cache, fb, + + config.renderloop isa Makie.Automatic ? renderloop : config.renderloop, + !start_renderloop, + config.pause_renderloop, + config.vsync, + config.framerate, RefValue{Task}(), + Dict{WeakRef, ScreenID}(), ScreenArea[], Tuple{ZIndex, ScreenID, RenderObject}[], @@ -464,18 +531,56 @@ function Screen(; ) GLFW.SetWindowRefreshCallback(window, window -> refreshwindowcb(window, screen)) + if start_renderloop - screen.rendertask[] = @async((SCREEN_CONFIG[].renderloop)(screen)) + start_renderloop!(screen) end + # display window if visible! if visible GLFW.ShowWindow(window) else GLFW.HideWindow(window) end + return screen end +function renderloop_running(screen::Screen) + return !screen.stop_renderloop && isassigned(screen.rendertask) && !istaskdone(screen.rendertask[]) +end + +function start_renderloop!(screen::Screen) + if renderloop_running(screen) + screen.pause_renderloop = false + return + else + screen.stop_renderloop = false + task = @async screen.renderloop(screen) + yield() + if istaskstarted(task) + screen.rendertask[] = task + elseif istaskfailed(task) + fetch(task) + else + error("What's up with task $(task)") + end + end +end + +function pause_renderloop!(screen::Screen) + screen.pause_renderloop = true +end + +function stop_renderloop!(screen::Screen) + screen.stop_renderloop = true + wait(screen.rendertask[]) # Make sure we quit! +end + +function set_framerate!(screen::Screen, fps=30) + screen.framerate = fps +end + function refreshwindowcb(window, screen) screen.render_tick[] = nothing render_frame(screen) @@ -483,12 +588,69 @@ function refreshwindowcb(window, screen) return end +# Open an interactive window Screen(scene::Scene; screen_attributes...) = singleton_screen(size(scene); visible=true, start_renderloop=true) +# Screen to save a png/jpeg to file or io function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::MIME; screen_attributes...) return singleton_screen(size(scene); visible=false, start_renderloop=false) end +# Screen that is efficient for `colorbuffer(screen)` function Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_attributes...) return singleton_screen(size(scene); visible=false, start_renderloop=false) end + +# TODO add render_tick event to scene events +function vsynced_renderloop(screen) + while isopen(screen) && !screen.stop_renderloop + if screen.pause_renderloop + pollevents(screen); sleep(0.1) + continue + end + pollevents(screen) # GLFW poll + render_frame(screen) + GLFW.SwapBuffers(to_native(screen)) + yield() + end +end + +function fps_renderloop(screen::Screen) + while isopen(screen) && !screen.stop_renderloop + if screen.pause_renderloop + pollevents(screen); sleep(0.1) + continue + end + time_per_frame = 1.0 / screen.framerate + t = time_ns() + pollevents(screen) # GLFW poll + render_frame(screen) + GLFW.SwapBuffers(to_native(screen)) + t_elapsed = (time_ns() - t) / 1e9 + diff = time_per_frame - t_elapsed + if diff > 0.001 # can't sleep less than 0.001 + sleep(diff) + else # if we don't sleep, we still need to yield explicitely to other tasks + yield() + end + end +end + +function renderloop(screen) + isopen(screen) || error("Screen most be open to run renderloop!") + try + if screen.vsync + GLFW.SwapInterval(1) + vsynced_renderloop(screen) + else + GLFW.SwapInterval(0) + fps_renderloop(screen) + end + catch e + showerror(stderr, e, catch_backtrace()) + println(stderr) + rethrow(e) + finally + destroy!(screen) + end +end diff --git a/MakieCore/src/types.jl b/MakieCore/src/types.jl index b8cebd00a0f..b79a2d39d4a 100644 --- a/MakieCore/src/types.jl +++ b/MakieCore/src/types.jl @@ -13,14 +13,19 @@ abstract type ScenePlot{Typ} <: AbstractPlot{Typ} end """ Constructors: - `MakieScreen(scene::Scene; screen_attributes...)` -Constructor aimed at showing the plot in a window. - `MakieScreen(scene::Scene, io::IO, mime; screen_attributes...)` -Screen that writes out a mime to an io - `MakieScreen(scene::Scene, img::Matrix{<: Colorant}; screen_attributes...)` -Screen optimized for `colorbuffer(screen)`. +```julia +# Constructor aimed at showing the plot in a window. +MakieScreen(scene::Scene; screen_attributes...) + +# Screen to save a png/jpeg to file or io +MakieScreen(scene::Scene, io::IO, mime; screen_attributes...) + +# Screen that is efficient for `colorbuffer(screen, format)` +MakieScreen(scene::Scene, format::Makie.ImageStorageFormat; screen_attributes...) +``` Interface: + ```julia # Needs to be overload: size(screen) # Size in pixel diff --git a/RPRMakie/src/RPRMakie.jl b/RPRMakie/src/RPRMakie.jl index 75bc7c8cee6..53efa7eb765 100644 --- a/RPRMakie/src/RPRMakie.jl +++ b/RPRMakie/src/RPRMakie.jl @@ -9,9 +9,22 @@ using FileIO const RPR = RadeonProRender using Makie: colorbuffer -const NUM_ITERATIONS = Ref(200) -const RENDER_RESOURCE = Ref(RPR.RPR_CREATION_FLAGS_ENABLE_GPU0) -const RENDER_PLUGIN = Ref(RPR.Tahoe) +struct ScreenConfig + iterations::Int + max_recursion::Int + resource::RPR.rpr_creation_flags_t + plugin::RPR.PluginType +end + +function ScreenConfig(iterations::Int, max_recursion::Int, render_resource, render_plugin) + return ScreenConfig( + iterations, + max_recursion, + render_resource isa Makie.Automatic ? RPR.RPR_CREATION_FLAGS_ENABLE_GPU0 : render_resource, + render_plugin isa Makie.Automatic ? RPR.Tahoe : render_plugin + ) +end + include("scene.jl") include("lines.jl") @@ -33,10 +46,8 @@ include("volume.jl") * `RPR.HybridPro`: The same as Hybrid, but works only for Radeon GPUs, using AMDs own hardware acceleration API. """ -function activate!(; iterations=200, resource=RENDER_RESOURCE[], plugin=RENDER_PLUGIN[]) - NUM_ITERATIONS[] = iterations - RENDER_RESOURCE[] = resource - RENDER_PLUGIN[] = plugin +function activate!(; screen_config...) + Makie.set_screen_config!(RPRMakie, screen_config) Makie.set_active_backend!(RPRMakie) return end diff --git a/RPRMakie/src/scene.jl b/RPRMakie/src/scene.jl index 61c952d50c4..9e3cdce87fd 100644 --- a/RPRMakie/src/scene.jl +++ b/RPRMakie/src/scene.jl @@ -153,29 +153,36 @@ mutable struct Screen <: Makie.MakieScreen cleared::Bool end -function Screen(scene::Scene; kw...) +Base.size(screen::Screen) = screen.fb_size + +function Base.show(io::IO, ::MIME"image/png", screen::Screen) + img = colorbuffer(screen) + FileIO.save(FileIO.Stream{FileIO.format"PNG"}(Makie.raw_io(io)), img) +end + +function Screen(scene::Scene; screen_config...) fb_size = size(scene) - screen = Screen(fb_size; kw...) + screen = Screen(fb_size; screen_config...) screen.scene = scene return screen end -Screen(scene::Scene, ::IO, ::MIME; kw...) = Screen(scene; kw...) -Screen(scene::Scene, ::Makie.ImageStorageFormat; kw...) = Screen(scene; kw...) +Screen(scene::Scene, ::IO, ::MIME; screen_config...) = Screen(scene; screen_config...) +Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_config...) = Screen(scene; screen_config...) -function Screen(fb_size::NTuple{2,<:Integer}; - iterations=NUM_ITERATIONS[], - resource=RENDER_RESOURCE[], - plugin=RENDER_PLUGIN[], max_recursion=10) - context = RPR.Context(; resource=resource, plugin=plugin) +function Screen(fb_size::NTuple{2,<:Integer}; screen_config...) + config = Makie.merge_screen_config(ScreenConfig, screen_config) + context = RPR.Context(; resource=config.resource, plugin=config.plugin) matsys = RPR.MaterialSystem(context, 0) set_standard_tonemapping!(context) - set!(context, RPR.RPR_CONTEXT_MAX_RECURSION, UInt(max_recursion)) + set!(context, RPR.RPR_CONTEXT_MAX_RECURSION, UInt(config.max_recursion)) framebuffer1 = RPR.FrameBuffer(context, RGBA, fb_size) framebuffer2 = RPR.FrameBuffer(context, RGBA, fb_size) set!(context, RPR.RPR_AOV_COLOR, framebuffer1) - return Screen(context, matsys, framebuffer1, framebuffer2, fb_size, nothing, false, nothing, - iterations, false) + return Screen( + context, matsys, framebuffer1, framebuffer2, fb_size, + nothing, false, nothing, + config.iterations, false) end function render(screen; clear=true, iterations=screen.iterations) @@ -234,3 +241,5 @@ function Base.insert!(screen::Screen, scene::Scene, plot::AbstractPlot) insert_plots!(context, matsys, rpr_scene, scene, plot) return screen end + +Makie.backend_showable(::Type{Screen}, ::Union{MIME"image/jpeg", MIME"image/png"}) = true diff --git a/WGLMakie/src/WGLMakie.jl b/WGLMakie/src/WGLMakie.jl index 4f1711cbf9a..5409295c657 100644 --- a/WGLMakie/src/WGLMakie.jl +++ b/WGLMakie/src/WGLMakie.jl @@ -44,18 +44,14 @@ include("imagelike.jl") include("display.jl") -const SCREEN_CONFIG = Ref(( - fps = 30, -)) - """ activate!(; fps=30) Set fps (frames per second) to a higher number for smoother animations, or to a lower to use less resources. """ -function activate!(; fps=30) - SCREEN_CONFIG[] = merge(SCREEN_CONFIG[], (fps=fps,)) +function activate!(; screen_config...) Makie.set_active_backend!(WGLMakie) + Makie.set_screen_config!(WGLMakie, screen_config) Makie.set_glyph_resolution!(Makie.Low) return end @@ -80,6 +76,6 @@ for name in names(Makie, all=true) end end -include("precompiles.jl") +# include("precompiles.jl") end # module diff --git a/WGLMakie/src/display.jl b/WGLMakie/src/display.jl index 65e5a01b6c2..9b4131c53ac 100644 --- a/WGLMakie/src/display.jl +++ b/WGLMakie/src/display.jl @@ -1,6 +1,6 @@ function JSServe.jsrender(session::Session, scene::Scene) - three, canvas = WGLMakie.three_display(session, scene) + three, canvas = three_display(session, scene) Makie.push_screen!(scene, three) return canvas end diff --git a/WGLMakie/src/three_plot.jl b/WGLMakie/src/three_plot.jl index 9c444cb307a..1bc424a99d5 100644 --- a/WGLMakie/src/three_plot.jl +++ b/WGLMakie/src/three_plot.jl @@ -58,7 +58,14 @@ function JSServe.print_js_code(io::IO, plot::AbstractPlot, context) JSServe.print_js_code(io, js"$(WGL).find_plots($(uuids))", context) end -function three_display(session::Session, scene::Scene; framerate=30.0) +struct ScreenConfig + framerate::Float64 # =30.0 +end + +function three_display(session::Session, scene::Scene; screen_config...) + + config = Makie.merge_screen_config(ScreenConfig, screen_config)::ScreenConfig + serialized = serialize_scene(scene) if TEXTURE_ATLAS_CHANGED[] @@ -90,7 +97,7 @@ function three_display(session::Session, scene::Scene; framerate=30.0) if ( renderer ) { const three_scenes = scenes.map(x=> $(WGL).deserialize_scene(x, canvas)) const cam = new $(THREE).PerspectiveCamera(45, 1, 0, 100) - $(WGL).start_renderloop(renderer, three_scenes, cam, $(framerate)) + $(WGL).start_renderloop(renderer, three_scenes, cam, $(config.framerate)) JSServe.on_update($canvas_width, w_h => { // `renderer.setSize` correctly updates `canvas` dimensions const pixelRatio = renderer.getPixelRatio(); diff --git a/docs/documentation/lighting.md b/docs/documentation/lighting.md index 1260a04522b..a9695e1549c 100644 --- a/docs/documentation/lighting.md +++ b/docs/documentation/lighting.md @@ -27,7 +27,7 @@ GLMakie also implements [_screen-space ambient occlusion_](https://learnopengl.c a good compromise. !!! note - The SSAO postprocessor is turned off by default to save on resources. To turn it on, set `GLMakie.enable_SSAO[] = true`, close any existing GLMakie window and reopen it. + The SSAO postprocessor is turned off by default to save on resources. To turn it on, set `GLMakie.activate!(ssao=true)`, close any existing GLMakie window and reopen it. ## Matcap @@ -95,8 +95,7 @@ app \begin{examplefigure}{} ```julia using GLMakie -GLMakie.activate!() # hide -GLMakie.enable_SSAO[] = true +GLMakie.activate!(ssao=true) # hide GLMakie.closeall() # close any open screen fig = Figure() @@ -113,7 +112,7 @@ fig \end{examplefigure} ```julia:disable-ssao -GLMakie.enable_SSAO[] = false # hide +GLMakie.activate!(ssao=false) hide GLMakie.closeall() # hide ``` diff --git a/docs/makedocs.jl b/docs/makedocs.jl index 877953ab58e..cb02544b617 100644 --- a/docs/makedocs.jl +++ b/docs/makedocs.jl @@ -118,6 +118,9 @@ function make_links_relative() end end +using GLMakie +GLMakie.activate!(pause_renderloop=true) + serve(; single=true, cleanup=false, fail_on_warning=true) # for interactive development of the docs, use: # cd(@__DIR__); serve(single=false, cleanup=true, clear=true, fail_on_warning = false) diff --git a/docs/tutorials/scenes.md b/docs/tutorials/scenes.md index 9c6218ce28c..e7a71e027c0 100644 --- a/docs/tutorials/scenes.md +++ b/docs/tutorials/scenes.md @@ -106,6 +106,7 @@ In GLMakie, we can actually take a look at the depthbuffer, to see how it looks GLMakie.activate!() # hide screen = display(scene) # use display, to get a reference to the screen object depth_color = GLMakie.depthbuffer(screen) +close(screen) # Look at result: f, ax, pl = heatmap(depth_color) Colorbar(f[1, 2], pl) diff --git a/src/display.jl b/src/display.jl index faf68ec5ee8..8c324d4795b 100644 --- a/src/display.jl +++ b/src/display.jl @@ -41,14 +41,32 @@ function delete_screen!(scene::Scene, display::AbstractDisplay) return end -function set_screen_config!(config::RefValue, new_values) - config_attributes = propertynames(config[]) +function set_screen_config!(backend::Module, new_values) + key = nameof(backend) + backend_defaults = CURRENT_DEFAULT_THEME[key] + bkeys = keys(backend_defaults) for (k, v) in pairs(new_values) - if !(k in config_attributes) - error("$k is not a valid screen config. Applicable options: $(config_attributes)") + if !(k in bkeys) + error("$k is not a valid screen config. Applicable options: $(keys(backend_defaults)). For help, check `?$(backend).ScreenCofig`") end + backend_defaults[k] = v end - config[] = merge(config[], new_values) + return +end + +function merge_screen_config(::Type{Config}, screen_config_kw) where Config + backend = parentmodule(Config) + key = nameof(backend) + backend_defaults = CURRENT_DEFAULT_THEME[key] + kw_nt = values(screen_config_kw) + arguments = map(fieldnames(Config)) do name + if haskey(kw_nt, name) + return getfield(kw_nt, name) + else + return to_value(backend_defaults[name]) + end + end + return Config(arguments...) end """ @@ -64,7 +82,7 @@ pt_per_unit=x.pt_per_unit px_per_unit=x.px_per_unit antialias=x.antialias """ -function Base.display(figlike::FigureLike; screen_kw...) +function Base.display(figlike::FigureLike; screen_config...) Backend = current_backend() if ismissing(Backend) error(""" @@ -75,7 +93,7 @@ function Base.display(figlike::FigureLike; screen_kw...) In that case, try `]build GLMakie` and watch out for any warnings. """) end - screen = Backend.Screen(get_scene(figlike); screen_kw...) + screen = Backend.Screen(get_scene(figlike); screen_config...) return display(screen, figlike) end @@ -199,8 +217,8 @@ function Stepper(figlike::FigureLike; backend=current_backend(), format=:png, vi return RamStepper(figlike, screen, Matrix{RGBf}[], format) end -function Stepper(figlike::FigureLike, path::String, step::Int; format=:png, backend=current_backend(), visible=false, connect=false, screen_kw...) - screen = backend.Screen(get_scene(figlike), JuliaNative; visible=visible, start_renderloop=false, screen_kw...) +function Stepper(figlike::FigureLike, path::String, step::Int; format=:png, backend=current_backend(), visible=false, connect=false, screen_config...) + screen = backend.Screen(get_scene(figlike), JuliaNative; visible=visible, start_renderloop=false, screen_config...) display(screen, figlike; connect=connect) return FolderStepper(figlike, screen, path, format, step) end @@ -324,7 +342,7 @@ struct VideoStream end """ - VideoStream(scene::Scene; framerate = 24, visible=false, connect=false, screen_kw...) + VideoStream(scene::Scene; framerate = 24, visible=false, connect=false, screen_config...) Returns a stream and a buffer that you can use, which don't allocate for new frames. Use [`recordframe!(stream)`](@ref) to add new video frames to the stream, and @@ -333,12 +351,12 @@ Use [`recordframe!(stream)`](@ref) to add new video frames to the stream, and * visible=false: make window visible or not * connect=false: connect window events or not """ -function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, connect=false, backend=current_backend(), screen_kw...) +function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, connect=false, backend=current_backend(), screen_config...) #codec = `-codec:v libvpx -quality good -cpu-used 0 -b:v 500k -qmin 10 -qmax 42 -maxrate 500k -bufsize 1000k -threads 8` dir = mktempdir() path = joinpath(dir, "$(gensym(:video)).mkv") scene = get_scene(fig) - screen = backend.Screen(scene, GLNative; visible=visible, start_renderloop=false, screen_kw...) + screen = backend.Screen(scene, GLNative; visible=visible, start_renderloop=false, screen_config...) display(screen, fig; connect=connect) _xdim, _ydim = size(screen) xdim = iseven(_xdim) ? _xdim : _xdim + 1 diff --git a/src/scenes.jl b/src/scenes.jl index 6143e286ed1..29633cf39d5 100644 --- a/src/scenes.jl +++ b/src/scenes.jl @@ -363,7 +363,7 @@ function Base.empty!(scene::Scene) empty!(scene.plots) empty!(scene.theme) - merge!(scene.theme, _current_default_theme) + merge!(scene.theme, CURRENT_DEFAULT_THEME) disconnect!(scene.camera) scene.camera_controls = EmptyCamera() diff --git a/src/theming.jl b/src/theming.jl index 18e1f94ceeb..73165bd34d1 100644 --- a/src/theming.jl +++ b/src/theming.jl @@ -97,32 +97,49 @@ const minimal_default = Attributes( type = "png", px_per_unit = 1.0, pt_per_unit = 0.75, - antialias = :best + antialias = :best, + visible = true, + start_renderloop = false ), + GLMakie = Attributes( + # Renderloop renderloop = automatic, + pause_renderloop = false, vsync = false, framerate = 30.0, + + # GLFW window attributes float = false, - pause_rendering = false, focus_on_show = false, decorated = true, title = "Makie", + fullscreen = false, + debugging = false, + monitor = nothing, + + # Preproccessor + oit = true, + fxaa = true, + ssao = false ), + WGLMakie = Attributes( framerate = 30.0 ), + RPRMakie = Attributes( iterations = 200, resource = automatic, - plugin = automatic + plugin = automatic, + max_recursion = 10 ) ) -const _current_default_theme = deepcopy(minimal_default) +const CURRENT_DEFAULT_THEME = deepcopy(minimal_default) function current_default_theme(; kw_args...) - return merge!(Attributes(kw_args), deepcopy(_current_default_theme)) + return merge!(Attributes(kw_args), deepcopy(CURRENT_DEFAULT_THEME)) end """ @@ -132,10 +149,10 @@ Set the global default theme to `theme` and add / override any attributes given as keyword arguments. """ function set_theme!(new_theme = Theme()::Attributes; kwargs...) - empty!(_current_default_theme) + empty!(CURRENT_DEFAULT_THEME) new_theme = merge!(deepcopy(new_theme), deepcopy(minimal_default)) new_theme = merge!(Theme(kwargs), new_theme) - merge!(_current_default_theme, new_theme) + merge!(CURRENT_DEFAULT_THEME, new_theme) return end @@ -177,7 +194,7 @@ Nested attributes are either also updated incrementally, or replaced if they are """ function update_theme!(with_theme = Theme()::Attributes; kwargs...) new_theme = merge!(with_theme, Theme(kwargs)) - _update_attrs!(_current_default_theme, new_theme) + _update_attrs!(CURRENT_DEFAULT_THEME, new_theme) return end From b32642d8bce672ebac231e5a7a4453e749b9d792 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 4 Oct 2022 21:00:48 +0200 Subject: [PATCH 32/56] fix parse error --- docs/documentation/lighting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/lighting.md b/docs/documentation/lighting.md index a9695e1549c..094f3ac56d6 100644 --- a/docs/documentation/lighting.md +++ b/docs/documentation/lighting.md @@ -112,7 +112,7 @@ fig \end{examplefigure} ```julia:disable-ssao -GLMakie.activate!(ssao=false) hide +GLMakie.activate!(ssao=false) # hide GLMakie.closeall() # hide ``` From afa9525f941b7b90a420b4f921f696dd0d429629 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 5 Oct 2022 16:32:28 +0200 Subject: [PATCH 33/56] bring back precompiles --- CairoMakie/src/CairoMakie.jl | 2 +- GLMakie/src/GLMakie.jl | 2 +- WGLMakie/src/WGLMakie.jl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CairoMakie/src/CairoMakie.jl b/CairoMakie/src/CairoMakie.jl index b670147c1c2..b5995fe1f33 100644 --- a/CairoMakie/src/CairoMakie.jl +++ b/CairoMakie/src/CairoMakie.jl @@ -34,6 +34,6 @@ function __init__() activate!() end -# include("precompiles.jl") +include("precompiles.jl") end diff --git a/GLMakie/src/GLMakie.jl b/GLMakie/src/GLMakie.jl index e75537b7c2d..03e2c38f9e8 100644 --- a/GLMakie/src/GLMakie.jl +++ b/GLMakie/src/GLMakie.jl @@ -51,6 +51,6 @@ function __init__() activate!() end -# include("precompiles.jl") +include("precompiles.jl") end diff --git a/WGLMakie/src/WGLMakie.jl b/WGLMakie/src/WGLMakie.jl index 5409295c657..ff384252199 100644 --- a/WGLMakie/src/WGLMakie.jl +++ b/WGLMakie/src/WGLMakie.jl @@ -76,6 +76,6 @@ for name in names(Makie, all=true) end end -# include("precompiles.jl") +include("precompiles.jl") end # module From 666c82b89ddf794762ca52815c26491326814648 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 5 Oct 2022 16:38:46 +0200 Subject: [PATCH 34/56] small fixes/improvements --- CairoMakie/src/screen.jl | 5 +++-- GLMakie/src/screen.jl | 18 +++--------------- GLMakie/test/unit_tests.jl | 4 ++++ 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 6713967a203..dc0faed46b9 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -188,13 +188,14 @@ end function Makie.colorbuffer(screen::Screen{IMAGE}) ctx = screen.context Cairo.save(ctx) - Cairo.set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0) + bg = rgbatuple(screen.scene.backgroundcolor[]) + Cairo.set_source_rgba(ctx, bg...) Cairo.set_operator(ctx, Cairo.OPERATOR_CLEAR) Cairo.rectangle(ctx, 0, 0, size(screen)...) Cairo.paint_with_alpha(ctx, 1.0) Cairo.restore(ctx) cairo_draw(screen, screen.scene) - return permutedims(RGBf.(screen.surface.data)) + return PermutedDimsArray(screen.surface.data, (2, 1)) end is_vector_backend(ctx::Cairo.CairoContext) = is_vector_backend(ctx.surface) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 8601f01b48c..8cb95926d40 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -302,7 +302,7 @@ function fast_color_data!(dest::Array{RGB{N0f8}, 2}, source::Texture{T, 2}) wher glPixelStorei(GL_PACK_ALIGNMENT, 1) glGetTexImage(source.texturetype, 0, GL_RGB, GL_UNSIGNED_BYTE, dest) GLAbstraction.bind(source, 0) - nothing + return end """ @@ -349,20 +349,8 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma if format == Makie.GLNative return screen.framecache elseif format == Makie.JuliaNative - @static if VERSION < v"1.6" - bufc = copy(screen.framecache) - ind1, ind2 = axes(bufc) - n = first(ind2) + last(ind2) - for i in ind1 - @simd for j in ind2 - @inbounds bufc[i, n-j] = screen.framecache[i, j] - end - end - screen.framecache = bufc - else - reverse!(screen.framecache, dims = 2) - end - return PermutedDimsArray(screen.framecache, (2,1)) + img = screen.framecache + return PermutedDimsArray(view(img, :, size(img, 2):-1:1), (2, 1)) end end diff --git a/GLMakie/test/unit_tests.jl b/GLMakie/test/unit_tests.jl index c78c50228e6..4914f0065c2 100644 --- a/GLMakie/test/unit_tests.jl +++ b/GLMakie/test/unit_tests.jl @@ -6,6 +6,10 @@ function project_sp(scene, point) return point_px .+ offset end +GLMakie.closeall(GLMakie.GLFW_WINDOWS) +GLMakie.closeall(GLMakie.SINGLETON_SCREEN) +GLMakie.closeall(GLMakie.SINGLETON_SCREEN_NO_RENDERLOOP) + @testset "unit tests" begin @testset "Window handling" begin # Without previous windows/figures everything should be empty/unassigned From 5d8d4192434e9113c6758d9c1611154184b26d82 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 5 Oct 2022 17:10:39 +0200 Subject: [PATCH 35/56] incorperate changes from #2231 and refactor scene Co-authored-by: Robert Bennett --- CairoMakie/test/saving.jl | 15 + GLMakie/test/runtests.jl | 3 +- ReferenceTests/src/tests/updating.jl | 13 + src/display.jl | 506 ++++++++++++++++----------- test/record.jl | 59 ++++ 5 files changed, 385 insertions(+), 211 deletions(-) create mode 100644 test/record.jl diff --git a/CairoMakie/test/saving.jl b/CairoMakie/test/saving.jl index 1ea09fac6e0..20f1a0118d0 100644 --- a/CairoMakie/test/saving.jl +++ b/CairoMakie/test/saving.jl @@ -23,3 +23,18 @@ savepath(uid, fmt) = joinpath(format_save_path, "$uid.$fmt") end end end + +@testset "VideoStream & screen options" begin + N = 3 + points = Observable(Point2f[]) + f, ax, pl = scatter(points, axis=(type=Axis, aspect=DataAspect(), limits=(0.4, N + 0.6, 0.4, N + 0.6),), figure=(resolution=(600, 800),)) + vio = Makie.VideoStream(f; format="mp4", px_per_unit=2.0, backend=CairoMakie) + @test vio.screen isa CairoMakie.Screen{CairoMakie.IMAGE} + @test size(vio.screen) == size(f.scene) .* 2 + @test vio.screen.device_scaling_factor == 2.0 + + Makie.recordframe!(vio) + save("test.mp4", vio) + @test isfile("test.mp4") # Make sure no error etc + rm("test.mp4") +end diff --git a/GLMakie/test/runtests.jl b/GLMakie/test/runtests.jl index 4d70462f618..cb4eb0564ba 100644 --- a/GLMakie/test/runtests.jl +++ b/GLMakie/test/runtests.jl @@ -10,9 +10,10 @@ reference_tests_dir = normpath(joinpath(dirname(pathof(Makie)), "..", "Reference Pkg.develop(PackageSpec(path = reference_tests_dir)) using ReferenceTests -GLMakie.activate!() +GLMakie.activate!(framerate=1.0) @testset "mimes" begin + Makie.set_preferred_mime!() f, ax, pl = scatter(1:4) @test showable("image/png", f) @test showable("image/jpeg", f) diff --git a/ReferenceTests/src/tests/updating.jl b/ReferenceTests/src/tests/updating.jl index 495656a8c89..2cfbe635fcb 100644 --- a/ReferenceTests/src/tests/updating.jl +++ b/ReferenceTests/src/tests/updating.jl @@ -39,3 +39,16 @@ end notify(points) Makie.step!(st) end + +@reference_test "record test" begin + points = Observable(Point2f[]) + color = Observable(RGBAf[]) + N = 3 + f, ax, pl = scatter(points, color=color, markersize=1.0, marker=Circle, markerspace=:data, axis=(type=Axis, aspect=DataAspect(), limits=(0.4, N + 0.6, 0.4, N + 0.6),), figure=(resolution=(800, 800),)) + Record(f, CartesianIndices((N, N))) do ij + push!(points.val, Point2f(Tuple(ij))) + push!(color.val, RGBAf((Tuple(ij)./N)..., 0, 1)) + notify(color) + notify(points) + end +end diff --git a/src/display.jl b/src/display.jl index 8c324d4795b..d91a2448de4 100644 --- a/src/display.jl +++ b/src/display.jl @@ -301,16 +301,13 @@ end function FileIO.save( file::FileIO.Formatted, fig::FigureLike; resolution = size(get_scene(fig)), - pt_per_unit = 0.75, - px_per_unit = 1.0, + backend = current_backend(), + screen_config... ) - scene = get_scene(fig) - if resolution != size(scene) resize!(scene, resolution) end - filename = FileIO.filename(file) # Delete previous file if it exists and query only the file string for type. # We overwrite existing files anyway, so this doesn't change the behavior. @@ -320,25 +317,261 @@ function FileIO.save( isfile(filename) && rm(filename) # query the filetype only from the file extension F = filetype(file) - - open(filename, "w") do s - iocontext = IOContext(s, - :full_fidelity => true, - :pt_per_unit => pt_per_unit, - :px_per_unit => px_per_unit - ) - show(iocontext, format2mime(F), fig) + mime = format2mime(F) + open(filename, "w") do io + # If the scene already got displayed, we get the current screen its displayed on + # Else, we create a new scene and update the state of the fig + screen = getscreen(scene, backend) do + update_state_before_display!(fig) + return backend.Screen(scene, io, mime; screen_config...) + end + backend_show(screen, io, mime, scene) end end raw_io(io::IO) = io raw_io(io::IOContext) = raw_io(io.io) +# This has to be overloaded by the backend for its screen type. +function colorbuffer(x::MakieScreen) + error("colorbuffer not implemented for screen $(typeof(x))") +end + +function jl_to_gl_format(image) + @static if VERSION < v"1.6" + d1, d2 = size(image) + bufc = Array{eltype(image)}(undef, d2, d1) #permuted + ind1, ind2 = axes(image) + n = first(ind1) + last(ind1) + for i in ind1 + @simd for j in ind2 + @inbounds bufc[j, n-i] = image[i, j] + end + end + return bufc + else + reverse!(image; dims=1) + return PermutedDimsArray(image, (2, 1)) + end +end + +# less specific for overloading by backends +function colorbuffer(screen::MakieScreen, format::ImageStorageFormat) + image = colorbuffer(screen) + if format == GLNative + return jl_to_gl_format(image) + elseif format == JuliaNative + return image + end +end + +function getscreen(f::Function, scene::Scene, backend::Module) + screen = getscreen(scene) + if !isnothing(screen) && parentmodule(typeof(screen)) == backend + return screen + end + if ismissing(backend) + error(""" + You have not loaded a backend. Please load one (`using GLMakie` or `using CairoMakie`) + before trying to render a Scene. + """) + else + return f() + end +end + +""" + colorbuffer(scene, format::ImageStorageFormat = JuliaNative) + colorbuffer(screen, format::ImageStorageFormat = JuliaNative) + +Returns the content of the given scene or screen rasterised to a Matrix of +Colors. The return type is backend-dependent, but will be some form of RGB +or RGBA. + +- `format = JuliaNative` : Returns a buffer in the format of standard julia images (dims permuted and one reversed) +- `format = GLNative` : Returns a more efficient format buffer for GLMakie which can be directly + used in FFMPEG without conversion +""" +function colorbuffer(fig::FigureLike, format::ImageStorageFormat = JuliaNative; backend = current_backend(), screen_config...) + scene = get_scene(fig) + screen = getscreen(scene, backend) do + screen = backend.Screen(scene, format; start_renderloop=false, visible=false, screen_config...) + display(screen, fig; connect=false) + return screen + end + return colorbuffer(screen, format) +end + +# Fallback for any backend that will just use colorbuffer to write out an image +function backend_show(screen::MakieScreen, io::IO, m::MIME"image/png", scene::Scene) + display(screen, scene; connect=false) + img = colorbuffer(screen) + FileIO.save(FileIO.Stream{FileIO.format"PNG"}(Makie.raw_io(io)), img) + return +end + +function backend_show(screen::MakieScreen, io::IO, m::MIME"image/jpeg", scene::Scene) + display(screen, scene; connect=false) + img = colorbuffer(scene) + FileIO.save(FileIO.Stream{FileIO.format"JPEG"}(Makie.raw_io(io)), img) + return +end + +const VIDEO_STREAM_OPTIONS_FORMAT_DESC = """ +- `format = "mkv"`: The format of the video. Can be one of the following: + * `"mkv"` (open standard, the default) + * `"mp4"` (good for Web, most supported format) + * `"webm"` (smallest file size) + * `"gif"` (largest file size for the same quality) + + `mp4` and `mk4` are marginally bigger than `webm`. `gif`s can be significantly (as much as + 6x) larger with worse quality (due to the limited color palette) and only should be used + as a last resort, for playing in a context where videos aren't supported. +""" + +const VIDEO_STREAM_OPTIONS_KWARGS_DESC = """ +- `framerate = 24`: The target framerate. +- `compression = 20`: Controls the video compression via `ffmpeg`'s `-crf` option, with + smaller numbers giving higher quality and larger file sizes (lower compression), and and + higher numbers giving lower quality and smaller file sizes (higher compression). The + minimum value is `0` (lossless encoding). + - For `mp4`, `51` is the maximum. Note that `compression = 0` only works with `mp4` if + `profile = high444`. + - For `webm`, `63` is the maximum. + - `compression` has no effect on `mkv` and `gif` outputs. +- `profile = "high422"`: A ffmpeg compatible profile. Currently only applies to `mp4`. If + you have issues playing a video, try `profile = "high"` or `profile = "main"`. +- `pixel_format = "yuv420p"`: A ffmpeg compatible pixel format (`-pix_fmt`). Currently only + applies to `mp4`. Defaults to `yuv444p` for `profile = high444`. +""" + +""" + VideoStreamOptions(; format="mkv", framerate=24, compression=20, profile=nothing, pixel_format=nothing) + +Holds the options that will be used for encoding a `VideoStream`. `profile` and +`pixel_format` are only used when `format` is `"mp4"`; a warning will be issued if `format` +is not `"mp4"` and those two arguments are not `nothing`. Similarly, `compression` is only +valid when `format` is `"mp4"` or `"webm"`. + +You should not create a `VideoStreamOptions` directly; instead, pass its keyword args to the +`VideoStream` constructor. See the docs of [`VideoStream`](@ref) for how to create a +`VideoStream`. If you want a simpler interface, consider using [`record`](@ref). + +### Keyword Arguments: +$VIDEO_STREAM_OPTIONS_FORMAT_DESC +$VIDEO_STREAM_OPTIONS_KWARGS_DESC +""" +struct VideoStreamOptions + format::String + framerate::Int + compression::Union{Nothing,Int} + profile::Union{Nothing,String} + pixel_format::Union{Nothing,String} + + function VideoStreamOptions(format::AbstractString, framerate::Integer, compression, profile, pixel_format) + + if format == "mp4" + (profile === nothing) && (profile = "high422") + (pixel_format === nothing) && (pixel_format = (profile == "high444" ? "yuv444p" : "yuv420p")) + end + + if format in ("mp4", "webm") + (compression === nothing) && (compression = 20) + end + + # items are name, value, allowed_formats + allowed_kwargs = [("compression", compression, ("mp4", "webm")), + ("profile", profile, ("mp4",)), + ("pixel_format", pixel_format, ("mp4",))] + + for (name, value, allowed_formats) in allowed_kwargs + if !(format in allowed_formats) && value !== nothing + @warn("""`$name`, with value $(repr(value)) + was passed as a keyword argument to `record` or `VideoStream`, + which only has an effect when the output video's format is one of: $(collect(allowed_formats)). + But the actual video format was $(repr(format)). + Keyword arg `$name` will be ignored. + """) + end + end + return new(format, framerate, compression, profile, pixel_format) + end +end + +function to_ffmpeg_cmd(vso::VideoStreamOptions, xdim::Integer, ydim::Integer) + # explanation of ffmpeg args. note that the order of args is important; args pertaining + # to the input have to go before -i and args pertaining to the output have to go after. + # -y: "yes", overwrite any existing without confirmation + # -f: format is raw video (from frames) + # -framerate: set the input framerate + # -pixel_format: the buffer we're sending ffmpeg via stdin contains rgb24 pixels + # -s:v: sets the dimensions of the input to xdim × ydim + # -i: read input from stdin (pipe:0) + # -vf: video filter for the output + # -c:v, -c:a: video and audio codec, respectively + # -b:v, -b:a: video and audio bitrate, respectively + # -crf: "constant rate factor", the lower the better the quality (0 is lossless, 51 is + # maximum compression) + # -pix_fmt: (mp4 only) the output pixel format + # -profile:v: (mp4 only) the output video profile + # -an: no audio in output + (format, framerate, compression, profile, pixel_format) = (vso.format, vso.framerate, vso.compression, vso.profile, vso.pixel_format) + ffmpeg_prefix = ` + $(FFMPEG.ffmpeg) + -y + -loglevel quiet + -f rawvideo + -framerate $(framerate) + -pixel_format rgb24 + -r $(framerate) + -s:v $(xdim)x$(ydim) + ` + + ffmpeg_options = if format == "mkv" + `-i pipe:0 + -vf vflip + -an + ` + elseif format == "mp4" + `-i pipe:0 + -profile:v $(profile) + -vf scale=$(xdim):$(ydim),vflip + -crf $(compression) + -preset slow + -c:v libx264 + -pix_fmt $(pixel_format) + -an + ` + elseif format == "webm" + # this may need improvement, see here: https://trac.ffmpeg.org/wiki/Encode/VP9 + `-threads 16 + -i pipe:0 + -vf scale=$(xdim):$(ydim),vflip + -crf $(compression) + -c:v libvpx-vp9 + -b:v 0 + -an + ` + elseif format == "gif" + # from https://superuser.com/a/556031 + # avoids creating a PNG file of the palette + `-i pipe:0 + -vf "vflip,fps=$(framerate),scale=$(xdim):-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" + ` + else + error("Video type $(format) not known") + end + + return `$(ffmpeg_prefix) $(ffmpeg_options)` +end + struct VideoStream io process::Base.Process screen::MakieScreen + buffer::Matrix{RGB{N0f8}} path::String + options::VideoStreamOptions end """ @@ -351,18 +584,24 @@ Use [`recordframe!(stream)`](@ref) to add new video frames to the stream, and * visible=false: make window visible or not * connect=false: connect window events or not """ -function VideoStream(fig::FigureLike; framerate::Integer=24, visible=false, connect=false, backend=current_backend(), screen_config...) - #codec = `-codec:v libvpx -quality good -cpu-used 0 -b:v 500k -qmin 10 -qmax 42 -maxrate 500k -bufsize 1000k -threads 8` +function VideoStream(fig::FigureLike; + format="mp4", framerate=24, compression=nothing, profile=nothing, pixel_format=nothing, + visible=false, connect=false, backend=current_backend(), + screen_config...) + dir = mktempdir() - path = joinpath(dir, "$(gensym(:video)).mkv") + path = joinpath(dir, "$(gensym(:video)).$(format)") scene = get_scene(fig) screen = backend.Screen(scene, GLNative; visible=visible, start_renderloop=false, screen_config...) display(screen, fig; connect=connect) _xdim, _ydim = size(screen) xdim = iseven(_xdim) ? _xdim : _xdim + 1 ydim = iseven(_ydim) ? _ydim : _ydim + 1 - process = @ffmpeg_env open(`$(FFMPEG.ffmpeg) -framerate $(framerate) -loglevel quiet -f rawvideo -pixel_format rgb24 -r $framerate -s:v $(xdim)x$(ydim) -i pipe:0 -vf vflip -y $path`, "w") - return VideoStream(process.in, process, screen, abspath(path)) + buffer = Matrix{RGB{N0f8}}(undef, xdim, ydim) + vso = VideoStreamOptions(format, framerate, compression, profile, pixel_format) + cmd = to_ffmpeg_cmd(vso, xdim, ydim) + process = @ffmpeg_env open(`$cmd $path`, "w") + return VideoStream(process.in, process, screen, buffer, abspath(path), vso) end """ @@ -371,89 +610,40 @@ end Adds a video frame to the VideoStream `io`. """ function recordframe!(io::VideoStream) - frame = convert(Matrix{RGB{N0f8}}, colorbuffer(io.screen, GLNative)) - _xdim, _ydim = size(frame) - if isodd(_xdim) || isodd(_ydim) - xdim = iseven(_xdim) ? _xdim : _xdim + 1 - ydim = iseven(_ydim) ? _ydim : _ydim + 1 - padded = fill(zero(eltype(frame)), (xdim, ydim)) - padded[1:_xdim, 1:_ydim] = frame - frame = padded - end - write(io.io, frame) + glnative = colorbuffer(io.screen, GLNative) + # Make no copy if already Matrix{RGB{N0f8}} + # There may be a 1px padding for odd dimensions + xdim, ydim = size(glnative) + copy!(view(io.buffer, 1:xdim, 1:ydim), glnative) + write(io.io, io.buffer) return end """ - save(path::String, io::VideoStream[; kwargs...]) - -Flushes the video stream and converts the file to the extension found in `path`, -which can be one of the following: -- `.mkv` (the default, doesn't need to convert) -- `.mp4` (good for Web, most supported format) -- `.webm` (smallest file size) -- `.gif` (largest file size for the same quality) + save(path::String, io::VideoStream) -`.mp4` and `.mk4` are marginally bigger and `.gif`s are up to -6 times bigger with the same quality! - -See the docs of [`VideoStream`](@ref) for how to create a VideoStream. -If you want a simpler interface, consider using [`record`](@ref). - -### Keyword Arguments: -- `framerate = 24`: The target framerate. -- `compression = 0`: Controls the video compression with `0` being lossless and - `51` being the highest compression. Note that `compression = 0` - only works with `.mp4` if `profile = high444`. -- `profile = "high422"`: A ffmpeg compatible profile. Currently only applies to - `.mp4`. If you have issues playing a video, try - `profile = "high"` or `profile = "main"`. -- `pixel_format = "yuv420p"`: A ffmpeg compatible pixel format (pix_fmt). Currently - only applies to `.mp4`. Defaults to `yuv444p` for - `profile = high444`. +Flushes the video stream and saves it to `path`. `path`'s file extension must be the same as +the format that the `VideoStream` was created with (e.g., if created with format "mp4" then +`path`'s file extension must be ".mp4"). If using [`record`](@ref) then this is handled for +you, as the `VideoStream`'s format is deduced from the file extension of the path passed to +`record`. """ -function save( - path::String, io::VideoStream; - framerate::Int = 24, compression = 20, profile = "high422", - pixel_format = profile == "high444" ? "yuv444p" : "yuv420p", - kwargs... - ) - +function save(path::String, io::VideoStream) close(io.process) wait(io.process) p, typ = splitext(path) - if typ == ".mkv" - cp(io.path, path, force=true) - else - mktempdir() do dir - out = joinpath(dir, "out$(typ)") - if typ == ".mp4" - # ffmpeg_exe(`-loglevel quiet -i $(io.path) -crf $compression -c:v libx264 -preset slow -r $framerate -pix_fmt yuv420p -c:a libvo_aacenc -b:a 128k -y $out`) - ffmpeg_exe(`-loglevel quiet -i $(io.path) -crf $compression -c:v libx264 -preset slow -r $framerate -profile:v $profile -pix_fmt $pixel_format -c:a libvo_aacenc -b:a 128k -y $out`) - elseif typ == ".webm" - ffmpeg_exe(`-loglevel quiet -i $(io.path) -crf $compression -c:v libvpx-vp9 -threads 16 -b:v 2000k -c:a libvorbis -threads 16 -r $framerate -vf scale=iw:ih -y $out`) - elseif typ == ".gif" - filters = "fps=$framerate,scale=iw:ih:flags=lanczos" - palette_path = dirname(io.path) - pname = joinpath(palette_path, "palette.bmp") - isfile(pname) && rm(pname, force = true) - ffmpeg_exe(`-loglevel quiet -i $(io.path) -vf "$filters,palettegen" -y $pname`) - ffmpeg_exe(`-loglevel quiet -i $(io.path) -i $pname -lavfi "$filters [x]; [x][1:v] paletteuse" -y $out`) - rm(pname, force = true) - else - rm(io.path) - error("Video type $typ not known") - end - cp(out, path, force=true) - end + video_fmt = io.options.format + if typ != ".$(video_fmt)" + error("invalid `path`; the video stream was created for `$(video_fmt)`, but the provided path had extension `$(typ)`") end + cp(io.path, path; force=true) rm(io.path) return path end """ - record(func, figure, path; framerate = 24, compression = 20, kwargs...) - record(func, figure, path, iter; framerate = 24, compression = 20, kwargs...) + record(func, figurelike, path; kwargs...) + record(func, figurelike, path, iter; kwargs...) The first signature provides `func` with a VideoStream, which it should call `recordframe!(io)` on when recording a frame. @@ -462,30 +652,22 @@ The second signature iterates `iter`, calling `recordframe!(io)` internally after calling `func` with the current iteration element. Both notations require a Figure, FigureAxisPlot or Scene `figure` to work. - The animation is then saved to `path`, with the format determined by `path`'s -extension. Allowable extensions are: -- `.mkv` (the default, doesn't need to convert) -- `.mp4` (good for Web, most supported format) -- `.webm` (smallest file size) -- `.gif` (largest file size for the same quality) +extension. -`.mp4` and `.mk4` are marginally bigger than `webm` and `.gif`s are up to -6 times bigger with the same quality! +$VIDEO_STREAM_OPTIONS_FORMAT_DESC -The `compression` argument controls the compression ratio; `51` is the -highest compression, and `0` or `1` is the lowest (with `0` being lossless). +### Keyword Arguments: +$VIDEO_STREAM_OPTIONS_KWARGS_DESC -Typical usage patterns would look like: +Typical usage patterns would look like: ```julia record(figure, "video.mp4", itr) do i func(i) # or some other manipulation of the figure end ``` - or, for more tweakability, - ```julia record(figure, "test.gif") do io for i = 1:100 @@ -494,13 +676,10 @@ record(figure, "test.gif") do io end end ``` - If you want a more tweakable interface, consider using [`VideoStream`](@ref) and [`save`](@ref). - ## Extended help ### Examples - ```julia fig, ax, p = lines(rand(10)) record(fig, "test.gif") do io @@ -517,37 +696,28 @@ record(fig, "test.gif", 1:255) do i p[:color] = RGBf(i/255, (255 - i)/255, 0) # animate figure end ``` - -### Keyword Arguments: -- `framerate = 24`: The target framerate. -- `compression = 0`: Controls the video compression with `0` being lossless and - `51` being the highest compression. Note that `compression = 0` - only works with `.mp4` if `profile = high444`. -- `profile = "high422`: A ffmpeg compatible profile. Currently only applies to - `.mp4`. If you have issues playing a video, try - `profile = "high"` or `profile = "main"`. -- `pixel_format = "yuv420p"`: A ffmpeg compatible pixel format (pix_fmt). Currently - only applies to `.mp4`. Defaults to `yuv444p` for - `profile = high444`. """ -function record(func, figlike, path; framerate::Int = 24, kwargs...) - io = Record(func, figlike, framerate = framerate) - save(path, io, framerate = framerate; kwargs...) +function record(func, figlike::FigureLike, path::AbstractString; record_kw...) + format = lstrip(splitext(path)[2], '.') + io = Record(func, figlike; format=format, record_kw...) + save(path, io) end -function Record(func, figlike; framerate=24) - io = VideoStream(figlike; framerate = framerate) - func(io) - return io +function record(func, figlike::FigureLike, path::AbstractString, iter; record_kw...) + format = lstrip(splitext(path)[2], '.') + io = Record(func, figlike, iter; format=format, record_kw...) + save(path, io) end -function record(func, figlike, path, iter; framerate::Int = 24, kwargs...) - io = Record(func, figlike, iter; framerate=framerate) - save(path, io, framerate = framerate; kwargs...) + +function Record(func, figlike; videostream_kw...) + io = VideoStream(figlike; videostream_kw...) + func(io) + return io end -function Record(func, figlike, iter; framerate::Int = 24) - io = VideoStream(figlike; framerate=framerate) +function Record(func, figlike, iter; videostream_kw...) + io = VideoStream(figlike; videostream_kw...) for i in iter func(i) recordframe!(io) @@ -568,87 +738,3 @@ function Base.show(io::IO, ::MIME"text/html", vs::VideoStream) ) end end - - - -# This has to be overloaded by the backend for its screen type. -function colorbuffer(x::MakieScreen) - error("colorbuffer not implemented for screen $(typeof(x))") -end - -function jl_to_gl_format(image) - @static if VERSION < v"1.6" - d1, d2 = size(image) - bufc = Array{eltype(image)}(undef, d2, d1) #permuted - ind1, ind2 = axes(image) - n = first(ind1) + last(ind1) - for i in ind1 - @simd for j in ind2 - @inbounds bufc[j, n-i] = image[i, j] - end - end - return bufc - else - reverse!(image; dims=1) - return collect(PermutedDimsArray(image, (2, 1))) - end -end - -# less specific for overloading by backends -function colorbuffer(screen::Any, format::ImageStorageFormat = JuliaNative) - image = colorbuffer(screen) - if format == GLNative - if string(typeof(screen)) == "GLMakie.Screen" - @warn "Inefficient re-conversion back to GLNative buffer format. Update GLMakie to support direct buffer access" maxlog=1 - end - return jl_to_gl_format(image) - elseif format == JuliaNative - return image - end -end - -""" - colorbuffer(scene, format::ImageStorageFormat = JuliaNative) - colorbuffer(screen, format::ImageStorageFormat = JuliaNative) - -Returns the content of the given scene or screen rasterised to a Matrix of -Colors. The return type is backend-dependent, but will be some form of RGB -or RGBA. - -- `format = JuliaNative` : Returns a buffer in the format of standard julia images (dims permuted and one reversed) -- `format = GLNative` : Returns a more efficient format buffer for GLMakie which can be directly - used in FFMPEG without conversion -""" -function colorbuffer(fig::FigureLike, format::ImageStorageFormat = JuliaNative; backend = current_backend()) - scene = get_scene(fig) - screen = getscreen(scene) - if isnothing(screen) - if ismissing(CURRENT_BACKEND[]) - error(""" - You have not loaded a backend. Please load one (`using GLMakie` or `using CairoMakie`) - before trying to render a Scene. - """) - else - screen = backend.Screen(scene, format; start_renderloop=false, visible=false) - display(screen, fig; connect=false) - return colorbuffer(screen, format) - end - end - return colorbuffer(screen, format) -end - - -# Fallback for any backend that will just use colorbuffer to write out an image -function backend_show(screen::MakieScreen, io::IO, m::MIME"image/png", scene::Scene) - display(screen, scene; connect=false) - img = colorbuffer(screen) - FileIO.save(FileIO.Stream{FileIO.format"PNG"}(Makie.raw_io(io)), img) - return -end - -function backend_show(screen::MakieScreen, io::IO, m::MIME"image/jpeg", scene::Scene) - display(screen, scene; connect=false) - img = colorbuffer(scene) - FileIO.save(FileIO.Stream{FileIO.format"JPEG"}(Makie.raw_io(io)), img) - return -end diff --git a/test/record.jl b/test/record.jl new file mode 100644 index 00000000000..4a0d82cc674 --- /dev/null +++ b/test/record.jl @@ -0,0 +1,59 @@ +using Logging + +mktempdir() do tempdir + @testset "Video encoding" begin + n = 2 + x = 0:(n - 1) + fig, ax, _ = lines(x, zeros(size(x))) + # test for no throwing when encoding + @testset "Encoding" begin + for fmt in ("mkv", "mp4", "webm", "gif") + dst = joinpath(tempdir2, "out.$fmt") + @test begin + record(fig, dst, 1:n) do i + lines!(ax, sin.(i .* x)) + return nothing + end + true + end + end + end + + # test that the proper warnings are thrown + @testset "Warnings" begin + function run_record(dst; kwargs...) + record(fig, dst, 1:n; kwargs...) do i + lines!(ax, sin.(i .* x)) + return nothing + end + end + + # kwarg => (value, (should_warn => format)) + warn_tests = [ + (kwarg=:compression, value=20, warn_fmts=["mkv", "gif"], no_warn_fmts=["mp4", "webm"]), + (kwarg=:profile, value="high422", warn_fmts=["mkv", "webm", "gif"], no_warn_fmts=["mp4"]), + ( + kwarg=:pixel_format, + value="yuv420p", + warn_fmts=["mkv", "webm", "gif"], + no_warn_fmts=["mp4"], + ), + ] + + for (; kwarg, value, warn_fmts, no_warn_fmts) in warn_tests + kwargs = Dict(kwarg => value) + warning_re = Regex("^`$(kwarg)`, with value $(repr(value))") + + for fmt in warn_fmts + dst = joinpath(tempdir2, "out.$fmt") + @test_logs (:warn, warning_re) run_record(dst; kwargs...) + end + + for fmt in no_warn_fmts + dst = joinpath(tempdir2, "out.$fmt") + @test_logs min_level = Logging.Warn run_record(dst; kwargs...) + end + end + end + end +end From 50110ead03a9edb1135f156596d9bb900a9e3393 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 5 Oct 2022 17:25:27 +0200 Subject: [PATCH 36/56] fix CI ? --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab3c64f7551..1be2f3c359f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,7 @@ jobs: with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} + - name: install xclip run: sudo apt-get -y install xclip - uses: julia-actions/cache@v1 - name: Install Julia dependencies From fea07cf3b373fe0b8ac91ee895b9406e4cf1d828 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 6 Oct 2022 11:51:50 +0200 Subject: [PATCH 37/56] improved screen clean up --- GLMakie/src/GLAbstraction/GLTexture.jl | 4 ++ GLMakie/src/GLAbstraction/GLTypes.jl | 9 +++- GLMakie/src/display.jl | 3 +- GLMakie/src/events.jl | 16 +------- GLMakie/src/screen.jl | 57 +++++++++++++------------- src/display.jl | 17 ++++---- 6 files changed, 52 insertions(+), 54 deletions(-) diff --git a/GLMakie/src/GLAbstraction/GLTexture.jl b/GLMakie/src/GLAbstraction/GLTexture.jl index b782f27aa7f..51521d9fdf6 100644 --- a/GLMakie/src/GLAbstraction/GLTexture.jl +++ b/GLMakie/src/GLAbstraction/GLTexture.jl @@ -52,6 +52,10 @@ Base.length(t::TextureBuffer) = length(t.buffer) bind(t::Texture) = glBindTexture(t.texturetype, t.id) bind(t::Texture, id) = glBindTexture(t.texturetype, id) ShaderAbstractions.switch_context!(t::TextureBuffer) = switch_context!(t.texture.context) +function unsafe_free(tb::TextureBuffer) + unsafe_free(tb.texture) + unsafe_free(tb.buffer) +end is_texturearray(t::Texture) = t.texturetype == GL_TEXTURE_2D_ARRAY is_texturebuffer(t::Texture) = t.texturetype == GL_TEXTURE_BUFFER diff --git a/GLMakie/src/GLAbstraction/GLTypes.jl b/GLMakie/src/GLAbstraction/GLTypes.jl index e066dc88a3b..e888216dfd0 100644 --- a/GLMakie/src/GLAbstraction/GLTypes.jl +++ b/GLMakie/src/GLAbstraction/GLTypes.jl @@ -284,13 +284,15 @@ end mutable struct RenderObject{Pre} context # OpenGL context uniforms::Dict{Symbol,Any} + observables::Vector{Observable} # for clean up vertexarray::GLVertexArray prerenderfunction::Pre postrenderfunction id::UInt32 function RenderObject{Pre}( context, - uniforms::Dict{Symbol,Any}, vertexarray::GLVertexArray, + uniforms::Dict{Symbol,Any}, observables::Vector{Observable}, + vertexarray::GLVertexArray, prerenderfunctions, postrenderfunctions ) where Pre fxaa = to_value(pop!(uniforms, :fxaa, true)) @@ -303,7 +305,7 @@ mutable struct RenderObject{Pre} id = pack_bool(RENDER_OBJECT_ID_COUNTER[], fxaa) new( context, - uniforms, vertexarray, + uniforms, observables, vertexarray, prerenderfunctions, postrenderfunctions, id ) @@ -321,7 +323,9 @@ function RenderObject( targets = get(data, :gl_convert_targets, Dict()) delete!(data, :gl_convert_targets) passthrough = Dict{Symbol,Any}() # we also save a few non opengl related values in data + observables = Observable[] for (k, v) in data # convert everything to OpenGL compatible types + v isa Observable && push!(observables, v) # save for clean up if haskey(targets, k) # glconvert is designed to just convert everything to a fitting opengl datatype, but sometimes exceptions are needed # e.g. Texture{T,1} and GLBuffer{T} are both usable as an native conversion canditate for a Julia's Array{T, 1} type. @@ -354,6 +358,7 @@ function RenderObject( robj = RenderObject{Pre}( context, data, + observables, vertexarray, pre, post, diff --git a/GLMakie/src/display.jl b/GLMakie/src/display.jl index 4c4f4246de6..5974c503762 100644 --- a/GLMakie/src/display.jl +++ b/GLMakie/src/display.jl @@ -3,9 +3,8 @@ function Base.display(screen::Screen, scene::Scene; connect=true) resize!(screen, size(scene)...) # So, the GLFW window events are not guarantee to fire # when we close a window, so we ensure this here! - window_open = events(scene).window_open on(screen.window_open) do open - window_open[] = open + events(scene).window_open[] = open end connect && connect_screen(scene, screen) Makie.push_screen!(scene, screen) diff --git a/GLMakie/src/events.jl b/GLMakie/src/events.jl index 0b3cc5ac0b9..914e577adf9 100644 --- a/GLMakie/src/events.jl +++ b/GLMakie/src/events.jl @@ -68,12 +68,12 @@ end function Makie.window_area(scene::Scene, screen::Screen) disconnect!(screen, window_area) - + updater = WindowAreaUpdater( to_native(screen), scene.events.window_dpi, scene.events.window_area ) on(updater, screen.render_tick) - + return end @@ -223,18 +223,6 @@ function Makie.mouse_position(scene::Scene, screen::Screen) to_native(screen), scene.events.mouseposition, scene.events.hasfocus ) on(updater, screen.render_tick) - - # function cursorposition(window, w::Cdouble, h::Cdouble) - # @print_error begin - # pos = correct_mouse(window, w, h) - # @timeit "triggerless mouseposition" begin - # e.mouseposition.val = pos - # end - # return - # end - # end - # GLFW.SetCursorPosCallback(window, cursorposition) - return end function Makie.disconnect!(screen::Screen, ::typeof(mouse_position)) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 8cb95926d40..0ace5304e4b 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -198,6 +198,28 @@ function Base.delete!(screen::Screen, scene::Scene) return end + +function destroy!(rob::RenderObject) + # These need explicit clean up because (some of) the source observables + # remain when the plot is deleted. + GLAbstraction.switch_context!(rob.context) + for (k, v) in rob.uniforms + if v isa Observable + for input in v.inputs + off(input) + end + elseif v isa GPUArray + GLAbstraction.free(v) + end + end + for obs in rob.observables + for input in obs.inputs + off(input) + end + end + GLAbstraction.free(rob.vertexarray) +end + function Base.delete!(screen::Screen, scene::Scene, plot::AbstractPlot) if !isempty(plot.plots) # this plot consists of children, so we flatten it and delete the children instead @@ -205,21 +227,13 @@ function Base.delete!(screen::Screen, scene::Scene, plot::AbstractPlot) delete!(screen, scene, cplot) end else - renderobject = get(screen.cache, objectid(plot)) do - error("Could not find $(typeof(plot)) in current GLMakie screen!") + # I think we can double delete renderobjects, so this may be ok + # TODO, is it? + renderobject = get(screen.cache, objectid(plot), nothing) + if !isnothing(renderobject) + destroy!(renderobject) + filter!(x-> x[3] !== renderobject, screen.renderlist) end - - # These need explicit clean up because (some of) the source observables - # remain when the plot is deleted. - for k in (:normalmatrix, ) - if haskey(renderobject.uniforms, k) - n = renderobject.uniforms[k] - for input in n.inputs - off(input) - end - end - end - filter!(x-> x[3] !== renderobject, screen.renderlist) end end @@ -370,21 +384,6 @@ end Makie.to_native(x::Screen) = x.glscreen -""" -OpenGL shares all data containers between shared contexts, but not vertexarrays -.- -So to share a robjs between a context, we need to rewrap the vertexarray into a new one for that -specific context. -""" -function rewrap(robj::RenderObject{Pre}) where Pre - RenderObject{Pre}( - robj.context, - robj.uniforms, - GLVertexArray(robj.vertexarray), - robj.prerenderfunction, - robj.postrenderfunction, - ) -end - """ Loads the makie loading icon and embedds it in an image the size of resolution """ diff --git a/src/display.jl b/src/display.jl index d91a2448de4..fb0a6037797 100644 --- a/src/display.jl +++ b/src/display.jl @@ -19,15 +19,16 @@ function push_screen!(scene::Scene, display) error("$(display) not a valid Makie display.") end -function push_screen!(scene::Scene, display::AbstractDisplay) - if !any(x -> x === display, scene.current_screens) - push!(scene.current_screens, display) +function push_screen!(scene::Scene, screen::MakieScreen) + if !any(x -> x === screen, scene.current_screens) + push!(scene.current_screens, screen) deregister = nothing deregister = on(events(scene).window_open, priority=typemax(Int)) do is_open # when screen closes, it should set the scene isopen event to false - # so that's when we can remove the display + # so that's when we can remove the screen if !is_open - filter!(x-> x !== display, scene.current_screens) + delete_screen!(scene, screen) + # deregister itself !isnothing(deregister) && off(deregister) end return Consume(false) @@ -36,8 +37,10 @@ function push_screen!(scene::Scene, display::AbstractDisplay) return end -function delete_screen!(scene::Scene, display::AbstractDisplay) - filter!(x-> x !== display, scene.current_screens) +function delete_screen!(scene::Scene, screen::MakieScreen) + delete!(screen, scene) + empty!(screen) + filter!(x-> x !== screen, scene.current_screens) return end From 6a93b545738536e3699d0a365511dca74457cb13 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 6 Oct 2022 14:33:30 +0200 Subject: [PATCH 38/56] clean up and tests --- CairoMakie/src/screen.jl | 20 +- MakieCore/src/types.jl | 1 + ReferenceTests/src/runtests.jl | 7 +- ReferenceTests/src/tests/refimages.jl | 2 + ReferenceTests/src/tests/updating.jl | 57 +++- WGLMakie/src/display.jl | 3 + src/Makie.jl | 2 + src/display.jl | 406 +------------------------- src/ffmpeg-util.jl | 273 +++++++++++++++++ src/makielayout/blocks/axis3d.jl | 1 - src/recording.jl | 170 +++++++++++ test/display.jl | 4 + test/record.jl | 21 +- test/runtests.jl | 1 + 14 files changed, 551 insertions(+), 417 deletions(-) create mode 100644 src/ffmpeg-util.jl create mode 100644 src/recording.jl diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index dc0faed46b9..26b5f7ee5a0 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -38,6 +38,17 @@ struct Screen{SurfaceRenderType} <: Makie.MakieScreen visible::Bool end +function Base.empty!(screen::Screen) + ctx = screen.context + Cairo.save(ctx) + bg = rgbatuple(screen.scene.backgroundcolor[]) + Cairo.set_source_rgba(ctx, bg...) + Cairo.set_operator(ctx, Cairo.OPERATOR_CLEAR) + Cairo.rectangle(ctx, 0, 0, size(screen)...) + Cairo.paint_with_alpha(ctx, 1.0) + Cairo.restore(ctx) +end + Base.size(screen::Screen) = round.(Int, (screen.surface.width, screen.surface.height)) # we render the scene directly, since we have # no screen dependent state like in e.g. opengl @@ -186,14 +197,7 @@ function Makie.colorbuffer(screen::Screen) end function Makie.colorbuffer(screen::Screen{IMAGE}) - ctx = screen.context - Cairo.save(ctx) - bg = rgbatuple(screen.scene.backgroundcolor[]) - Cairo.set_source_rgba(ctx, bg...) - Cairo.set_operator(ctx, Cairo.OPERATOR_CLEAR) - Cairo.rectangle(ctx, 0, 0, size(screen)...) - Cairo.paint_with_alpha(ctx, 1.0) - Cairo.restore(ctx) + empty!(screen) cairo_draw(screen, screen.scene) return PermutedDimsArray(screen.surface.data, (2, 1)) end diff --git a/MakieCore/src/types.jl b/MakieCore/src/types.jl index b79a2d39d4a..6890349c36c 100644 --- a/MakieCore/src/types.jl +++ b/MakieCore/src/types.jl @@ -29,6 +29,7 @@ Interface: ```julia # Needs to be overload: size(screen) # Size in pixel +empty!(screen) # empties screen state to reuse the screen, or to close it # Optional wait(screen) # waits as long window is open diff --git a/ReferenceTests/src/runtests.jl b/ReferenceTests/src/runtests.jl index 129d7a9a2e8..80ca98f85bf 100644 --- a/ReferenceTests/src/runtests.jl +++ b/ReferenceTests/src/runtests.jl @@ -1,8 +1,3 @@ -function extract_frames(video, frame_folder) - path = joinpath(frame_folder, "frames%04d.png") - FFMPEG.ffmpeg_exe(`-loglevel quiet -i $video -y $path`) -end - function get_frames(a, b) return (get_frames(a), get_frames(b)) end @@ -11,7 +6,7 @@ function get_frames(video) mktempdir() do folder afolder = joinpath(folder, "a") mkpath(afolder) - extract_frames(video, afolder) + Makie.extract_frames(video, afolder) aframes = joinpath.(afolder, readdir(afolder)) if length(aframes) > 10 # we don't want to compare too many frames since it's time costly diff --git a/ReferenceTests/src/tests/refimages.jl b/ReferenceTests/src/tests/refimages.jl index 17cfca3cf54..55e510ced3b 100644 --- a/ReferenceTests/src/tests/refimages.jl +++ b/ReferenceTests/src/tests/refimages.jl @@ -8,7 +8,9 @@ using ReferenceTests.FileIO using ReferenceTests.Colors using ReferenceTests.LaTeXStrings using ReferenceTests.DelimitedFiles +using ReferenceTests.Test using Makie: Record, volume +using Colors: RGB, N0f8 @testset "primitives" begin include("primitives.jl") diff --git a/ReferenceTests/src/tests/updating.jl b/ReferenceTests/src/tests/updating.jl index 2cfbe635fcb..485cd35ee85 100644 --- a/ReferenceTests/src/tests/updating.jl +++ b/ReferenceTests/src/tests/updating.jl @@ -40,15 +40,64 @@ end Makie.step!(st) end -@reference_test "record test" begin +function generate_plot(N = 3) points = Observable(Point2f[]) color = Observable(RGBAf[]) - N = 3 - f, ax, pl = scatter(points, color=color, markersize=1.0, marker=Circle, markerspace=:data, axis=(type=Axis, aspect=DataAspect(), limits=(0.4, N + 0.6, 0.4, N + 0.6),), figure=(resolution=(800, 800),)) - Record(f, CartesianIndices((N, N))) do ij + fig, ax, pl = scatter(points, color=color, markersize=1.0, marker=Circle, markerspace=:data, axis=(type=Axis, aspect=DataAspect(), limits=(0.4, N + 0.6, 0.4, N + 0.6),), figure=(resolution=(800, 800),)) + function update_func(ij) push!(points.val, Point2f(Tuple(ij))) push!(color.val, RGBAf((Tuple(ij)./N)..., 0, 1)) notify(color) notify(points) end + return fig, CartesianIndices((N, N)), update_func +end + +@reference_test "record test" begin + fig, iter, func = generate_plot(3) + Record(func, fig, iter) +end + +function load_frames(video, dir) + framedir = joinpath(dir, "frames") + isdir(framedir) && rm(framedir; recursive=true, force=true) + mkdir(framedir) + Makie.extract_frames(video, framedir) + return map(readdir(framedir; join=true)) do path + return convert(Matrix{RGB{N0f8}}, load(path)) + end +end + +function compare_videos(reference, vpath, dir) + to_compare = load_frames(vpath, dir) + n = length(to_compare) + @test n == length(reference) + + @test all(1:n) do i + v = ReferenceTests.compare_media(reference[i], to_compare[i]) + return v < 0.02 + end +end + +# To not spam our reference image comparison with lots of frames, we do a manual frame comparison between the formats and only add the mkv reference to the reference image tests +@reference_test "record test formats" begin + mktempdir() do dir + fig, iter, func = generate_plot(2) + record(func, fig, joinpath(dir, "reference.mkv"), iter) + reference = load_frames(joinpath(dir, "reference.mkv"), dir) + @testset "$format" for format in ["mp4", "mkv", "webm"] + path = joinpath(dir, "test.$format") + fig, iter, func = generate_plot(2) + record(func, fig, path, iter) + compare_videos(reference, path, dir) + + fig, iter, func = generate_plot(2) + vso = Makie.Record(func, fig, iter; format="mkv") + path = joinpath(dir, "test.$format") + save(path, vso) + compare_videos(reference, path, dir) + end + # We re-use ramstepper, to add our array of frames to the reference image comparison + return Makie.RamStepper(fig, Makie.current_backend().Screen(fig.scene), reference, :png) + end end diff --git a/WGLMakie/src/display.jl b/WGLMakie/src/display.jl index 9b4131c53ac..65f5b776376 100644 --- a/WGLMakie/src/display.jl +++ b/WGLMakie/src/display.jl @@ -58,6 +58,9 @@ Screen() = Screen(nothing, nothing) Screen(::Scene; kw...) = Screen() Screen(::Scene, ::IO, ::MIME; kw...) = Screen() Screen(::Scene, ::Makie.ImageStorageFormat; kw...) = Screen() +function Base.empty!(::WGLMakie.Screen) + # TODO, empty state in JS, to be able to reuse screen +end function Base.display(screen::Screen, scene::Scene; kw...) Makie.push_screen!(scene, screen) diff --git a/src/Makie.jl b/src/Makie.jl index 902af9590bf..4688631ee5b 100644 --- a/src/Makie.jl +++ b/src/Makie.jl @@ -172,6 +172,8 @@ include("interaction/inspector.jl") # documentation and help functions include("documentation/documentation.jl") include("display.jl") +include("ffmpeg-util.jl") +include("recording.jl") include("event-recorder.jl") # bezier paths diff --git a/src/display.jl b/src/display.jl index fb0a6037797..ceb262a2d4e 100644 --- a/src/display.jl +++ b/src/display.jl @@ -19,6 +19,11 @@ function push_screen!(scene::Scene, display) error("$(display) not a valid Makie display.") end +""" + push_screen!(scene::Scene, screen::MakieScreen) + +Adds a screen to the scene and registeres a clean up event when screen closes. +""" function push_screen!(scene::Scene, screen::MakieScreen) if !any(x -> x === screen, scene.current_screens) push!(scene.current_screens, screen) @@ -37,6 +42,11 @@ function push_screen!(scene::Scene, screen::MakieScreen) return end +""" + delete_screen!(scene::Scene, screen::MakieScreen) + +Removes screen from scene and cleans up screen +""" function delete_screen!(scene::Scene, screen::MakieScreen) delete!(screen, scene) empty!(screen) @@ -187,79 +197,6 @@ function Base.show(io::IO, m::MIME, figlike::FigureLike) return end -""" - Stepper(scene, path; format = :jpg) - -Creates a Stepper for generating progressive plot examples. - -Each "step" is saved as a separate file in the folder -pointed to by `path`, and the format is customizable by -`format`, which can be any output type your backend supports. - -Notice that the relevant `Makie.step!` is not -exported and should be accessed by module name. -""" -mutable struct FolderStepper - figlike::FigureLike - screen::MakieScreen - folder::String - format::Symbol - step::Int -end - -mutable struct RamStepper - figlike::FigureLike - screen::MakieScreen - images::Vector{Matrix{RGBf}} - format::Symbol -end - -function Stepper(figlike::FigureLike; backend=current_backend(), format=:png, visible=false, connect=false, srceen_kw...) - screen = backend.Screen(get_scene(figlike), JuliaNative; visible=visible, start_renderloop=false, srceen_kw...) - display(screen, figlike; connect=connect) - return RamStepper(figlike, screen, Matrix{RGBf}[], format) -end - -function Stepper(figlike::FigureLike, path::String, step::Int; format=:png, backend=current_backend(), visible=false, connect=false, screen_config...) - screen = backend.Screen(get_scene(figlike), JuliaNative; visible=visible, start_renderloop=false, screen_config...) - display(screen, figlike; connect=connect) - return FolderStepper(figlike, screen, path, format, step) -end - -function Stepper(figlike::FigureLike, path::String; kw...) - ispath(path) || mkpath(path) - return Stepper(figlike, path, 1; kw...) -end - -""" - step!(s::Stepper) - -steps through a `Makie.Stepper` and outputs a file with filename `filename-step.jpg`. -This is useful for generating progressive plot examples. -""" -function step!(s::FolderStepper) - update_state_before_display!(s.figlike) - FileIO.save(joinpath(s.folder, basename(s.folder) * "-$(s.step).$(s.format)"), colorbuffer(s.screen)) - s.step += 1 - return s -end - -function step!(s::RamStepper) - update_state_before_display!(s.figlike) - img = convert(Matrix{RGBf}, colorbuffer(s.screen)) - push!(s.images, img) - return s -end - -function FileIO.save(dir::String, s::RamStepper) - if !isdir(dir) - mkpath(dir) - end - for (i, img) in enumerate(s.images) - FileIO.save(joinpath(dir, "step-$i.$(s.format)"), img) - end -end - format2mime(::Type{FileIO.format"PNG"}) = MIME("image/png") format2mime(::Type{FileIO.format"SVG"}) = MIME("image/svg+xml") format2mime(::Type{FileIO.format"JPEG"}) = MIME("image/jpeg") @@ -332,6 +269,7 @@ function FileIO.save( end end +# Methods are used in backends to unwrap raw_io(io::IO) = io raw_io(io::IOContext) = raw_io(io.io) @@ -419,325 +357,3 @@ function backend_show(screen::MakieScreen, io::IO, m::MIME"image/jpeg", scene::S FileIO.save(FileIO.Stream{FileIO.format"JPEG"}(Makie.raw_io(io)), img) return end - -const VIDEO_STREAM_OPTIONS_FORMAT_DESC = """ -- `format = "mkv"`: The format of the video. Can be one of the following: - * `"mkv"` (open standard, the default) - * `"mp4"` (good for Web, most supported format) - * `"webm"` (smallest file size) - * `"gif"` (largest file size for the same quality) - - `mp4` and `mk4` are marginally bigger than `webm`. `gif`s can be significantly (as much as - 6x) larger with worse quality (due to the limited color palette) and only should be used - as a last resort, for playing in a context where videos aren't supported. -""" - -const VIDEO_STREAM_OPTIONS_KWARGS_DESC = """ -- `framerate = 24`: The target framerate. -- `compression = 20`: Controls the video compression via `ffmpeg`'s `-crf` option, with - smaller numbers giving higher quality and larger file sizes (lower compression), and and - higher numbers giving lower quality and smaller file sizes (higher compression). The - minimum value is `0` (lossless encoding). - - For `mp4`, `51` is the maximum. Note that `compression = 0` only works with `mp4` if - `profile = high444`. - - For `webm`, `63` is the maximum. - - `compression` has no effect on `mkv` and `gif` outputs. -- `profile = "high422"`: A ffmpeg compatible profile. Currently only applies to `mp4`. If - you have issues playing a video, try `profile = "high"` or `profile = "main"`. -- `pixel_format = "yuv420p"`: A ffmpeg compatible pixel format (`-pix_fmt`). Currently only - applies to `mp4`. Defaults to `yuv444p` for `profile = high444`. -""" - -""" - VideoStreamOptions(; format="mkv", framerate=24, compression=20, profile=nothing, pixel_format=nothing) - -Holds the options that will be used for encoding a `VideoStream`. `profile` and -`pixel_format` are only used when `format` is `"mp4"`; a warning will be issued if `format` -is not `"mp4"` and those two arguments are not `nothing`. Similarly, `compression` is only -valid when `format` is `"mp4"` or `"webm"`. - -You should not create a `VideoStreamOptions` directly; instead, pass its keyword args to the -`VideoStream` constructor. See the docs of [`VideoStream`](@ref) for how to create a -`VideoStream`. If you want a simpler interface, consider using [`record`](@ref). - -### Keyword Arguments: -$VIDEO_STREAM_OPTIONS_FORMAT_DESC -$VIDEO_STREAM_OPTIONS_KWARGS_DESC -""" -struct VideoStreamOptions - format::String - framerate::Int - compression::Union{Nothing,Int} - profile::Union{Nothing,String} - pixel_format::Union{Nothing,String} - - function VideoStreamOptions(format::AbstractString, framerate::Integer, compression, profile, pixel_format) - - if format == "mp4" - (profile === nothing) && (profile = "high422") - (pixel_format === nothing) && (pixel_format = (profile == "high444" ? "yuv444p" : "yuv420p")) - end - - if format in ("mp4", "webm") - (compression === nothing) && (compression = 20) - end - - # items are name, value, allowed_formats - allowed_kwargs = [("compression", compression, ("mp4", "webm")), - ("profile", profile, ("mp4",)), - ("pixel_format", pixel_format, ("mp4",))] - - for (name, value, allowed_formats) in allowed_kwargs - if !(format in allowed_formats) && value !== nothing - @warn("""`$name`, with value $(repr(value)) - was passed as a keyword argument to `record` or `VideoStream`, - which only has an effect when the output video's format is one of: $(collect(allowed_formats)). - But the actual video format was $(repr(format)). - Keyword arg `$name` will be ignored. - """) - end - end - return new(format, framerate, compression, profile, pixel_format) - end -end - -function to_ffmpeg_cmd(vso::VideoStreamOptions, xdim::Integer, ydim::Integer) - # explanation of ffmpeg args. note that the order of args is important; args pertaining - # to the input have to go before -i and args pertaining to the output have to go after. - # -y: "yes", overwrite any existing without confirmation - # -f: format is raw video (from frames) - # -framerate: set the input framerate - # -pixel_format: the buffer we're sending ffmpeg via stdin contains rgb24 pixels - # -s:v: sets the dimensions of the input to xdim × ydim - # -i: read input from stdin (pipe:0) - # -vf: video filter for the output - # -c:v, -c:a: video and audio codec, respectively - # -b:v, -b:a: video and audio bitrate, respectively - # -crf: "constant rate factor", the lower the better the quality (0 is lossless, 51 is - # maximum compression) - # -pix_fmt: (mp4 only) the output pixel format - # -profile:v: (mp4 only) the output video profile - # -an: no audio in output - (format, framerate, compression, profile, pixel_format) = (vso.format, vso.framerate, vso.compression, vso.profile, vso.pixel_format) - ffmpeg_prefix = ` - $(FFMPEG.ffmpeg) - -y - -loglevel quiet - -f rawvideo - -framerate $(framerate) - -pixel_format rgb24 - -r $(framerate) - -s:v $(xdim)x$(ydim) - ` - - ffmpeg_options = if format == "mkv" - `-i pipe:0 - -vf vflip - -an - ` - elseif format == "mp4" - `-i pipe:0 - -profile:v $(profile) - -vf scale=$(xdim):$(ydim),vflip - -crf $(compression) - -preset slow - -c:v libx264 - -pix_fmt $(pixel_format) - -an - ` - elseif format == "webm" - # this may need improvement, see here: https://trac.ffmpeg.org/wiki/Encode/VP9 - `-threads 16 - -i pipe:0 - -vf scale=$(xdim):$(ydim),vflip - -crf $(compression) - -c:v libvpx-vp9 - -b:v 0 - -an - ` - elseif format == "gif" - # from https://superuser.com/a/556031 - # avoids creating a PNG file of the palette - `-i pipe:0 - -vf "vflip,fps=$(framerate),scale=$(xdim):-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" - ` - else - error("Video type $(format) not known") - end - - return `$(ffmpeg_prefix) $(ffmpeg_options)` -end - -struct VideoStream - io - process::Base.Process - screen::MakieScreen - buffer::Matrix{RGB{N0f8}} - path::String - options::VideoStreamOptions -end - -""" - VideoStream(scene::Scene; framerate = 24, visible=false, connect=false, screen_config...) - -Returns a stream and a buffer that you can use, which don't allocate for new frames. -Use [`recordframe!(stream)`](@ref) to add new video frames to the stream, and -[`save(path, stream)`](@ref) to save the video. - -* visible=false: make window visible or not -* connect=false: connect window events or not -""" -function VideoStream(fig::FigureLike; - format="mp4", framerate=24, compression=nothing, profile=nothing, pixel_format=nothing, - visible=false, connect=false, backend=current_backend(), - screen_config...) - - dir = mktempdir() - path = joinpath(dir, "$(gensym(:video)).$(format)") - scene = get_scene(fig) - screen = backend.Screen(scene, GLNative; visible=visible, start_renderloop=false, screen_config...) - display(screen, fig; connect=connect) - _xdim, _ydim = size(screen) - xdim = iseven(_xdim) ? _xdim : _xdim + 1 - ydim = iseven(_ydim) ? _ydim : _ydim + 1 - buffer = Matrix{RGB{N0f8}}(undef, xdim, ydim) - vso = VideoStreamOptions(format, framerate, compression, profile, pixel_format) - cmd = to_ffmpeg_cmd(vso, xdim, ydim) - process = @ffmpeg_env open(`$cmd $path`, "w") - return VideoStream(process.in, process, screen, buffer, abspath(path), vso) -end - -""" - recordframe!(io::VideoStream) - -Adds a video frame to the VideoStream `io`. -""" -function recordframe!(io::VideoStream) - glnative = colorbuffer(io.screen, GLNative) - # Make no copy if already Matrix{RGB{N0f8}} - # There may be a 1px padding for odd dimensions - xdim, ydim = size(glnative) - copy!(view(io.buffer, 1:xdim, 1:ydim), glnative) - write(io.io, io.buffer) - return -end - -""" - save(path::String, io::VideoStream) - -Flushes the video stream and saves it to `path`. `path`'s file extension must be the same as -the format that the `VideoStream` was created with (e.g., if created with format "mp4" then -`path`'s file extension must be ".mp4"). If using [`record`](@ref) then this is handled for -you, as the `VideoStream`'s format is deduced from the file extension of the path passed to -`record`. -""" -function save(path::String, io::VideoStream) - close(io.process) - wait(io.process) - p, typ = splitext(path) - video_fmt = io.options.format - if typ != ".$(video_fmt)" - error("invalid `path`; the video stream was created for `$(video_fmt)`, but the provided path had extension `$(typ)`") - end - cp(io.path, path; force=true) - rm(io.path) - return path -end - -""" - record(func, figurelike, path; kwargs...) - record(func, figurelike, path, iter; kwargs...) - -The first signature provides `func` with a VideoStream, which it should call -`recordframe!(io)` on when recording a frame. - -The second signature iterates `iter`, calling `recordframe!(io)` internally -after calling `func` with the current iteration element. - -Both notations require a Figure, FigureAxisPlot or Scene `figure` to work. -The animation is then saved to `path`, with the format determined by `path`'s -extension. - -$VIDEO_STREAM_OPTIONS_FORMAT_DESC - -### Keyword Arguments: -$VIDEO_STREAM_OPTIONS_KWARGS_DESC - - -Typical usage patterns would look like: -```julia -record(figure, "video.mp4", itr) do i - func(i) # or some other manipulation of the figure -end -``` -or, for more tweakability, -```julia -record(figure, "test.gif") do io - for i = 1:100 - func!(figure) # animate figure - recordframe!(io) # record a new frame - end -end -``` -If you want a more tweakable interface, consider using [`VideoStream`](@ref) and -[`save`](@ref). -## Extended help -### Examples -```julia -fig, ax, p = lines(rand(10)) -record(fig, "test.gif") do io - for i in 1:255 - p[:color] = RGBf(i/255, (255 - i)/255, 0) # animate figure - recordframe!(io) - end -end -``` -or -```julia -fig, ax, p = lines(rand(10)) -record(fig, "test.gif", 1:255) do i - p[:color] = RGBf(i/255, (255 - i)/255, 0) # animate figure -end -``` -""" -function record(func, figlike::FigureLike, path::AbstractString; record_kw...) - format = lstrip(splitext(path)[2], '.') - io = Record(func, figlike; format=format, record_kw...) - save(path, io) -end - -function record(func, figlike::FigureLike, path::AbstractString, iter; record_kw...) - format = lstrip(splitext(path)[2], '.') - io = Record(func, figlike, iter; format=format, record_kw...) - save(path, io) -end - - -function Record(func, figlike; videostream_kw...) - io = VideoStream(figlike; videostream_kw...) - func(io) - return io -end - -function Record(func, figlike, iter; videostream_kw...) - io = VideoStream(figlike; videostream_kw...) - for i in iter - func(i) - recordframe!(io) - @debug "Recording" progress=i/length(iter) - yield() - end - return io -end - -function Base.show(io::IO, ::MIME"text/html", vs::VideoStream) - mktempdir() do dir - path = save(joinpath(dir, "video.mp4"), vs) - print( - io, - """""" - ) - end -end diff --git a/src/ffmpeg-util.jl b/src/ffmpeg-util.jl new file mode 100644 index 00000000000..1a983de2455 --- /dev/null +++ b/src/ffmpeg-util.jl @@ -0,0 +1,273 @@ +# TODO move to something like FFMPEGUtil.jl ? + +const VIDEO_STREAM_OPTIONS_FORMAT_DESC = """ +- `format = "mkv"`: The format of the video. Can be one of the following: + * `"mkv"` (open standard, the default) + * `"mp4"` (good for Web, most supported format) + * `"webm"` (smallest file size) + * `"gif"` (largest file size for the same quality) + + `mp4` and `mk4` are marginally bigger than `webm`. `gif`s can be significantly (as much as + 6x) larger with worse quality (due to the limited color palette) and only should be used + as a last resort, for playing in a context where videos aren't supported. +""" + +const VIDEO_STREAM_OPTIONS_KWARGS_DESC = """ +- `framerate = 24`: The target framerate. +- `compression = 20`: Controls the video compression via `ffmpeg`'s `-crf` option, with + smaller numbers giving higher quality and larger file sizes (lower compression), and and + higher numbers giving lower quality and smaller file sizes (higher compression). The + minimum value is `0` (lossless encoding). + - For `mp4`, `51` is the maximum. Note that `compression = 0` only works with `mp4` if + `profile = high444`. + - For `webm`, `63` is the maximum. + - `compression` has no effect on `mkv` and `gif` outputs. +- `profile = "high422"`: A ffmpeg compatible profile. Currently only applies to `mp4`. If + you have issues playing a video, try `profile = "high"` or `profile = "main"`. +- `pixel_format = "yuv420p"`: A ffmpeg compatible pixel format (`-pix_fmt`). Currently only + applies to `mp4`. Defaults to `yuv444p` for `profile = high444`. +""" + +""" + VideoStreamOptions(; format="mkv", framerate=24, compression=20, profile=nothing, pixel_format=nothing, loglevel="quiet", input="pipe:0", rawvideo=true) + +Holds the options that will be used for encoding a `VideoStream`. `profile` and +`pixel_format` are only used when `format` is `"mp4"`; a warning will be issued if `format` +is not `"mp4"` and those two arguments are not `nothing`. Similarly, `compression` is only +valid when `format` is `"mp4"` or `"webm"`. + +You should not create a `VideoStreamOptions` directly; instead, pass its keyword args to the +`VideoStream` constructor. See the docs of [`VideoStream`](@ref) for how to create a +`VideoStream`. If you want a simpler interface, consider using [`record`](@ref). + +### Keyword Arguments: +$VIDEO_STREAM_OPTIONS_FORMAT_DESC +$VIDEO_STREAM_OPTIONS_KWARGS_DESC +""" +struct VideoStreamOptions + format::String + framerate::Int + compression::Union{Nothing,Int} + profile::Union{Nothing,String} + pixel_format::Union{Nothing,String} + + loglevel::String + input::String + rawvideo::Bool + + function VideoStreamOptions( + format::AbstractString, framerate::Integer, compression, profile, + pixel_format, loglevel::String, input::String, rawvideo::Bool=true) + + if format == "mp4" + (profile === nothing) && (profile = "high422") + (pixel_format === nothing) && (pixel_format = (profile == "high444" ? "yuv444p" : "yuv420p")) + end + + if format in ("mp4", "webm") + (compression === nothing) && (compression = 20) + end + + # items are name, value, allowed_formats + allowed_kwargs = [("compression", compression, ("mp4", "webm")), + ("profile", profile, ("mp4",)), + ("pixel_format", pixel_format, ("mp4",))] + + for (name, value, allowed_formats) in allowed_kwargs + if !(format in allowed_formats) && value !== nothing + @warn("""`$name`, with value $(repr(value)) + was passed as a keyword argument to `record` or `VideoStream`, + which only has an effect when the output video's format is one of: $(collect(allowed_formats)). + But the actual video format was $(repr(format)). + Keyword arg `$name` will be ignored. + """) + end + end + + if !((input == "pipe:0" || isfile(input))) + error("file needs to be \"pipe:0\" or a valid file path") + end + + loglevels = Set([ + "quiet", + "panic", + "fatal", + "error", + "warning", + "info", + "verbose", + "debug"]) + + if !(loglevel in loglevels) + error("loglevel needs to be one of $(loglevels)") + end + return new(format, framerate, compression, profile, pixel_format, loglevel, input, rawvideo) + end +end + +function VideoStreamOptions(; format="mp4", framerate=24, compression=nothing, profile=nothing, pixel_format=nothing, loglevel="quiet", input="pipe:0", rawvideo=true) + return VideoStreamOptions(format, framerate, compression, profile, pixel_format, loglevel, input, rawvideo) +end + +function to_ffmpeg_cmd(vso::VideoStreamOptions, xdim::Integer=0, ydim::Integer=0) + # explanation of ffmpeg args. note that the order of args is important; args pertaining + # to the input have to go before -i and args pertaining to the output have to go after. + # -y: "yes", overwrite any existing without confirmation + # -f: format is raw video (from frames) + # -framerate: set the input framerate + # -pixel_format: the buffer we're sending ffmpeg via stdin contains rgb24 pixels + # -s:v: sets the dimensions of the input to xdim × ydim + # -i: read input from stdin (pipe:0) + # -vf: video filter for the output + # -c:v, -c:a: video and audio codec, respectively + # -b:v, -b:a: video and audio bitrate, respectively + # -crf: "constant rate factor", the lower the better the quality (0 is lossless, 51 is + # maximum compression) + # -pix_fmt: (mp4 only) the output pixel format + # -profile:v: (mp4 only) the output video profile + # -an: no audio in output + (format, framerate, compression, profile, pixel_format) = (vso.format, vso.framerate, vso.compression, vso.profile, vso.pixel_format) + + cpu_cores = length(Sys.cpu_info()) + ffmpeg_prefix = ` + $(FFMPEG.ffmpeg) + -y + -loglevel $(vso.loglevel) + -threads $(cpu_cores)` + # options for raw video input via pipe + if vso.rawvideo + ffmpeg_prefix = ` + $ffmpeg_prefix + -framerate $(framerate) + -pixel_format rgb24 + -f rawvideo` + end + xdim > 0 && ydim > 0 && (ffmpeg_prefix = `$ffmpeg_prefix -s:v $(xdim)x$(ydim)`) + ffmpeg_prefix = `$ffmpeg_prefix -r $(framerate) -i $(vso.input)` + # Sigh, it's not easy to specify this for all + if vso.rawvideo && format != "gif" + ffmpeg_prefix = `$ffmpeg_prefix -vf vflip` + end + ffmpeg_options = if format == "mkv" + `-an` + elseif format == "mp4" + `-profile:v $(profile) + -crf $(compression) + -preset slow + -c:v libx264 + -pix_fmt $(pixel_format) + -an + ` + elseif format == "webm" + # this may need improvement, see here: https://trac.ffmpeg.org/wiki/Encode/VP9 + `-crf $(compression) + -c:v libvpx-vp9 + -b:v 0 + -an + ` + elseif format == "gif" + # from https://superuser.com/a/556031 + # avoids creating a PNG file of the palette + if vso.rawvideo + `-vf "vflip,fps=$(framerate),scale=$(xdim):-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse"` + else + `-vf "fps=$(framerate),scale=$(xdim):-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse"` + end + else + error("Video type $(format) not known") + end + + return `$(ffmpeg_prefix) $(ffmpeg_options)` +end + +struct VideoStream + io::Base.PipeEndpoint + process::Base.Process + screen::MakieScreen + buffer::Matrix{RGB{N0f8}} + path::String + options::VideoStreamOptions +end + +""" + VideoStream(scene::Scene; framerate = 24, visible=false, connect=false, screen_config...) + +Returns a `VideoStream` which can pipe new frames into the ffmpeg process with few allocations via [`recordframe!(stream)`](@ref). +When done, use [`save(path, stream)`](@ref) to write the video out to a file. + +* visible=false: make window visible or not +* connect=false: connect window events or not +* backend=current_backend(): backend used to record frames +* screen_config... : Have a look at `?Backend.Screen` for infos about applicable configurations. +""" +function VideoStream(fig::FigureLike; + format="mp4", framerate=24, compression=nothing, profile=nothing, pixel_format=nothing, loglevel="quiet", + visible=false, connect=false, backend=current_backend(), + screen_config...) + + dir = mktempdir() + path = joinpath(dir, "$(gensym(:video)).$(format)") + scene = get_scene(fig) + screen = backend.Screen(scene, GLNative; visible=visible, start_renderloop=false, screen_config...) + display(screen, fig; connect=connect) + _xdim, _ydim = size(screen) + xdim = iseven(_xdim) ? _xdim : _xdim + 1 + ydim = iseven(_ydim) ? _ydim : _ydim + 1 + buffer = Matrix{RGB{N0f8}}(undef, xdim, ydim) + vso = VideoStreamOptions(format, framerate, compression, profile, pixel_format, loglevel, "pipe:0", true) + cmd = to_ffmpeg_cmd(vso, xdim, ydim) + process = @ffmpeg_env open(`$cmd $path`, "w") + return VideoStream(process.in, process, screen, buffer, abspath(path), vso) +end + +""" + recordframe!(io::VideoStream) + +Adds a video frame to the VideoStream `io`. +""" +function recordframe!(io::VideoStream) + glnative = colorbuffer(io.screen, GLNative) + # Make no copy if already Matrix{RGB{N0f8}} + # There may be a 1px padding for odd dimensions + xdim, ydim = size(glnative) + copy!(view(io.buffer, 1:xdim, 1:ydim), glnative) + write(io.io, io.buffer) + return +end + +""" + save(path::String, io::VideoStream) + +Flushes the video stream and saves it to `path`. Ideally, `path`'s file extension is the same as +the format that the `VideoStream` was created with (e.g., if created with format "mp4" then +`path`'s file extension must be ".mp4"). Otherwise, the video will get converted to the target format. +If using [`record`](@ref) then this is handled for you, +as the `VideoStream`'s format is deduced from the file extension of the path passed to `record`. +""" +function save(path::String, io::VideoStream; video_options...) + close(io.process) + wait(io.process) + p, typ = splitext(path) + video_fmt = io.options.format + if typ != ".$(video_fmt)" || !isempty(video_options) + # Maybe warn? + convert_video(io.path, path; video_options...) + else + cp(io.path, path; force=true) + end + rm(io.path) + return path +end + +function convert_video(input_path, output_path; video_options...) + p, typ = splitext(output_path) + format = lstrip(typ, '.') + vso = VideoStreamOptions(; format=format, input=input_path, rawvideo=false, video_options...) + cmd = to_ffmpeg_cmd(vso) + @ffmpeg_env run(`$cmd $output_path`) +end + +function extract_frames(video, frame_folder; loglevel="quiet") + path = joinpath(frame_folder, "frame%04d.png") + FFMPEG.ffmpeg_exe(`-loglevel $(loglevel) -i $video -y $path`) +end diff --git a/src/makielayout/blocks/axis3d.jl b/src/makielayout/blocks/axis3d.jl index 0d49dbc8527..f62afd9a049 100644 --- a/src/makielayout/blocks/axis3d.jl +++ b/src/makielayout/blocks/axis3d.jl @@ -241,7 +241,6 @@ function projectionmatrix(viewmatrix, limits, eyepos, radius, azim, elev, angle, if viewmode in (:fitzoom, :stretch) points = decompose(Point3f, limits) - # @show points projpoints = Ref(pm * viewmatrix) .* to_ndim.(Point4f, points, 1) maxx = maximum(x -> abs(x[1] / x[4]), projpoints) diff --git a/src/recording.jl b/src/recording.jl new file mode 100644 index 00000000000..138864dc848 --- /dev/null +++ b/src/recording.jl @@ -0,0 +1,170 @@ + +""" + Stepper(scene, path; format = :jpg) + +Creates a Stepper for generating progressive plot examples. + +Each "step" is saved as a separate file in the folder +pointed to by `path`, and the format is customizable by +`format`, which can be any output type your backend supports. + +Notice that the relevant `Makie.step!` is not +exported and should be accessed by module name. +""" +mutable struct FolderStepper + figlike::FigureLike + screen::MakieScreen + folder::String + format::Symbol + step::Int +end + +mutable struct RamStepper + figlike::FigureLike + screen::MakieScreen + images::Vector{Matrix{RGBf}} + format::Symbol +end + +function Stepper(figlike::FigureLike; backend=current_backend(), format=:png, visible=false, connect=false, srceen_kw...) + screen = backend.Screen(get_scene(figlike), JuliaNative; visible=visible, start_renderloop=false, srceen_kw...) + display(screen, figlike; connect=connect) + return RamStepper(figlike, screen, Matrix{RGBf}[], format) +end + +function Stepper(figlike::FigureLike, path::String, step::Int; format=:png, backend=current_backend(), visible=false, connect=false, screen_config...) + screen = backend.Screen(get_scene(figlike), JuliaNative; visible=visible, start_renderloop=false, screen_config...) + display(screen, figlike; connect=connect) + return FolderStepper(figlike, screen, path, format, step) +end + +function Stepper(figlike::FigureLike, path::String; kw...) + ispath(path) || mkpath(path) + return Stepper(figlike, path, 1; kw...) +end + +""" + step!(s::Stepper) + +steps through a `Makie.Stepper` and outputs a file with filename `filename-step.jpg`. +This is useful for generating progressive plot examples. +""" +function step!(s::FolderStepper) + update_state_before_display!(s.figlike) + FileIO.save(joinpath(s.folder, basename(s.folder) * "-$(s.step).$(s.format)"), colorbuffer(s.screen)) + s.step += 1 + return s +end + +function step!(s::RamStepper) + update_state_before_display!(s.figlike) + img = convert(Matrix{RGBf}, colorbuffer(s.screen)) + push!(s.images, img) + return s +end + +function FileIO.save(dir::String, s::RamStepper) + if !isdir(dir) + mkpath(dir) + end + for (i, img) in enumerate(s.images) + FileIO.save(joinpath(dir, "step-$i.$(s.format)"), img) + end +end + +""" + record(func, figurelike, path; kwargs...) + record(func, figurelike, path, iter; kwargs...) + +The first signature provides `func` with a VideoStream, which it should call +`recordframe!(io)` on when recording a frame. + +The second signature iterates `iter`, calling `recordframe!(io)` internally +after calling `func` with the current iteration element. + +Both notations require a Figure, FigureAxisPlot or Scene `figure` to work. +The animation is then saved to `path`, with the format determined by `path`'s +extension. + +$VIDEO_STREAM_OPTIONS_FORMAT_DESC + +### Keyword Arguments: +$VIDEO_STREAM_OPTIONS_KWARGS_DESC + + +Typical usage patterns would look like: +```julia +record(figure, "video.mp4", itr) do i + func(i) # or some other manipulation of the figure +end +``` +or, for more tweakability, +```julia +record(figure, "test.gif") do io + for i = 1:100 + func!(figure) # animate figure + recordframe!(io) # record a new frame + end +end +``` +If you want a more tweakable interface, consider using [`VideoStream`](@ref) and +[`save`](@ref). +## Extended help +### Examples +```julia +fig, ax, p = lines(rand(10)) +record(fig, "test.gif") do io + for i in 1:255 + p[:color] = RGBf(i/255, (255 - i)/255, 0) # animate figure + recordframe!(io) + end +end +``` +or +```julia +fig, ax, p = lines(rand(10)) +record(fig, "test.gif", 1:255) do i + p[:color] = RGBf(i/255, (255 - i)/255, 0) # animate figure +end +``` +""" +function record(func, figlike::FigureLike, path::AbstractString; record_kw...) + format = lstrip(splitext(path)[2], '.') + io = Record(func, figlike; format=format, record_kw...) + save(path, io) +end + +function record(func, figlike::FigureLike, path::AbstractString, iter; record_kw...) + format = lstrip(splitext(path)[2], '.') + io = Record(func, figlike, iter; format=format, record_kw...) + save(path, io) +end + +function Record(func, figlike; videostream_kw...) + io = VideoStream(figlike; videostream_kw...) + func(io) + return io +end + +function Record(func, figlike, iter; videostream_kw...) + io = VideoStream(figlike; videostream_kw...) + for i in iter + func(i) + recordframe!(io) + @debug "Recording" progress=i/length(iter) + yield() + end + return io +end + +function Base.show(io::IO, ::MIME"text/html", vs::VideoStream) + mktempdir() do dir + path = save(joinpath(dir, "video.mp4"), vs) + print( + io, + """""" + ) + end +end diff --git a/test/display.jl b/test/display.jl index 06a5fb0aa76..2fc0b76a3b6 100644 --- a/test/display.jl +++ b/test/display.jl @@ -24,12 +24,14 @@ end @testset "with Backend + preferred mime" begin Makie.set_active_backend!(TestBackend) Makie.set_preferred_mime!("text/html") + s = Scene(); @test Base.showable("text/html", s) @test !Base.showable("image/png", s) end @testset "without preferred mime, backend capabilities should be returned" begin Makie.set_preferred_mime!() + s = Scene(); @test Base.showable("text/html", s) @test Base.showable("image/png", s) @test !Base.showable("text/plain", s) @@ -38,6 +40,7 @@ end @testset "only plain for missing backend" begin Makie.set_active_backend!(missing) + s = Scene(); @test Base.showable("text/plain", s) @test !Base.showable("image/png", s) @test !Base.showable("text/html", s) @@ -45,5 +48,6 @@ end @testset "preferred mime without backend should return false" begin Makie.set_preferred_mime!("text/html") + s = Scene(); @test !Base.showable("text/html", s) end diff --git a/test/record.jl b/test/record.jl index 4a0d82cc674..afdd2a3fb8d 100644 --- a/test/record.jl +++ b/test/record.jl @@ -1,5 +1,19 @@ using Logging +module VideoBackend + using Makie + struct Screen <: MakieScreen + size::Tuple{Int, Int} + end + Base.size(screen::Screen) = screen.size + Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_config...) = Screen(size(scene)) + Makie.backend_showable(::Type{Screen}, ::MIME"text/html") = true + Makie.backend_showable(::Type{Screen}, ::MIME"image/png") = true + Makie.colorbuffer(screen::Screen) = zeros(RGBf, reverse(screen.size)...) + Base.display(::Screen, ::Scene; kw...) = nothing +end +Makie.set_active_backend!(VideoBackend) + mktempdir() do tempdir @testset "Video encoding" begin n = 2 @@ -8,7 +22,7 @@ mktempdir() do tempdir # test for no throwing when encoding @testset "Encoding" begin for fmt in ("mkv", "mp4", "webm", "gif") - dst = joinpath(tempdir2, "out.$fmt") + dst = joinpath(tempdir, "out.$fmt") @test begin record(fig, dst, 1:n) do i lines!(ax, sin.(i .* x)) @@ -45,15 +59,16 @@ mktempdir() do tempdir warning_re = Regex("^`$(kwarg)`, with value $(repr(value))") for fmt in warn_fmts - dst = joinpath(tempdir2, "out.$fmt") + dst = joinpath(tempdir, "out.$fmt") @test_logs (:warn, warning_re) run_record(dst; kwargs...) end for fmt in no_warn_fmts - dst = joinpath(tempdir2, "out.$fmt") + dst = joinpath(tempdir, "out.$fmt") @test_logs min_level = Logging.Warn run_record(dst; kwargs...) end end end end end +Makie.set_active_backend!(missing) diff --git a/test/runtests.jl b/test/runtests.jl index 8978535ee43..75a47bfc107 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -18,6 +18,7 @@ using Makie: volume @test all(hi .>= (8,8,10)) end + include("record.jl") include("display.jl") include("scenes.jl") include("conversions.jl") From 04d81313b771c34dc8005440b7099e3ddee9ed67 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 6 Oct 2022 14:40:28 +0200 Subject: [PATCH 39/56] add logging --- test/Project.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index a245f204ca2..42c77e8b94a 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -2,8 +2,10 @@ CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" KernelDensity = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" MeshIO = "7269a6da-0436-5bbc-96c2-40638cbb6118" Observables = "510215fc-4207-5dde-b226-833fc4488ee2" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" @@ -11,4 +13,3 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" From b2f7cefd29d621275d44bfea705e8f0cd6b0dc16 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 6 Oct 2022 16:12:25 +0200 Subject: [PATCH 40/56] fix makie unit tests --- .github/workflows/ci.yml | 2 -- test/events.jl | 13 ++++++++++--- test/record.jl | 6 +++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1be2f3c359f..8f98103de62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,8 +41,6 @@ jobs: with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - name: install xclip - run: sudo apt-get -y install xclip - uses: julia-actions/cache@v1 - name: Install Julia dependencies shell: julia --project=monorepo {0} diff --git a/test/events.jl b/test/events.jl index 6f6f83b598c..31b2b484eb8 100644 --- a/test/events.jl +++ b/test/events.jl @@ -1,6 +1,6 @@ using Makie: MouseButtonEvent, KeyEvent, Figure, Textbox using Makie: Not, And, Or -using InteractiveUtils: clipboard +using InteractiveUtils # rudimentary equality for tests Base.:(==)(l::Exclusively, r::Exclusively) = l.x == r.x @@ -126,7 +126,14 @@ Base.:(==)(l::Or, r::Or) = l.left == r.left && l.right == r.right @test x | false == x end end - + # Okay, this is hacky, + # but we're not going to install a whole linux desktop environment on the CI just to test the clipboard + # (what the hell xclip, y u need all that) + @eval InteractiveUtils begin + const CLIP = Ref{String}() + clipboard(str::String) = (CLIP[] = str) + clipboard() = CLIP[] + end @testset "copy_paste" begin f = Figure(resolution=(640,480)) tb = Textbox(f[1,1], placeholder="Copy/paste into me") @@ -155,7 +162,7 @@ Base.:(==)(l::Or, r::Or) = l.left == r.left && l.right == r.right @test tb.stored_string[] == "test string" - + # Refresh figure to test right control + v combination empty!(f) diff --git a/test/record.jl b/test/record.jl index afdd2a3fb8d..c9a57e72126 100644 --- a/test/record.jl +++ b/test/record.jl @@ -44,8 +44,8 @@ mktempdir() do tempdir # kwarg => (value, (should_warn => format)) warn_tests = [ - (kwarg=:compression, value=20, warn_fmts=["mkv", "gif"], no_warn_fmts=["mp4", "webm"]), - (kwarg=:profile, value="high422", warn_fmts=["mkv", "webm", "gif"], no_warn_fmts=["mp4"]), + (:compression, 20, ["mkv", "gif"], ["mp4", "webm"]), + (:profile, "high422", ["mkv", "webm", "gif"], ["mp4"]), ( kwarg=:pixel_format, value="yuv420p", @@ -54,7 +54,7 @@ mktempdir() do tempdir ), ] - for (; kwarg, value, warn_fmts, no_warn_fmts) in warn_tests + for (kwarg, value, warn_fmts, no_warn_fmts) in warn_tests kwargs = Dict(kwarg => value) warning_re = Regex("^`$(kwarg)`, with value $(repr(value))") From 1fc3da564120a06dc3addbe514421268d7fb6250 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 6 Oct 2022 16:47:01 +0200 Subject: [PATCH 41/56] let CI fail gracefully --- ReferenceTests/src/runtests.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ReferenceTests/src/runtests.jl b/ReferenceTests/src/runtests.jl index 80ca98f85bf..13526823361 100644 --- a/ReferenceTests/src/runtests.jl +++ b/ReferenceTests/src/runtests.jl @@ -37,6 +37,13 @@ function compare_media(a, b; sigma=[1,1]) return compare_media(conv(imga), conv(imgb), sigma=sigma) elseif ext in (".mp4", ".gif") aframes, bframes = get_frames(a, b) + # Frames can differ in length, which usually shouldn't be the case but can happen + # when the implementation of record changes, or when the example changes its number of frames + # In that case, we just return inf + warn + if length(aframes) != length(bframes) + @warn "not the same number of frames in video, difference will be Inf" + return Inf + end return mean(compare_media.(aframes, bframes; sigma=sigma)) else error("Unknown media extension: $ext") From 162c8da25e5750fe19ef6fecf7132226f5a50e10 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 6 Oct 2022 17:11:46 +0200 Subject: [PATCH 42/56] fix import --- ReferenceTests/src/tests/refimages.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReferenceTests/src/tests/refimages.jl b/ReferenceTests/src/tests/refimages.jl index 55e510ced3b..74faac9c3a3 100644 --- a/ReferenceTests/src/tests/refimages.jl +++ b/ReferenceTests/src/tests/refimages.jl @@ -9,8 +9,8 @@ using ReferenceTests.Colors using ReferenceTests.LaTeXStrings using ReferenceTests.DelimitedFiles using ReferenceTests.Test +using ReferenceTests.Colors: RGB, N0f8 using Makie: Record, volume -using Colors: RGB, N0f8 @testset "primitives" begin include("primitives.jl") From 87e070721c7fbd89d83e35e5ad1b1e76962478f0 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 7 Oct 2022 13:16:58 +0200 Subject: [PATCH 43/56] remove last GLMakie.Screen related globals --- GLMakie/src/drawing_primitives.jl | 48 +-- GLMakie/src/gl_backend.jl | 15 +- GLMakie/src/glshaders/image_like.jl | 16 +- GLMakie/src/glshaders/lines.jl | 16 +- GLMakie/src/glshaders/mesh.jl | 8 +- GLMakie/src/glshaders/particles.jl | 32 +- GLMakie/src/glshaders/surface.jl | 16 +- GLMakie/src/glshaders/visualize_interface.jl | 20 +- GLMakie/src/rendering.jl | 2 +- GLMakie/src/screen.jl | 294 ++++++++++--------- src/theming.jl | 6 +- 11 files changed, 247 insertions(+), 226 deletions(-) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index e05ba476910..23b1f98cab5 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -112,7 +112,7 @@ function cached_robj!(robj_func, screen, scene, x::AbstractPlot) robj end -function Base.insert!(screen::GLScreen, scene::Scene, x::Combined) +function Base.insert!(screen::Screen, scene::Scene, x::Combined) ShaderAbstractions.switch_context!(screen.glscreen) # poll inside functions to make wait on compile less prominent pollevents(screen) @@ -188,7 +188,7 @@ function handle_intensities!(attributes) end end -function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::Union{Scatter, MeshScatter})) +function draw_atomic(screen::Screen, scene::Scene, @nospecialize(x::Union{Scatter, MeshScatter})) return cached_robj!(screen, scene, x) do gl_attributes # signals not supported for shading yet gl_attributes[:shading] = to_value(get(gl_attributes, :shading, true)) @@ -231,19 +231,19 @@ function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::Union{Scat delete!(gl_attributes, :color_norm) delete!(gl_attributes, :color_map) end - return draw_pixel_scatter(screen.shader_cache, positions, gl_attributes) + return draw_pixel_scatter(screen, positions, gl_attributes) else handle_intensities!(gl_attributes) if x isa MeshScatter - return draw_mesh_particle(screen.shader_cache, (marker, positions), gl_attributes) + return draw_mesh_particle(screen, (marker, positions), gl_attributes) else - return draw_scatter(screen.shader_cache, (marker, positions), gl_attributes) + return draw_scatter(screen, (marker, positions), gl_attributes) end end end end -function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::Lines)) +function draw_atomic(screen::Screen, scene::Scene, @nospecialize(x::Lines)) return cached_robj!(screen, scene, x) do gl_attributes linestyle = pop!(gl_attributes, :linestyle) data = Dict{Symbol, Any}(gl_attributes) @@ -258,11 +258,11 @@ function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::Lines)) positions = apply_transform(transform_func_obs(x), positions) handle_intensities!(data) connect_camera!(data, scene.camera) - return draw_lines(screen.shader_cache, positions, data) + return draw_lines(screen, positions, data) end end -function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::LineSegments)) +function draw_atomic(screen::Screen, scene::Scene, @nospecialize(x::LineSegments)) return cached_robj!(screen, scene, x) do gl_attributes linestyle = pop!(gl_attributes, :linestyle) data = Dict{Symbol, Any}(gl_attributes) @@ -284,11 +284,11 @@ function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::LineSegmen end connect_camera!(data, scene.camera) - return draw_linesegments(screen.shader_cache, positions, data) + return draw_linesegments(screen, positions, data) end end -function draw_atomic(screen::GLScreen, scene::Scene, +function draw_atomic(screen::Screen, scene::Scene, x::Text{<:Tuple{<:Union{<:Makie.GlyphCollection, <:AbstractVector{<:Makie.GlyphCollection}}}}) return cached_robj!(screen, scene, x) do gl_attributes @@ -360,7 +360,7 @@ function draw_atomic(screen::GLScreen, scene::Scene, connect_camera!(gl_attributes, cam, markerspace) # Avoid julia#15276 - _robj = draw_scatter(screen.shader_cache, (DISTANCEFIELD, positions), gl_attributes) + _robj = draw_scatter(screen, (DISTANCEFIELD, positions), gl_attributes) return _robj end @@ -372,7 +372,7 @@ xy_convert(x::AbstractArray{Float32}, n) = copy(x) xy_convert(x::AbstractArray, n) = el32convert(x) xy_convert(x, n) = Float32[LinRange(extrema(x)..., n + 1);] -function draw_atomic(screen::GLScreen, scene::Scene, x::Heatmap) +function draw_atomic(screen::Screen, scene::Scene, x::Heatmap) return cached_robj!(screen, scene, x) do gl_attributes t = Makie.transform_func_obs(scene) mat = x[3] @@ -413,11 +413,11 @@ function draw_atomic(screen::GLScreen, scene::Scene, x::Heatmap) gl_attributes[:stroke_width] = pop!(gl_attributes, :thickness) connect_camera!(gl_attributes, scene.camera) - return draw_heatmap(screen.shader_cache, tex, gl_attributes) + return draw_heatmap(screen, tex, gl_attributes) end end -function draw_atomic(screen::GLScreen, scene::Scene, x::Image) +function draw_atomic(screen::Screen, scene::Scene, x::Image) return cached_robj!(screen, scene, x) do gl_attributes mesh = const_lift(x[1], x[2]) do x, y r = to_range(x, y) @@ -434,7 +434,7 @@ function draw_atomic(screen::GLScreen, scene::Scene, x::Image) gl_attributes[:color] = x[3] gl_attributes[:shading] = false connect_camera!(gl_attributes, scene.camera) - return mesh_inner(screen.shader_cache, mesh, transform_func_obs(x), gl_attributes) + return mesh_inner(screen, mesh, transform_func_obs(x), gl_attributes) end end @@ -445,7 +445,7 @@ function update_positions(mesh::GeometryBasics.Mesh, positions) return GeometryBasics.Mesh(meta(positions; attr...), faces(mesh)) end -function mesh_inner(shader_cache, mesh, transfunc, gl_attributes) +function mesh_inner(screen::Screen, mesh, transfunc, gl_attributes) # signals not supported for shading yet gl_attributes[:shading] = to_value(pop!(gl_attributes, :shading)) color = pop!(gl_attributes, :color) @@ -477,18 +477,18 @@ function mesh_inner(shader_cache, mesh, transfunc, gl_attributes) end return mesh end - return draw_mesh(shader_cache, mesh, gl_attributes) + return draw_mesh(screen, mesh, gl_attributes) end -function draw_atomic(screen::GLScreen, scene::Scene, meshplot::Mesh) +function draw_atomic(screen::Screen, scene::Scene, meshplot::Mesh) return cached_robj!(screen, scene, meshplot) do gl_attributes t = transform_func_obs(meshplot) connect_camera!(gl_attributes, scene.camera) - return mesh_inner(screen.shader_cache, meshplot[1], t, gl_attributes) + return mesh_inner(screen, meshplot[1], t, gl_attributes) end end -function draw_atomic(screen::GLScreen, scene::Scene, x::Surface) +function draw_atomic(screen::Screen, scene::Scene, x::Surface) robj = cached_robj!(screen, scene, x) do gl_attributes color = pop!(gl_attributes, :color) img = nothing @@ -543,20 +543,20 @@ function draw_atomic(screen::GLScreen, scene::Scene, x::Surface) if isnothing(img) gl_attributes[:image] = args[3] end - return draw_surface(screen.shader_cache, args, gl_attributes) + return draw_surface(screen, args, gl_attributes) else gl_attributes[:ranges] = to_range.(to_value.(x[1:2])) z_data = Texture(el32convert(x[3]); minfilter=:linear) if isnothing(img) gl_attributes[:image] = z_data end - return draw_surface(screen.shader_cache, z_data, gl_attributes) + return draw_surface(screen, z_data, gl_attributes) end end return robj end -function draw_atomic(screen::GLScreen, scene::Scene, vol::Volume) +function draw_atomic(screen::Screen, scene::Scene, vol::Volume) robj = cached_robj!(screen, scene, vol) do gl_attributes model = vol[:model] x, y, z = vol[1], vol[2], vol[3] @@ -573,6 +573,6 @@ function draw_atomic(screen::GLScreen, scene::Scene, vol::Volume) return convert(Mat4f, m) * m2 end connect_camera!(gl_attributes, scene.camera) - return draw_volume(screen.shader_cache, vol[4], gl_attributes) + return draw_volume(screen, vol[4], gl_attributes) end end diff --git a/GLMakie/src/gl_backend.jl b/GLMakie/src/gl_backend.jl index 3557cf6bef7..559b8a4137b 100644 --- a/GLMakie/src/gl_backend.jl +++ b/GLMakie/src/gl_backend.jl @@ -54,15 +54,9 @@ function get_texture!(atlas) return tex end -# find a better way to handle this -# enable_SSAO and FXAA adjust the rendering pipeline and are currently per screen -const enable_SSAO = Ref(false) -const enable_FXAA = Ref(true) -# This adjusts a factor in the rendering shaders for order independent -# transparency. This should be the same for all of them (within one rendering -# pipeline) otherwise depth "order" will be broken. -const transparency_weight_scale = Ref(1000f0) - +include("glwindow.jl") +include("postprocessing.jl") +include("screen.jl") include("glshaders/visualize_interface.jl") include("glshaders/lines.jl") include("glshaders/image_like.jl") @@ -70,9 +64,6 @@ include("glshaders/mesh.jl") include("glshaders/particles.jl") include("glshaders/surface.jl") -include("glwindow.jl") -include("postprocessing.jl") -include("screen.jl") include("picking.jl") include("rendering.jl") include("events.jl") diff --git a/GLMakie/src/glshaders/image_like.jl b/GLMakie/src/glshaders/image_like.jl index 117a4a400f5..ec21243a53f 100644 --- a/GLMakie/src/glshaders/image_like.jl +++ b/GLMakie/src/glshaders/image_like.jl @@ -31,7 +31,7 @@ end """ A matrix of Intensities will result in a contourf kind of plot """ -function draw_heatmap(shader_cache, main, data::Dict) +function draw_heatmap(screen, main, data::Dict) primitive = triangle_mesh(Rect2(0f0,0f0,1f0,1f0)) to_opengl_mesh!(data, primitive) @gen_defaults! data begin @@ -46,11 +46,11 @@ function draw_heatmap(shader_cache, main, data::Dict) stroke_color = RGBA{Float32}(0,0,0,0) transparency = false shader = GLVisualizeShader( - shader_cache, + screen, "fragment_output.frag", "heatmap.vert", "heatmap.frag", view = Dict( - "buffers" => output_buffers(to_value(transparency)), - "buffer_writes" => output_buffer_writes(to_value(transparency)) + "buffers" => output_buffers(screen, to_value(transparency)), + "buffer_writes" => output_buffer_writes(screen, to_value(transparency)) ) ) fxaa = false @@ -58,7 +58,7 @@ function draw_heatmap(shader_cache, main, data::Dict) return assemble_shader(data) end -function draw_volume(shader_cache, main::VolumeTypes, data::Dict) +function draw_volume(screen, main::VolumeTypes, data::Dict) geom = Rect3f(Vec3f(0), Vec3f(1)) to_opengl_mesh!(data, const_lift(GeometryBasics.triangle_mesh, geom)) @gen_defaults! data begin @@ -76,15 +76,15 @@ function draw_volume(shader_cache, main::VolumeTypes, data::Dict) enable_depth = true transparency = false shader = GLVisualizeShader( - shader_cache, + screen, "fragment_output.frag", "util.vert", "volume.vert", "volume.frag", view = Dict( "depth_init" => vol_depth_init(to_value(enable_depth)), "depth_default" => vol_depth_default(to_value(enable_depth)), "depth_main" => vol_depth_main(to_value(enable_depth)), "depth_write" => vol_depth_write(to_value(enable_depth)), - "buffers" => output_buffers(to_value(transparency)), - "buffer_writes" => output_buffer_writes(to_value(transparency)) + "buffers" => output_buffers(screen, to_value(transparency)), + "buffer_writes" => output_buffer_writes(screen, to_value(transparency)) ) ) prerender = VolumePrerender(data[:transparency], data[:overdraw]) diff --git a/GLMakie/src/glshaders/lines.jl b/GLMakie/src/glshaders/lines.jl index aded0e784ae..323d21e3d01 100644 --- a/GLMakie/src/glshaders/lines.jl +++ b/GLMakie/src/glshaders/lines.jl @@ -48,7 +48,7 @@ function ticks(points, resolution) end @nospecialize -function draw_lines(shader_cache, position::Union{VectorTypes{T}, MatTypes{T}}, data::Dict) where T<:Point +function draw_lines(screen, position::Union{VectorTypes{T}, MatTypes{T}}, data::Dict) where T<:Point p_vec = if isa(position, GPUArray) position else @@ -74,11 +74,11 @@ function draw_lines(shader_cache, position::Union{VectorTypes{T}, MatTypes{T}}, end => to_index_buffer transparency = false shader = GLVisualizeShader( - shader_cache, + screen, "fragment_output.frag", "util.vert", "lines.vert", "lines.geom", "lines.frag", view = Dict( - "buffers" => output_buffers(to_value(transparency)), - "buffer_writes" => output_buffer_writes(to_value(transparency)) + "buffers" => output_buffers(screen, to_value(transparency)), + "buffer_writes" => output_buffer_writes(screen, to_value(transparency)) ) ) gl_primitive = GL_LINE_STRIP_ADJACENCY @@ -104,7 +104,7 @@ function draw_lines(shader_cache, position::Union{VectorTypes{T}, MatTypes{T}}, return assemble_shader(data) end -function draw_linesegments(shader_cache, positions::VectorTypes{T}, data::Dict) where T <: Point +function draw_linesegments(screen, positions::VectorTypes{T}, data::Dict) where T <: Point @gen_defaults! data begin vertex = positions => GLBuffer color = default(RGBA, s, 1) => GLBuffer @@ -118,11 +118,11 @@ function draw_linesegments(shader_cache, positions::VectorTypes{T}, data::Dict) # TODO update boundingbox transparency = false shader = GLVisualizeShader( - shader_cache, + screen, "fragment_output.frag", "util.vert", "line_segment.vert", "line_segment.geom", "lines.frag", view = Dict( - "buffers" => output_buffers(to_value(transparency)), - "buffer_writes" => output_buffer_writes(to_value(transparency)) + "buffers" => output_buffers(screen, to_value(transparency)), + "buffer_writes" => output_buffer_writes(screen, to_value(transparency)) ) ) gl_primitive = GL_LINES diff --git a/GLMakie/src/glshaders/mesh.jl b/GLMakie/src/glshaders/mesh.jl index a327ce56e06..b64a91a5380 100644 --- a/GLMakie/src/glshaders/mesh.jl +++ b/GLMakie/src/glshaders/mesh.jl @@ -31,7 +31,7 @@ function to_opengl_mesh!(result, mesh_obs::TOrSignal{<: GeometryBasics.Mesh}) return result end -function draw_mesh(shader_cache, @nospecialize(mesh), data::Dict) +function draw_mesh(screen, @nospecialize(mesh), data::Dict) to_opengl_mesh!(data, mesh) @gen_defaults! data begin shading = true @@ -47,12 +47,12 @@ function draw_mesh(shader_cache, @nospecialize(mesh), data::Dict) transparency = false interpolate_in_fragment_shader = true shader = GLVisualizeShader( - shader_cache, + screen, "util.vert", "mesh.vert", "mesh.frag", "fragment_output.frag", view = Dict( "light_calc" => light_calc(shading), - "buffers" => output_buffers(to_value(transparency)), - "buffer_writes" => output_buffer_writes(to_value(transparency)) + "buffers" => output_buffers(screen, to_value(transparency)), + "buffer_writes" => output_buffer_writes(screen, to_value(transparency)) ) ) end diff --git a/GLMakie/src/glshaders/particles.jl b/GLMakie/src/glshaders/particles.jl index f051f650194..0e2ecdf6836 100644 --- a/GLMakie/src/glshaders/particles.jl +++ b/GLMakie/src/glshaders/particles.jl @@ -41,7 +41,7 @@ end """ This is the main function to assemble particles with a GLNormalMesh as a primitive """ -function draw_mesh_particle(shader_cache, p, data) +function draw_mesh_particle(screen, p, data) rot = get!(data, :rotation, Vec4f(0, 0, 0, 1)) rot = vec2quaternion(rot) delete!(data, :rotation) @@ -70,13 +70,13 @@ function draw_mesh_particle(shader_cache, p, data) shading = true transparency = false shader = GLVisualizeShader( - shader_cache, + screen, "util.vert", "particles.vert", "mesh.frag", "fragment_output.frag", view = Dict( "position_calc" => position_calc(position, nothing, nothing, nothing, TextureBuffer), "light_calc" => light_calc(shading), - "buffers" => output_buffers(to_value(transparency)), - "buffer_writes" => output_buffer_writes(to_value(transparency)) + "buffers" => output_buffers(screen, to_value(transparency)), + "buffer_writes" => output_buffer_writes(screen, to_value(transparency)) ) ) end @@ -92,7 +92,7 @@ end This is the most primitive particle system, which uses simple points as primitives. This is supposed to be the fastest way of displaying particles! """ -function draw_pixel_scatter(shader_cache, position::VectorTypes, data::Dict) +function draw_pixel_scatter(screen, position::VectorTypes, data::Dict) @gen_defaults! data begin vertex = position => GLBuffer color_map = nothing => Texture @@ -101,11 +101,11 @@ function draw_pixel_scatter(shader_cache, position::VectorTypes, data::Dict) scale = 2f0 transparency = false shader = GLVisualizeShader( - shader_cache, + screen, "fragment_output.frag", "dots.vert", "dots.frag", view = Dict( - "buffers" => output_buffers(to_value(transparency)), - "buffer_writes" => output_buffer_writes(to_value(transparency)) + "buffers" => output_buffers(screen, to_value(transparency)), + "buffer_writes" => output_buffer_writes(screen, to_value(transparency)) ) ) gl_primitive = GL_POINTS @@ -115,18 +115,18 @@ function draw_pixel_scatter(shader_cache, position::VectorTypes, data::Dict) end function draw_scatter( - shader_cache, p::Tuple{TOrSignal{Matrix{C}}, VectorTypes{P}}, data::Dict + screen, p::Tuple{TOrSignal{Matrix{C}}, VectorTypes{P}}, data::Dict ) where {C <: Colorant, P <: Point} data[:image] = p[1] # we don't want this to be overwritten by user @gen_defaults! data begin scale = lift(x-> Vec2f(size(x)), p[1]) offset = Vec2f(0) end - draw_scatter(shader_cache, (RECTANGLE, p[2]), data) + draw_scatter(screen, (RECTANGLE, p[2]), data) end function draw_scatter( - shader_cache, p::Tuple{VectorTypes{Matrix{C}}, VectorTypes{P}}, data::Dict + screen, p::Tuple{VectorTypes{Matrix{C}}, VectorTypes{P}}, data::Dict ) where {C <: Colorant, P <: Point} images = map(el32convert, to_value(p[1])) isempty(images) && error("Can not display empty vector of images as primitive") @@ -155,7 +155,7 @@ function draw_scatter( shape = RECTANGLE quad_offset = Vec2f(0) end - return draw_scatter(shader_cache, (RECTANGLE, p[2]), data) + return draw_scatter(screen, (RECTANGLE, p[2]), data) end function texture_distancefield(shape) @@ -168,7 +168,7 @@ end Main assemble functions for scatter particles. Sprites are anything like distance fields, images and simple geometries """ -function draw_scatter(shader_cache, (marker, position), data) +function draw_scatter(screen, (marker, position), data) rot = get!(data, :rotation, Vec4f(0, 0, 0, 1)) rot = vec2quaternion(rot) delete!(data, :rotation) @@ -202,13 +202,13 @@ function draw_scatter(shader_cache, (marker, position), data) fxaa = false transparency = false shader = GLVisualizeShader( - shader_cache, + screen, "fragment_output.frag", "util.vert", "sprites.geom", "sprites.vert", "distance_shape.frag", view = Dict( "position_calc" => position_calc(position, nothing, nothing, nothing, GLBuffer), - "buffers" => output_buffers(to_value(transparency)), - "buffer_writes" => output_buffer_writes(to_value(transparency)) + "buffers" => output_buffers(screen, to_value(transparency)), + "buffer_writes" => output_buffer_writes(screen, to_value(transparency)) ) ) scale_primitive = true diff --git a/GLMakie/src/glshaders/surface.jl b/GLMakie/src/glshaders/surface.jl index 5e7b1c5cf49..c4d5961fc1a 100644 --- a/GLMakie/src/glshaders/surface.jl +++ b/GLMakie/src/glshaders/surface.jl @@ -86,28 +86,28 @@ end @nospecialize # surface(::Matrix, ::Matrix, ::Matrix) -function draw_surface(shader_cache, main::Tuple{MatTypes{T}, MatTypes{T}, MatTypes{T}}, data::Dict) where T <: AbstractFloat +function draw_surface(screen, main::Tuple{MatTypes{T}, MatTypes{T}, MatTypes{T}}, data::Dict) where T <: AbstractFloat @gen_defaults! data begin position_x = main[1] => (Texture, "x position, must be a `Matrix{Float}`") position_y = main[2] => (Texture, "y position, must be a `Matrix{Float}`") position_z = main[3] => (Texture, "z position, must be a `Matrix{Float}`") scale = Vec3f(0) => "scale must be 0, for a surfacemesh" end - return draw_surface(shader_cache, position_z, data) + return draw_surface(screen, position_z, data) end # surface(Vector or Range, Vector or Range, ::Matrix) -function draw_surface(shader_cache, main::Tuple{VectorTypes{T}, VectorTypes{T}, MatTypes{T}}, data::Dict) where T <: AbstractFloat +function draw_surface(screen, main::Tuple{VectorTypes{T}, VectorTypes{T}, MatTypes{T}}, data::Dict) where T <: AbstractFloat @gen_defaults! data begin position_x = main[1] => (Texture, "x position, must be a `Vector{Float}`") position_y = main[2] => (Texture, "y position, must be a `Vector{Float}`") position_z = main[3] => (Texture, "z position, must be a `Matrix{Float}`") scale = Vec3f(0) => "scale must be 0, for a surfacemesh" end - return draw_surface(shader_cache, position_z, data) + return draw_surface(screen, position_z, data) end -function draw_surface(shader_cache, main, data::Dict) +function draw_surface(screen, main, data::Dict) primitive = triangle_mesh(Rect2(0f0,0f0,1f0,1f0)) to_opengl_mesh!(data, primitive) @gen_defaults! data begin @@ -137,15 +137,15 @@ function draw_surface(shader_cache, main, data::Dict) instances = const_lift(x->(size(x,1)-1) * (size(x,2)-1), main) => "number of planes used to render the surface" transparency = false shader = GLVisualizeShader( - shader_cache, + screen, "fragment_output.frag", "util.vert", "surface.vert", "mesh.frag", view = Dict( "position_calc" => position_calc(position, position_x, position_y, position_z, Texture), "normal_calc" => normal_calc(normal, to_value(invert_normals)), "light_calc" => light_calc(shading), - "buffers" => output_buffers(to_value(transparency)), - "buffer_writes" => output_buffer_writes(to_value(transparency)) + "buffers" => output_buffers(screen, to_value(transparency)), + "buffer_writes" => output_buffer_writes(screen, to_value(transparency)) ) ) end diff --git a/GLMakie/src/glshaders/visualize_interface.jl b/GLMakie/src/glshaders/visualize_interface.jl index 00a3e1f252a..018c6232882 100644 --- a/GLMakie/src/glshaders/visualize_interface.jl +++ b/GLMakie/src/glshaders/visualize_interface.jl @@ -65,11 +65,11 @@ function GLAbstraction.gl_convert_struct(g::Grid{1,T}, uniform_name::Symbol) whe end struct GLVisualizeShader <: AbstractLazyShader - shader_cache::GLAbstraction.ShaderCache + screen::Screen paths::Tuple kw_args::Dict{Symbol,Any} function GLVisualizeShader( - shader_cache::GLAbstraction.ShaderCache, paths::String...; + screen::Screen, paths::String...; view = Dict{String,String}(), kw_args... ) # TODO properly check what extensions are available @@ -80,12 +80,12 @@ struct GLVisualizeShader <: AbstractLazyShader args = Dict{Symbol, Any}(kw_args) args[:view] = view args[:fragdatalocation] = [(0, "fragment_color"), (1, "fragment_groupid")] - new(shader_cache, map(x -> loadshader(x), paths), args) + new(screen, map(x -> loadshader(x), paths), args) end end function GLAbstraction.gl_convert(shader::GLVisualizeShader, data) - GLAbstraction.gl_convert(shader.shader_cache, shader, data) + GLAbstraction.gl_convert(shader.screen.shader_cache, shader, data) end function assemble_shader(data) @@ -105,7 +105,7 @@ function assemble_shader(data) GLAbstraction.StandardPrerender(transp, overdraw) end - robj = RenderObject(data, shader, pre, shader.shader_cache.context) + robj = RenderObject(data, shader, pre, shader.screen.glscreen) post = if haskey(data, :instances) GLAbstraction.StandardPostrenderInstanced(data[:instances], robj.vertexarray, primitive) @@ -148,12 +148,12 @@ to_index_buffer(x) = error( Please choose from Int, Vector{UnitRange{Int}}, Vector{Int} or a signal of either of them" ) -function output_buffers(transparency = false) +function output_buffers(screen::Screen, transparency = false) if transparency """ layout(location=2) out float coverage; """ - elseif enable_SSAO[] + elseif screen.config.ssao """ layout(location=2) out vec3 fragment_position; layout(location=3) out vec3 fragment_normal_occlusion; @@ -163,16 +163,16 @@ function output_buffers(transparency = false) end end -function output_buffer_writes(transparency = false) +function output_buffer_writes(screen::Screen, transparency = false) if transparency - scale = transparency_weight_scale[] + scale = screen.config.transparency_weight_scale """ float weight = color.a * max(0.01, $scale * pow((1 - gl_FragCoord.z), 3)); coverage = 1.0 - clamp(color.a, 0.0, 1.0); fragment_color.rgb = weight * color.rgb; fragment_color.a = weight; """ - elseif enable_SSAO[] + elseif screen.config.ssao """ fragment_color = color; fragment_position = o_view_pos; diff --git a/GLMakie/src/rendering.jl b/GLMakie/src/rendering.jl index ecbbf64e416..b2301eac24d 100644 --- a/GLMakie/src/rendering.jl +++ b/GLMakie/src/rendering.jl @@ -130,7 +130,7 @@ function id2scene(screen, id1) return false, nothing end -function GLAbstraction.render(filter_elem_func, screen::GLScreen) +function GLAbstraction.render(filter_elem_func, screen::Screen) # Somehow errors in here get ignored silently!? try for (zindex, screenid, elem) in screen.renderlist diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 0ace5304e4b..f20b97db2fe 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -28,9 +28,9 @@ function renderloop end ssao::Bool = true) """ -struct ScreenConfig +mutable struct ScreenConfig # Renderloop - renderloop::Union{Makie.Automatic, Function}# = automatic, + renderloop::Function # GLMakie.renderloop, pause_renderloop::Bool vsync::Bool# = false, framerate::Float64# = 30.0, @@ -48,8 +48,51 @@ struct ScreenConfig oit::Bool# = true, fxaa::Bool# = true, ssao::Bool# = true + transparency_weight_scale::Float32 + + function ScreenConfig( + # Renderloop + renderloop::Union{Makie.Automatic, Function}, + pause_renderloop::Bool, + vsync::Bool, + framerate::Number, + # GLFW window attributes + float::Bool, + focus_on_show::Bool, + decorated::Bool, + title::AbstractString, + fullscreen::Bool, + debugging::Bool, + monitor::Union{Nothing, GLFW.Monitor}, + # Preproccessor + oit::Bool, + fxaa::Bool, + ssao::Bool, + transparency_weight_scale::Number) + + return new( + # Renderloop + renderloop isa Makie.Automatic ? GLMakie.renderloop : renderloop, + pause_renderloop, + vsync, + framerate, + # GLFW window attributes + float, + focus_on_show, + decorated, + title, + fullscreen, + debugging, + monitor, + # Preproccessor + oit, + fxaa, + ssao, + transparency_weight_scale) + end end + """ activate!(; renderloop = renderloop, @@ -74,18 +117,12 @@ function activate!(; screen_config...) return end -abstract type GLScreen <: MakieScreen end - -mutable struct Screen{GLWindow} <: GLScreen +mutable struct Screen{GLWindow} <: MakieScreen glscreen::GLWindow shader_cache::GLAbstraction.ShaderCache framebuffer::GLFramebuffer - - renderloop::Function + config::ScreenConfig stop_renderloop::Bool - pause_renderloop::Bool - vsync::Bool - framerate::Float64 rendertask::RefValue{Task} screen2scene::Dict{WeakRef, ScreenID} @@ -102,12 +139,8 @@ mutable struct Screen{GLWindow} <: GLScreen glscreen::GLWindow, shader_cache::GLAbstraction.ShaderCache, framebuffer::GLFramebuffer, - - renderloop::Function, + config::ScreenConfig, stop_renderloop::Bool, - pause_renderloop::Bool, - vsync::Bool, - framerate::Float64, rendertask::RefValue{Task}, screen2scene::Dict{WeakRef, ScreenID}, @@ -117,11 +150,11 @@ mutable struct Screen{GLWindow} <: GLScreen cache::Dict{UInt64, RenderObject}, cache2plot::Dict{UInt32, AbstractPlot}, ) where {GLWindow} + s = size(framebuffer) return new{GLWindow}( glscreen, shader_cache, framebuffer, - renderloop, stop_renderloop, pause_renderloop, vsync, framerate, - rendertask, + config, stop_renderloop, rendertask, screen2scene, screens, renderlist, postprocessors, cache, cache2plot, Matrix{RGB{N0f8}}(undef, s), Observable(nothing), @@ -130,7 +163,106 @@ mutable struct Screen{GLWindow} <: GLScreen end end -pollevents(::GLScreen) = nothing +function Screen(; + resolution = (10, 10), + visible = true, + start_renderloop = true, + screen_config... + ) + # Screen config is managed by the current active theme, so managed by Makie + config = Makie.merge_screen_config(ScreenConfig, screen_config) + + # Somehow this constant isn't wrapped by glfw + GLFW_FOCUS_ON_SHOW = 0x0002000C + + windowhints = [ + (GLFW.SAMPLES, 0), + (GLFW.DEPTH_BITS, 0), + + # SETTING THE ALPHA BIT IS REALLY IMPORTANT ON OSX, SINCE IT WILL JUST KEEP SHOWING A BLACK SCREEN + # WITHOUT ANY ERROR -.- + (GLFW.ALPHA_BITS, 8), + (GLFW.RED_BITS, 8), + (GLFW.GREEN_BITS, 8), + (GLFW.BLUE_BITS, 8), + + (GLFW.STENCIL_BITS, 0), + (GLFW.AUX_BUFFERS, 0), + (GLFW_FOCUS_ON_SHOW, config.focus_on_show), + (GLFW.DECORATED, config.decorated), + (GLFW.FLOATING, config.float), + # (GLFW.TRANSPARENT_FRAMEBUFFER, true) + ] + + window = try + GLFW.Window( + resolution = resolution, + windowhints = windowhints, + visible = false, + # from config + name = config.title, + focus = config.focus_on_show, + fullscreen = config.fullscreen, + debugging = config.debugging, + monitor = config.monitor + ) + catch e + @warn(""" + GLFW couldn't create an OpenGL window. + This likely means, you don't have an OpenGL capable Graphic Card, + or you don't have an OpenGL 3.3 capable video driver installed. + Have a look at the troubleshooting section in the GLMakie readme: + https://github.com/MakieOrg/Makie.jl/tree/master/GLMakie#troubleshooting-opengl. + """) + rethrow(e) + end + + GLFW.SetWindowIcon(window, Makie.icon()) + + # tell GLAbstraction that we created a new context. + # This is important for resource tracking, and only needed for the first context + ShaderAbstractions.switch_context!(window) + shader_cache = GLAbstraction.ShaderCache(window) + push!(GLFW_WINDOWS, window) + + resize_native!(window, resolution...) + + fb = GLFramebuffer(resolution) + postprocessors = [ + config.ssao ? ssao_postprocessor(fb, shader_cache) : empty_postprocessor(), + config.oit ? OIT_postprocessor(fb, shader_cache) : empty_postprocessor(), + config.fxaa ? fxaa_postprocessor(fb, shader_cache) : empty_postprocessor(), + to_screen_postprocessor(fb, shader_cache) + ] + + screen = Screen( + window, shader_cache, fb, + config, !start_renderloop, + RefValue{Task}(), + Dict{WeakRef, ScreenID}(), + ScreenArea[], + Tuple{ZIndex, ScreenID, RenderObject}[], + postprocessors, + Dict{UInt64, RenderObject}(), + Dict{UInt32, AbstractPlot}(), + ) + + GLFW.SetWindowRefreshCallback(window, window -> refreshwindowcb(window, screen)) + + if start_renderloop + start_renderloop!(screen) + end + + # display window if visible! + if visible + GLFW.ShowWindow(window) + else + GLFW.HideWindow(window) + end + + return screen +end + function pollevents(screen::Screen) ShaderAbstractions.switch_context!(screen.glscreen) notify(screen.render_tick) @@ -145,7 +277,7 @@ Base.show(io::IO, screen::Screen) = print(io, "GLMakie.Screen(...)") Base.isopen(x::Screen) = isopen(x.glscreen) Base.size(x::Screen) = size(x.framebuffer) -function Makie.insertplots!(screen::GLScreen, scene::Scene) +function Makie.insertplots!(screen::Screen, scene::Scene) ShaderAbstractions.switch_context!(screen.glscreen) get!(screen.screen2scene, WeakRef(scene)) do id = length(screen.screens) + 1 @@ -368,7 +500,7 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma end end -function Base.push!(screen::GLScreen, scene::Scene, robj) +function Base.push!(screen::Screen, scene::Scene, robj) # filter out gc'ed elements filter!(screen.screen2scene) do (k, v) k.value !== nothing @@ -427,123 +559,17 @@ function display_loading_image(screen::Screen) end end -function Screen(; - resolution = (10, 10), - visible = true, - start_renderloop = true, - screen_config... - ) - # Screen config is managed by the current active theme, so managed by Makie - config = Makie.merge_screen_config(ScreenConfig, screen_config) - - # Somehow this constant isn't wrapped by glfw - GLFW_FOCUS_ON_SHOW = 0x0002000C - - windowhints = [ - (GLFW.SAMPLES, 0), - (GLFW.DEPTH_BITS, 0), - - # SETTING THE ALPHA BIT IS REALLY IMPORTANT ON OSX, SINCE IT WILL JUST KEEP SHOWING A BLACK SCREEN - # WITHOUT ANY ERROR -.- - (GLFW.ALPHA_BITS, 8), - (GLFW.RED_BITS, 8), - (GLFW.GREEN_BITS, 8), - (GLFW.BLUE_BITS, 8), - - (GLFW.STENCIL_BITS, 0), - (GLFW.AUX_BUFFERS, 0), - (GLFW_FOCUS_ON_SHOW, config.focus_on_show), - (GLFW.DECORATED, config.decorated), - (GLFW.FLOATING, config.float), - # (GLFW.TRANSPARENT_FRAMEBUFFER, true) - ] - - window = try - GLFW.Window( - resolution = resolution, - windowhints = windowhints, - visible = false, - # from config - name = config.title, - focus = config.focus_on_show, - fullscreen = config.fullscreen, - debugging = config.debugging, - monitor = config.monitor - ) - catch e - @warn(""" - GLFW couldn't create an OpenGL window. - This likely means, you don't have an OpenGL capable Graphic Card, - or you don't have an OpenGL 3.3 capable video driver installed. - Have a look at the troubleshooting section in the GLMakie readme: - https://github.com/MakieOrg/Makie.jl/tree/master/GLMakie#troubleshooting-opengl. - """) - rethrow(e) - end - - GLFW.SetWindowIcon(window, Makie.icon()) - - # tell GLAbstraction that we created a new context. - # This is important for resource tracking, and only needed for the first context - ShaderAbstractions.switch_context!(window) - shader_cache = GLAbstraction.ShaderCache(window) - push!(GLFW_WINDOWS, window) - - resize_native!(window, resolution...) - - fb = GLFramebuffer(resolution) - postprocessors = [ - config.ssao ? ssao_postprocessor(fb, shader_cache) : empty_postprocessor(), - config.oit ? OIT_postprocessor(fb, shader_cache) : empty_postprocessor(), - config.fxaa ? fxaa_postprocessor(fb, shader_cache) : empty_postprocessor(), - to_screen_postprocessor(fb, shader_cache) - ] - - screen = Screen( - window, shader_cache, fb, - - config.renderloop isa Makie.Automatic ? renderloop : config.renderloop, - !start_renderloop, - config.pause_renderloop, - config.vsync, - config.framerate, - RefValue{Task}(), - - Dict{WeakRef, ScreenID}(), - ScreenArea[], - Tuple{ZIndex, ScreenID, RenderObject}[], - postprocessors, - Dict{UInt64, RenderObject}(), - Dict{UInt32, AbstractPlot}(), - ) - - GLFW.SetWindowRefreshCallback(window, window -> refreshwindowcb(window, screen)) - - if start_renderloop - start_renderloop!(screen) - end - - # display window if visible! - if visible - GLFW.ShowWindow(window) - else - GLFW.HideWindow(window) - end - - return screen -end - function renderloop_running(screen::Screen) return !screen.stop_renderloop && isassigned(screen.rendertask) && !istaskdone(screen.rendertask[]) end function start_renderloop!(screen::Screen) if renderloop_running(screen) - screen.pause_renderloop = false + screen.config.pause_renderloop = false return else screen.stop_renderloop = false - task = @async screen.renderloop(screen) + task = @async screen.config.renderloop(screen) yield() if istaskstarted(task) screen.rendertask[] = task @@ -556,7 +582,7 @@ function start_renderloop!(screen::Screen) end function pause_renderloop!(screen::Screen) - screen.pause_renderloop = true + screen.config.pause_renderloop = true end function stop_renderloop!(screen::Screen) @@ -565,7 +591,7 @@ function stop_renderloop!(screen::Screen) end function set_framerate!(screen::Screen, fps=30) - screen.framerate = fps + screen.config.framerate = fps end function refreshwindowcb(window, screen) @@ -591,7 +617,7 @@ end # TODO add render_tick event to scene events function vsynced_renderloop(screen) while isopen(screen) && !screen.stop_renderloop - if screen.pause_renderloop + if screen.config.pause_renderloop pollevents(screen); sleep(0.1) continue end @@ -604,11 +630,11 @@ end function fps_renderloop(screen::Screen) while isopen(screen) && !screen.stop_renderloop - if screen.pause_renderloop + if screen.config.pause_renderloop pollevents(screen); sleep(0.1) continue end - time_per_frame = 1.0 / screen.framerate + time_per_frame = 1.0 / screen.config.framerate t = time_ns() pollevents(screen) # GLFW poll render_frame(screen) @@ -626,7 +652,7 @@ end function renderloop(screen) isopen(screen) || error("Screen most be open to run renderloop!") try - if screen.vsync + if screen.config.vsync GLFW.SwapInterval(1) vsynced_renderloop(screen) else diff --git a/src/theming.jl b/src/theming.jl index 73165bd34d1..3c2c421a891 100644 --- a/src/theming.jl +++ b/src/theming.jl @@ -121,7 +121,11 @@ const minimal_default = Attributes( # Preproccessor oit = true, fxaa = true, - ssao = false + ssao = false, + # This adjusts a factor in the rendering shaders for order independent + # transparency. This should be the same for all of them (within one rendering + # pipeline) otherwise depth "order" will be broken. + transparency_weight_scale = 1000f0 ), WGLMakie = Attributes( From 3593f61da220294ad58140aa8459dedddcc07e39 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 7 Oct 2022 14:52:47 +0200 Subject: [PATCH 44/56] small improvements for Pluto + friends --- GLMakie/src/screen.jl | 1 + src/display.jl | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index f20b97db2fe..deb3d09ae32 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -111,6 +111,7 @@ function activate!(; screen_config...) if haskey(screen_config, :pause_rendering) error("pause_rendering got renamed to pause_renderloop.") end + Makie.set_preferred_mime!() # reset mime Makie.set_screen_config!(GLMakie, screen_config) Makie.set_active_backend!(GLMakie) Makie.set_glyph_resolution!(Makie.High) diff --git a/src/display.jl b/src/display.jl index ceb262a2d4e..f90f0077bd6 100644 --- a/src/display.jl +++ b/src/display.jl @@ -83,8 +83,6 @@ function merge_screen_config(::Type{Config}, screen_config_kw) where Config end """ - - # GLMakie start_renderloop=true visible=true @@ -95,9 +93,8 @@ pt_per_unit=x.pt_per_unit px_per_unit=x.px_per_unit antialias=x.antialias """ -function Base.display(figlike::FigureLike; screen_config...) - Backend = current_backend() - if ismissing(Backend) +function Base.display(figlike::FigureLike; backend=current_backend(), screen_config...) + if ismissing(backend) error(""" No backend available! Make sure to also `import/using` a backend (GLMakie, CairoMakie, WGLMakie). @@ -106,7 +103,7 @@ function Base.display(figlike::FigureLike; screen_config...) In that case, try `]build GLMakie` and watch out for any warnings. """) end - screen = Backend.Screen(get_scene(figlike); screen_config...) + screen = backend.Screen(get_scene(figlike); screen_config...) return display(screen, figlike) end @@ -191,9 +188,15 @@ function Base.show(io::IO, ::MIME"text/plain", scene::Scene) end function Base.show(io::IO, m::MIME, figlike::FigureLike) - update_state_before_display!(figlike) scene = get_scene(figlike) - backend_show(current_backend().Screen(scene, io, m), io, m, scene) + backend = current_backend() + # get current screen the scene is already displayed on, or create a new screen + screen = getscreen(scene, backend) do + # only update fig if not already displayed + update_state_before_display!(figlike) + return backend.Screen(scene, io, m) + end + backend_show(screen, io, m, scene) return end From adabcabb2be059bad38aea25ef6445685c30775c Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 7 Oct 2022 16:24:26 +0200 Subject: [PATCH 45/56] add comment --- src/display.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/display.jl b/src/display.jl index f90f0077bd6..9e79271c814 100644 --- a/src/display.jl +++ b/src/display.jl @@ -107,6 +107,9 @@ function Base.display(figlike::FigureLike; backend=current_backend(), screen_con return display(screen, figlike) end +# Backends overload display(::Backend.Screen, scene::Scene), while Makie overloads the below, +# so that they don't need to worry +# about stuff like `update_state_before_display!` function Base.display(screen::MakieScreen, figlike::FigureLike; display_attributes...) update_state_before_display!(figlike) scene = get_scene(figlike) From 10a3c589f880934247abff4bc864580ee44300e0 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Mon, 10 Oct 2022 16:18:07 +0200 Subject: [PATCH 46/56] clean up docs --- CairoMakie/src/screen.jl | 180 +++++++++++++++++------------ GLMakie/src/screen.jl | 85 ++++++++------ MakieCore/src/types.jl | 10 +- RPRMakie/src/RPRMakie.jl | 30 ++--- RPRMakie/src/scene.jl | 12 ++ WGLMakie/src/WGLMakie.jl | 9 +- WGLMakie/src/display.jl | 19 +++ WGLMakie/src/three_plot.jl | 5 - src/Makie.jl | 2 +- src/deprecated.jl | 16 ++- src/display.jl | 17 ++- src/documentation/documentation.jl | 2 +- src/ffmpeg-util.jl | 68 ++++++----- src/recording.jl | 43 ++++--- 14 files changed, 293 insertions(+), 205 deletions(-) diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 26b5f7ee5a0..2b8ae36243e 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -1,68 +1,6 @@ -@enum RenderType SVG IMAGE PDF EPS - -struct ScreenConfig - type::RenderType # = IMAGE, - px_per_unit::Float64 # = 1.0, - pt_per_unit::Float64 # = 0.75, - antialias::Symbol # = Cairo.ANTIALIAS_BEST - visible::Bool - start_renderloop::Bool -end - -function device_scaling_factor(sc::ScreenConfig) - if is_vector_backend(sc.type) - return sc.pt_per_unit - else - return sc.px_per_unit - end -end - -function activate!(; type="png", screen_config...) - Makie.set_preferred_mime!(to_mime(convert(RenderType, type))) - Makie.set_screen_config!(CairoMakie, screen_config) - Makie.set_active_backend!(CairoMakie) - return -end +using Base.Docs: doc -""" - struct Screen{S} <: MakieScreen -A "screen" type for CairoMakie, which encodes a surface -and a context which are used to draw a Scene. -""" -struct Screen{SurfaceRenderType} <: Makie.MakieScreen - scene::Scene - surface::Cairo.CairoSurface - context::Cairo.CairoContext - device_scaling_factor::Float64 - antialias::Int # cairo_antialias_t - visible::Bool -end - -function Base.empty!(screen::Screen) - ctx = screen.context - Cairo.save(ctx) - bg = rgbatuple(screen.scene.backgroundcolor[]) - Cairo.set_source_rgba(ctx, bg...) - Cairo.set_operator(ctx, Cairo.OPERATOR_CLEAR) - Cairo.rectangle(ctx, 0, 0, size(screen)...) - Cairo.paint_with_alpha(ctx, 1.0) - Cairo.restore(ctx) -end - -Base.size(screen::Screen) = round.(Int, (screen.surface.width, screen.surface.height)) -# we render the scene directly, since we have -# no screen dependent state like in e.g. opengl -Base.insert!(screen::Screen, scene::Scene, plot) = nothing -function Base.delete!(screen::Screen, scene::Scene, plot::AbstractPlot) - # Currently, we rerender every time, so nothing needs - # to happen here. However, in the event that changes, - # e.g. if we integrate a Gtk window, we may need to - # do something here. -end - -function Base.show(io::IO, ::MIME"text/plain", screen::Screen{S}) where S - println(io, "CairoMakie.Screen{$S}") -end +@enum RenderType SVG IMAGE PDF EPS function Base.convert(::Type{RenderType}, type::String) if type == "png" @@ -78,11 +16,6 @@ function Base.convert(::Type{RenderType}, type::String) end end -function path_to_type(path) - type = splitext(path)[2][2:end] - return convert(RenderType, type) -end - "Convert a rendering type to a MIME type" function to_mime(type::RenderType) type == SVG && return MIME("image/svg+xml") @@ -90,7 +23,6 @@ function to_mime(type::RenderType) type == EPS && return MIME("application/postscript") return MIME("image/png") end -to_mime(screen::Screen) = to_mime(screen.typ) "convert a mime to a RenderType" function mime_to_rendertype(mime::Symbol)::RenderType @@ -130,10 +62,9 @@ function surface_from_output_type(type::RenderType, io, w, h) end end -######################################## -# Constructor # -######################################## - +""" +Supported options: `[:best => Cairo.ANTIALIAS_BEST, :good => Cairo.ANTIALIAS_GOOD, :subpixel => Cairo.ANTIALIAS_SUBPIXEL, :none => Cairo.ANTIALIAS_NONE]` +""" function to_cairo_antialias(sym::Symbol) sym == :best && return Cairo.ANTIALIAS_BEST sym == :good && return Cairo.ANTIALIAS_GOOD @@ -143,12 +74,107 @@ function to_cairo_antialias(sym::Symbol) end to_cairo_antialias(aa::Int) = aa -# Default to ARGB Surface as backing device """ - Screen(scene::Scene; screen_config...) +* `px_per_unit = 1.0`: see [figure size docs](https://docs.makie.org/v0.17.13/documentation/figure_size/index.html). +* `pt_per_unit = 0.75`: see [figure size docs](https://docs.makie.org/v0.17.13/documentation/figure_size/index.html). +* `antialias::Union{Symbol, Int} = :best`: antialias modus Cairo uses to draw. Applicable options: `[:best => Cairo.ANTIALIAS_BEST, :good => Cairo.ANTIALIAS_GOOD, :subpixel => Cairo.ANTIALIAS_SUBPIXEL, :none => Cairo.ANTIALIAS_NONE]`. +* `visible::Bool`: if true, a browser/image viewer will open to display rendered output. +""" +struct ScreenConfig + type::RenderType + px_per_unit::Float64 + pt_per_unit::Float64 + antialias::Symbol + visible::Bool + start_renderloop::Bool + function ScreenConfig(type, px_per_unit::Number, pt_per_unit::Number, anitalias::Symbol, visible::Bool, start_renderloop::Bool) + return new(convert(RenderType, type), px_per_unit, pt_per_unit, anitalias, visible, start_renderloop) + end +end + +function device_scaling_factor(sc::ScreenConfig) + if is_vector_backend(sc.type) + return sc.pt_per_unit + else + return sc.px_per_unit + end +end + +""" + CairoMakie.activate!(; screen_config...) + +Sets CairoMakie as the currently active backend and also allows to quickly set the `screen_config`. +Note, that the `screen_config` can also be set via permanently via `Makie.set_theme!(CairoMakie=(screen_config...,))`. + +# Arguments one can pass via `screen_config`: + +$(Base.doc(ScreenConfig)) +""" +function activate!(; screen_config...) + config = Makie.set_screen_config!(CairoMakie, screen_config) + Makie.set_preferred_mime!(to_mime(convert(RenderType, config.type[]))) + Makie.set_active_backend!(CairoMakie) + return +end + +""" + Screen(; screen_config...) + +# Arguments one can pass via `screen_config`: + +$(Base.doc(ScreenConfig)) -Create a Screen backed by an image surface. +# Constructors: + +$(Base.doc(MakieScreen)) """ +struct Screen{SurfaceRenderType} <: Makie.MakieScreen + scene::Scene + surface::Cairo.CairoSurface + context::Cairo.CairoContext + device_scaling_factor::Float64 + antialias::Int # cairo_antialias_t + visible::Bool +end + +function Base.empty!(screen::Screen) + ctx = screen.context + Cairo.save(ctx) + bg = rgbatuple(screen.scene.backgroundcolor[]) + Cairo.set_source_rgba(ctx, bg...) + Cairo.set_operator(ctx, Cairo.OPERATOR_CLEAR) + Cairo.rectangle(ctx, 0, 0, size(screen)...) + Cairo.paint_with_alpha(ctx, 1.0) + Cairo.restore(ctx) +end + +Base.size(screen::Screen) = round.(Int, (screen.surface.width, screen.surface.height)) +# we render the scene directly, since we have +# no screen dependent state like in e.g. opengl +Base.insert!(screen::Screen, scene::Scene, plot) = nothing +function Base.delete!(screen::Screen, scene::Scene, plot::AbstractPlot) + # Currently, we rerender every time, so nothing needs + # to happen here. However, in the event that changes, + # e.g. if we integrate a Gtk window, we may need to + # do something here. +end + +function Base.show(io::IO, ::MIME"text/plain", screen::Screen{S}) where S + println(io, "CairoMakie.Screen{$S}") +end + + +function path_to_type(path) + type = splitext(path)[2][2:end] + return convert(RenderType, type) +end +to_mime(screen::Screen) = to_mime(screen.typ) + + +######################################## +# Constructor # +######################################## + Screen(scene::Scene; screen_config...) = Screen(scene, nothing, IMAGE; screen_config...) function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::Union{MIME, Symbol, RenderType}; screen_config...) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index deb3d09ae32..abef49f266c 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -6,26 +6,32 @@ const ScreenArea = Tuple{ScreenID, Scene} function renderloop end """ - ScreenConfig( - # Renderloop - renderloop::Union{Makie.Automatic, Function} = automatic, - pause_renderloop::Bool = false, - vsync::Bool = false, - framerate::Float64 = 30.0, - - # GLFW window attributes - float::Bool = false, - focus_on_show::Bool = false, - decorated::Bool = true, - title::String = "Makie", - fullscreen::Bool = false, - debugging::Bool = false, - monitor::Union{Nothing, GLFW.Monitor} = nothing, - - # Preproccessor - oit::Bool = true, - fxaa::Bool = true, - ssao::Bool = true) +## Renderloop + +* `renderloop = GLMakie.renderloop`: sets a function `renderloop(::GLMakie.Screen)` which starts a renderloop for the screen. + + + !!! warning + The below are not effective if renderloop isn't set to `GLMakie.renderloop`, unless implemented in custom renderloop: + + +* `pause_renderloop = false`: creates a screen with paused renderlooop. Can be started with `GLMakie.start_renderloop!(screen)` or paused again with `GLMakie.pause_renderloop!(screen)`. +* `vsync = false`: enables vsync for the window. +* `framerate = 30.0`: sets the currently rendered frames per second. + +## GLFW window attributes +* `float = false`: Lets the opened window float above anything else. +* `focus_on_show = false`: Focusses the window when newly opened. +* `decorated = true`: shows the window decorations or not. +* `title::String = "Makie"`: Sets the window title. +* `fullscreen = false`: Starts the window in fullscreen. +* `debugging = false`: Starts the GLFW.Window/OpenGL context with debug output. +* `monitor::Union{Nothing, GLFW.Monitor} = nothing`: Sets the monitor on which the Window should be opened. + +## Preproccessor +* `oit = true`: Enles order independent transparency for the window. +* `fxaa = true`: Enables fxaa (anti-aliasing) for the window. +* `ssao = true`: Enables screen space occlusion, which gives 3D meshes a soft shadow towards their edges. """ mutable struct ScreenConfig @@ -92,20 +98,15 @@ mutable struct ScreenConfig end end - """ - activate!(; - renderloop = renderloop, - vsync = false, - framerate = 30.0, - float = false, - pause_rendering = false, - focus_on_show = false, - decorated = true, - title = "Makie" - ) -Updates the screen configuration, will only go into effect after closing the current -window and opening a new one! + GLMakie.activate!(; screen_config...) + +Sets GLMakie as the currently active backend and also allows to quickly set the `screen_config`. +Note, that the `screen_config` can also be set via permanently via `Makie.set_theme!(GLMakie=(screen_config...,))`. + +# Arguments one can pass via `screen_config`: + +$(Base.doc(ScreenConfig)) """ function activate!(; screen_config...) if haskey(screen_config, :pause_rendering) @@ -118,6 +119,18 @@ function activate!(; screen_config...) return end + +""" + Screen(; screen_config...) + +# Arguments one can pass via `screen_config`: + +$(Base.doc(ScreenConfig)) + +# Constructors: + +$(Base.doc(MakieScreen)) +""" mutable struct Screen{GLWindow} <: MakieScreen glscreen::GLWindow shader_cache::GLAbstraction.ShaderCache @@ -603,15 +616,15 @@ function refreshwindowcb(window, screen) end # Open an interactive window -Screen(scene::Scene; screen_attributes...) = singleton_screen(size(scene); visible=true, start_renderloop=true) +Screen(scene::Scene; screen_config...) = singleton_screen(size(scene); visible=true, start_renderloop=true) # Screen to save a png/jpeg to file or io -function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::MIME; screen_attributes...) +function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::MIME; screen_config...) return singleton_screen(size(scene); visible=false, start_renderloop=false) end # Screen that is efficient for `colorbuffer(screen)` -function Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_attributes...) +function Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_config...) return singleton_screen(size(scene); visible=false, start_renderloop=false) end diff --git a/MakieCore/src/types.jl b/MakieCore/src/types.jl index 6890349c36c..3a633fcb09d 100644 --- a/MakieCore/src/types.jl +++ b/MakieCore/src/types.jl @@ -11,20 +11,20 @@ abstract type AbstractScene <: Transformable end abstract type ScenePlot{Typ} <: AbstractPlot{Typ} end """ -Constructors: +Screen constructors implemented by all backends: ```julia # Constructor aimed at showing the plot in a window. -MakieScreen(scene::Scene; screen_attributes...) +Screen(scene::Scene; screen_config...) # Screen to save a png/jpeg to file or io -MakieScreen(scene::Scene, io::IO, mime; screen_attributes...) +Screen(scene::Scene, io::IO, mime; screen_config...) # Screen that is efficient for `colorbuffer(screen, format)` -MakieScreen(scene::Scene, format::Makie.ImageStorageFormat; screen_attributes...) +Screen(scene::Scene, format::Makie.ImageStorageFormat; screen_config...) ``` -Interface: +Interface implemented by all backends: ```julia # Needs to be overload: diff --git a/RPRMakie/src/RPRMakie.jl b/RPRMakie/src/RPRMakie.jl index 53efa7eb765..1dc79f75aee 100644 --- a/RPRMakie/src/RPRMakie.jl +++ b/RPRMakie/src/RPRMakie.jl @@ -5,10 +5,19 @@ using RadeonProRender using GeometryBasics using Colors using FileIO +using Makie: colorbuffer const RPR = RadeonProRender -using Makie: colorbuffer +""" +* `iterations = 200`: Iterations of light simulations. The more iterations, the less noisy the picture becomes, but higher numbers take much longer. For e.g. `iterations=10` one should expect rendering to take a couple of seconds but it will produce pretty noisy output. 200 is a good middle ground taking around ~30s on an old nvidia 1060. For highest quality output, numbers above 500 are required. +* `resource = RPR.RPR_CREATION_FLAGS_ENABLE_GPU0`: GPU or CPU to use. Multiple GPUs and CPUs can be used together by using `&` (e.g. `RPR.RPR_CREATION_FLAGS_ENABLE_GPU0 & RPR.RPR_CREATION_FLAGS_ENABLE_CPU`). +* `plugin = RPR.Tahoe`: + * `RPR.Tahoe`, the legacy RadeonProRender backend. It's the most stable, but doesn't have all new features (e.g. the `RPR.MatX` material), and may be slower than others + * `RPR.Northstar`, the new rewritten backend, faster and optimized for many iterations. Single iterations are much slower, so less usable for interactive display. Sometimes, Northstar just produces black, jiggly objects. It's not clear yet, if that's just a bug, or the result of using an unsupported/deprecated feature. Switch to `Tahoe` if that happens. + * `RPR.Hybrid`: Vulkan backend, fit for real time rendering, using AMDs and NVIDIAs new hardware accelerated ray tracing. Doesn't work reliably yet and only works with `RPR.Uber` material. + * `RPR.HybridPro`: The same as Hybrid, but works only for Radeon GPUs, using AMDs own hardware acceleration API. +""" struct ScreenConfig iterations::Int max_recursion::Int @@ -25,26 +34,21 @@ function ScreenConfig(iterations::Int, max_recursion::Int, render_resource, rend ) end - include("scene.jl") include("lines.jl") include("meshes.jl") include("volume.jl") """ - RPRMakie.activate!(; - iterations=200, - resource=RPR.RPR_CREATION_FLAGS_ENABLE_GPU0, - plugin=RPR.Tahoe) + RPRMakie.activate!(; screen_config...) -- iterations: Iterations of light simulations. The more iterations, the less noisy the picture becomes, but higher numbers take much longer. For e.g. `iterations=10` one should expect rendering to take a couple of seconds but it will produce pretty noisy output. 200 is a good middle ground taking around ~30s on an old nvidia 1060. For highest quality output, numbers above 500 are required. -- resource: GPU or CPU to use. Multiple GPUs and CPUs can be used together by using `&` (e.g. `RPR.RPR_CREATION_FLAGS_ENABLE_GPU0 & RPR.RPR_CREATION_FLAGS_ENABLE_CPU`) -- plugin: - * `RPR.Tahoe`, the legacy RadeonProRender backend. It's the most stable, but doesn't have all new features (e.g. the `RPR.MatX` material), and may be slower than others - * `RPR.Northstar`, the new rewritten backend, faster and optimized for many iterations. Single iterations are much slower, so less usable for interactive display. Sometimes, Northstar just produces black, jiggly objects. It's not clear yet, if that's just a bug, or the result of using an unsupported/deprecated feature. Switch to `Tahoe` if that happens. - * `RPR.Hybrid`: Vulkan backend, fit for real time rendering, using AMDs and NVIDIAs new hardware accelerated ray tracing. Doesn't work reliably yet and only works with `RPR.Uber` material. - * `RPR.HybridPro`: The same as Hybrid, but works only for Radeon GPUs, using AMDs own hardware acceleration API. +Sets RPRMakie as the currently active backend and also allows to quickly set the `screen_config`. +Note, that the `screen_config` can also be set via permanently via `Makie.set_theme!(RPRMakie=(screen_config...,))`. + +# Arguments one can pass via `screen_config`: + +$(Base.doc(ScreenConfig)) """ function activate!(; screen_config...) Makie.set_screen_config!(RPRMakie, screen_config) diff --git a/RPRMakie/src/scene.jl b/RPRMakie/src/scene.jl index 9e3cdce87fd..81d02e0065b 100644 --- a/RPRMakie/src/scene.jl +++ b/RPRMakie/src/scene.jl @@ -140,6 +140,18 @@ function replace_scene_rpr!(scene::Makie.Scene, screen=Screen(scene); refresh=Ob return context, task, rpr_scene end + +""" + Screen(args...; screen_config...) + +# Arguments one can pass via `screen_config`: + +$(Base.doc(ScreenConfig)) + +# Constructors: + +$(Base.doc(MakieScreen)) +""" mutable struct Screen <: Makie.MakieScreen context::RPR.Context matsys::RPR.MaterialSystem diff --git a/WGLMakie/src/WGLMakie.jl b/WGLMakie/src/WGLMakie.jl index ff384252199..3a6e1649b23 100644 --- a/WGLMakie/src/WGLMakie.jl +++ b/WGLMakie/src/WGLMakie.jl @@ -45,9 +45,14 @@ include("display.jl") """ - activate!(; fps=30) + WGLMakie.activate!(; screen_config...) -Set fps (frames per second) to a higher number for smoother animations, or to a lower to use less resources. +Sets WGLMakie as the currently active backend and also allows to quickly set the `screen_config`. +Note, that the `screen_config` can also be set via permanently via `Makie.set_theme!(WGLMakie=(screen_config...,))`. + +# Arguments one can pass via `screen_config`: + +$(Base.doc(ScreenConfig)) """ function activate!(; screen_config...) Makie.set_active_backend!(WGLMakie) diff --git a/WGLMakie/src/display.jl b/WGLMakie/src/display.jl index 65f5b776376..a09bd8df271 100644 --- a/WGLMakie/src/display.jl +++ b/WGLMakie/src/display.jl @@ -16,6 +16,25 @@ const WEB_MIMES = ( MIME"application/prs.juno.plotpane+html", MIME"juliavscode/html") + +""" +* `framerate = 30`: Set framerate (frames per second) to a higher number for smoother animations, or to a lower to use less resources. +""" +struct ScreenConfig + framerate::Float64 # =30.0 +end + +""" + Screen(args...; screen_config...) + +# Arguments one can pass via `screen_config`: + +$(Base.doc(ScreenConfig)) + +# Constructors: + +$(Base.doc(MakieScreen)) +""" mutable struct Screen <: Makie.MakieScreen three::Union{Nothing, ThreeDisplay} display::Any diff --git a/WGLMakie/src/three_plot.jl b/WGLMakie/src/three_plot.jl index 1bc424a99d5..14240ed0913 100644 --- a/WGLMakie/src/three_plot.jl +++ b/WGLMakie/src/three_plot.jl @@ -52,16 +52,11 @@ function find_plots(session::Session, plot::AbstractPlot) return WGL.find_plots(session, uuids) end - function JSServe.print_js_code(io::IO, plot::AbstractPlot, context) uuids = js_uuid.(Makie.flatten_plots(plot)) JSServe.print_js_code(io, js"$(WGL).find_plots($(uuids))", context) end -struct ScreenConfig - framerate::Float64 # =30.0 -end - function three_display(session::Session, scene::Scene; screen_config...) config = Makie.merge_screen_config(ScreenConfig, screen_config)::ScreenConfig diff --git a/src/Makie.jl b/src/Makie.jl index 4688631ee5b..8b4aa31a17e 100644 --- a/src/Makie.jl +++ b/src/Makie.jl @@ -264,7 +264,7 @@ export abline! # until deprecation removal export Stepper, replay_events, record_events, RecordEvents, record, VideoStream -export VideoStream, recordframe!, record +export VideoStream, recordframe!, record, Record export save # colormap stuff from PlotUtils, and showgradients diff --git a/src/deprecated.jl b/src/deprecated.jl index f026ba713f3..9c267e49c6d 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -1,11 +1,15 @@ -function inline!() - +function inline!(val=true) + @warn("inline!(false/true) has been deprecated, since it was never working properly. + Use `display([Backend.Screen()], figure)` to open a window, + and otherwise any plot on any backend should be inlined into the plotpane/output by default.") end -function register_backend!() - +function register_backend!(backend) + @warn("`register_backend!` is an internal deprecated function, which shouldn't be used outside Makie. + if you must really use this function, it's now `set_active_backend!(::Module)") end -function backend_display() - +function backend_display(args...) + @warn("`backend_display` is an internal deprecated function, which shouldn't be used outside Makie. + if you must really use this function, it's now just `display(::Backend.Screen, figlike)`") end diff --git a/src/display.jl b/src/display.jl index 9e79271c814..947045cb1fe 100644 --- a/src/display.jl +++ b/src/display.jl @@ -64,7 +64,7 @@ function set_screen_config!(backend::Module, new_values) end backend_defaults[k] = v end - return + return backend_defaults end function merge_screen_config(::Type{Config}, screen_config_kw) where Config @@ -83,15 +83,12 @@ function merge_screen_config(::Type{Config}, screen_config_kw) where Config end """ -# GLMakie -start_renderloop=true -visible=true -connect=true - -# CairoMakie -pt_per_unit=x.pt_per_unit -px_per_unit=x.px_per_unit -antialias=x.antialias + Base.display(figlike::FigureLike; backend=current_backend(), screen_config...) + +Displays the figurelike in a window or the browser, depending on the backend. + +The parameters for `screen_config` are backend dependend, +see `?Backend.Screen` or `Base.doc(Backend.Screen)` for applicable options. """ function Base.display(figlike::FigureLike; backend=current_backend(), screen_config...) if ismissing(backend) diff --git a/src/documentation/documentation.jl b/src/documentation/documentation.jl index a2b2f8967d5..ce83835cd4c 100644 --- a/src/documentation/documentation.jl +++ b/src/documentation/documentation.jl @@ -32,7 +32,7 @@ function _help(io::IO, input::Type{T}; extended = false) where T <: AbstractPlot str = to_string(input) # Print docstrings - println(io, Base.Docs.doc(func)) + println(io, Base.doc(func)) # Arguments help_arguments(io, func) diff --git a/src/ffmpeg-util.jl b/src/ffmpeg-util.jl index 1a983de2455..b354985d6e6 100644 --- a/src/ffmpeg-util.jl +++ b/src/ffmpeg-util.jl @@ -1,48 +1,34 @@ # TODO move to something like FFMPEGUtil.jl ? -const VIDEO_STREAM_OPTIONS_FORMAT_DESC = """ -- `format = "mkv"`: The format of the video. Can be one of the following: +""" +- `format = "mkv"`: The format of the video. If a path is present, will be inferred form the file extension. + Can be one of the following: * `"mkv"` (open standard, the default) * `"mp4"` (good for Web, most supported format) * `"webm"` (smallest file size) * `"gif"` (largest file size for the same quality) - `mp4` and `mk4` are marginally bigger than `webm`. `gif`s can be significantly (as much as - 6x) larger with worse quality (due to the limited color palette) and only should be used - as a last resort, for playing in a context where videos aren't supported. -""" - -const VIDEO_STREAM_OPTIONS_KWARGS_DESC = """ + `mp4` and `mk4` are marginally bigger than `webm`. `gif`s can be significantly (as much as + 6x) larger with worse quality (due to the limited color palette) and only should be used + as a last resort, for playing in a context where videos aren't supported. - `framerate = 24`: The target framerate. - `compression = 20`: Controls the video compression via `ffmpeg`'s `-crf` option, with - smaller numbers giving higher quality and larger file sizes (lower compression), and and - higher numbers giving lower quality and smaller file sizes (higher compression). The - minimum value is `0` (lossless encoding). + smaller numbers giving higher quality and larger file sizes (lower compression), and and + higher numbers giving lower quality and smaller file sizes (higher compression). The + minimum value is `0` (lossless encoding). - For `mp4`, `51` is the maximum. Note that `compression = 0` only works with `mp4` if - `profile = high444`. + `profile = high444`. - For `webm`, `63` is the maximum. - `compression` has no effect on `mkv` and `gif` outputs. - `profile = "high422"`: A ffmpeg compatible profile. Currently only applies to `mp4`. If - you have issues playing a video, try `profile = "high"` or `profile = "main"`. +you have issues playing a video, try `profile = "high"` or `profile = "main"`. - `pixel_format = "yuv420p"`: A ffmpeg compatible pixel format (`-pix_fmt`). Currently only - applies to `mp4`. Defaults to `yuv444p` for `profile = high444`. -""" +applies to `mp4`. Defaults to `yuv444p` for `profile = high444`. -""" - VideoStreamOptions(; format="mkv", framerate=24, compression=20, profile=nothing, pixel_format=nothing, loglevel="quiet", input="pipe:0", rawvideo=true) - -Holds the options that will be used for encoding a `VideoStream`. `profile` and -`pixel_format` are only used when `format` is `"mp4"`; a warning will be issued if `format` -is not `"mp4"` and those two arguments are not `nothing`. Similarly, `compression` is only -valid when `format` is `"mp4"` or `"webm"`. - -You should not create a `VideoStreamOptions` directly; instead, pass its keyword args to the -`VideoStream` constructor. See the docs of [`VideoStream`](@ref) for how to create a -`VideoStream`. If you want a simpler interface, consider using [`record`](@ref). - -### Keyword Arguments: -$VIDEO_STREAM_OPTIONS_FORMAT_DESC -$VIDEO_STREAM_OPTIONS_KWARGS_DESC + !!! warning + `profile` and `pixel_format` are only used when `format` is `"mp4"`; a warning will be issued if `format` + is not `"mp4"` and those two arguments are not `nothing`. Similarly, `compression` is only + valid when `format` is `"mp4"` or `"webm"`. """ struct VideoStreamOptions format::String @@ -180,6 +166,7 @@ function to_ffmpeg_cmd(vso::VideoStreamOptions, xdim::Integer=0, ydim::Integer=0 return `$(ffmpeg_prefix) $(ffmpeg_options)` end + struct VideoStream io::Base.PipeEndpoint process::Base.Process @@ -190,15 +177,26 @@ struct VideoStream end """ - VideoStream(scene::Scene; framerate = 24, visible=false, connect=false, screen_config...) + VideoStream(fig::FigureLike; + format="mp4", framerate=24, compression=nothing, profile=nothing, pixel_format=nothing, loglevel="quiet", + visible=false, connect=false, backend=current_backend(), + screen_config...) Returns a `VideoStream` which can pipe new frames into the ffmpeg process with few allocations via [`recordframe!(stream)`](@ref). When done, use [`save(path, stream)`](@ref) to write the video out to a file. -* visible=false: make window visible or not -* connect=false: connect window events or not -* backend=current_backend(): backend used to record frames -* screen_config... : Have a look at `?Backend.Screen` for infos about applicable configurations. +# Arguments + +## Video options + +$(Base.doc(VideoStreamOptions)) + +## Backend options + +* `backend=current_backend()`: backend used to record frames +* `visible=false`: make window visible or not +* `connect=false`: connect window events or not +* `screen_config...`: See `?Backend.Screen` or `Base.doc(Backend.Screen)` for applicable options that can be passed and forwarded to the backend. """ function VideoStream(fig::FigureLike; format="mp4", framerate=24, compression=nothing, profile=nothing, pixel_format=nothing, loglevel="quiet", diff --git a/src/recording.jl b/src/recording.jl index 138864dc848..6ed450aa730 100644 --- a/src/recording.jl +++ b/src/recording.jl @@ -73,8 +73,8 @@ function FileIO.save(dir::String, s::RamStepper) end """ - record(func, figurelike, path; kwargs...) - record(func, figurelike, path, iter; kwargs...) + record(func, figurelike, path; backend=current_backend(), kwargs...) + record(func, figurelike, path, iter; backend=current_backend(), kwargs...) The first signature provides `func` with a VideoStream, which it should call `recordframe!(io)` on when recording a frame. @@ -86,13 +86,22 @@ Both notations require a Figure, FigureAxisPlot or Scene `figure` to work. The animation is then saved to `path`, with the format determined by `path`'s extension. -$VIDEO_STREAM_OPTIONS_FORMAT_DESC +Under the hood, `record` is just `video_io = Record(func, figurelike, [iter]; same_kw...); save(path, video_io)`. +`Record` can be used directly as well to do the saving at a later point, or to inline a video directly into a Notebook (the video supports, `show(video_io, "text/html")` for that purpose). -### Keyword Arguments: -$VIDEO_STREAM_OPTIONS_KWARGS_DESC +# Options one can pass via `kwargs...`: +* `backend::Module = current_backend()`: set the backend to write out video, can be set to `CairoMakie`, `GLMakie`, `WGLMakie`, `RPRMakie`. +### Backend options + +See `?Backend.Screen` or `Base.doc(Backend.Screen)` for applicable options that can be passed and forwarded to the backend. + +### Video options + +$(Base.doc(VideoStreamOptions)) + +# Typical usage -Typical usage patterns would look like: ```julia record(figure, "video.mp4", itr) do i func(i) # or some other manipulation of the figure @@ -128,26 +137,32 @@ record(fig, "test.gif", 1:255) do i end ``` """ -function record(func, figlike::FigureLike, path::AbstractString; record_kw...) +function record(func, figlike::FigureLike, path::AbstractString; kw_args...) format = lstrip(splitext(path)[2], '.') - io = Record(func, figlike; format=format, record_kw...) + io = Record(func, figlike; format=format, kw_args...) save(path, io) end -function record(func, figlike::FigureLike, path::AbstractString, iter; record_kw...) +function record(func, figlike::FigureLike, path::AbstractString, iter; kw_args...) format = lstrip(splitext(path)[2], '.') - io = Record(func, figlike, iter; format=format, record_kw...) + io = Record(func, figlike, iter; format=format, kw_args...) save(path, io) end -function Record(func, figlike; videostream_kw...) - io = VideoStream(figlike; videostream_kw...) + +""" + Record(func, figlike, [iter]; kw_args...) + +Check [`Makie.record`](@ref) for documentation. +""" +function Record(func, figlike; kw_args...) + io = VideoStream(figlike; kw_args...) func(io) return io end -function Record(func, figlike, iter; videostream_kw...) - io = VideoStream(figlike; videostream_kw...) +function Record(func, figlike, iter; kw_args...) + io = VideoStream(figlike; kw_args...) for i in iter func(i) recordframe!(io) From 28f6605fb275563a1be117aa723510a219eadd15 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Mon, 10 Oct 2022 17:50:51 +0200 Subject: [PATCH 47/56] move preferred mime machinery to CairoMakie, since its not needed anywhere else --- CairoMakie/src/CairoMakie.jl | 2 +- CairoMakie/src/display.jl | 52 +++++++++++++++++++++++++++++++++--- CairoMakie/src/screen.jl | 14 +++++++++- src/display.jl | 43 +---------------------------- 4 files changed, 63 insertions(+), 48 deletions(-) diff --git a/CairoMakie/src/CairoMakie.jl b/CairoMakie/src/CairoMakie.jl index b5995fe1f33..b670147c1c2 100644 --- a/CairoMakie/src/CairoMakie.jl +++ b/CairoMakie/src/CairoMakie.jl @@ -34,6 +34,6 @@ function __init__() activate!() end -include("precompiles.jl") +# include("precompiles.jl") end diff --git a/CairoMakie/src/display.jl b/CairoMakie/src/display.jl index 35f898180a8..cae2c4634b0 100644 --- a/CairoMakie/src/display.jl +++ b/CairoMakie/src/display.jl @@ -99,7 +99,51 @@ function Makie.backend_show(screen::Screen{IMAGE}, io::IO, ::MIME"image/png", sc return screen end -Makie.backend_showable(::Type{Screen}, ::MIME"image/svg+xml") = true -Makie.backend_showable(::Type{Screen}, ::MIME"application/pdf") = true -Makie.backend_showable(::Type{Screen}, ::MIME"application/postscript") = true -Makie.backend_showable(::Type{Screen}, ::MIME"image/png") = true +# Disabling mimes and showable + +const DISABLED_MIMES = Set{String}() +const SUPPORTED_MIMES = Set([ + "image/svg+xml", + "application/pdf", + "application/postscript", + "image/png" +]) + +function Makie.backend_showable(::Type{Screen}, ::MIME{SYM}) where SYM + supported_mimes = Base.setdiff(SUPPORTED_MIMES, DISABLED_MIMES) + return string(SYM) in supported_mimes +end + +""" + disable_mime!(mime::Union{String, Symbol, MIME}...) + +The default is automatic, which lets the display system figure out the best mime. +If set to any other valid mime, will result in `showable(any_other_mime, figurelike)` to return false and only return true for `showable(preferred_mime, figurelike)`. +Depending on the display system used, this may result in nothing getting displayed. +""" +function disable_mime!(mimes::Union{String, Symbol, MIME}...) + empty!(DISABLED_MIMES) # always start from 0 + if isempty(mimes) + # Reset disabled mimes when called with no arguments + return + end + mime_strings = Set{String}() + for mime in mimes + if mime isa MIME + mime_str = string(mime) + if !(mime_str in SUPPORTED_MIMES) + error("Mime $(mime) not supported by CairoMakie") + end + else + mime_str = string(mime) + if !(mime_str in SUPPORTED_MIMES) + mime_str = string(to_mime(convert(RenderType, mime_str))) + end + end + push!(mime_strings, mime_str) + end + + + union!(DISABLED_MIMES, mime_strings) + return +end diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 2b8ae36243e..9d746222782 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -112,7 +112,19 @@ $(Base.doc(ScreenConfig)) """ function activate!(; screen_config...) config = Makie.set_screen_config!(CairoMakie, screen_config) - Makie.set_preferred_mime!(to_mime(convert(RenderType, config.type[]))) + type = config.type[] + + if type == "png" + # So this is a bit counter intuitive, since the display system doesn't let us prefer a mime. + # Instead, any IDE with rich output usually has a priority list of mimes, which it iterates to figure out the best mime. + # So, if we want to prefer the png mime, we disable the mimes that are usually higher up in the stack. + disable_mime!("svg", "pdf") + elseif type == "svg" + # SVG is usually pretty high up the priority, so we can just enable all mimes + # If we implement html display for CairoMakie, we might need to disable that. + disable_mime!() + end + Makie.set_active_backend!(CairoMakie) return end diff --git a/src/display.jl b/src/display.jl index 947045cb1fe..84537a0bc71 100644 --- a/src/display.jl +++ b/src/display.jl @@ -114,54 +114,13 @@ function Base.display(screen::MakieScreen, figlike::FigureLike; display_attribut return screen end -const PREFERRED_MIME = Base.RefValue{Union{Automatic, String}}(automatic) - -""" - set_preferred_mime!(mime::Union{String, Symbol, MIME, Automatic}=automatic) - -The default is automatic, which lets the display system figure out the best mime. -If set to any other valid mime, will result in `showable(any_other_mime, figurelike)` to return false and only return true for `showable(preferred_mime, figurelike)`. -Depending on the display system used, this may result in nothing getting displayed. -""" -function set_preferred_mime!(mime::Union{String, Symbol, MIME, Automatic}=automatic) - if mime isa Automatic - PREFERRED_MIME[] = mime - return - end - # Mimes supported accross Makie backends - # TODO, make this dynamic and backends can register their mimes? - supported_mimes = Set([ - "text/html", - "application/vnd.webio.application+html", - "application/prs.juno.plotpane+html", - "juliavscode/html", - "image/svg+xml", - "application/pdf", - "application/postscript", - "image/png", - "image/jpeg"]) - - mime_str = string(mime) - - if mime_str in supported_mimes - PREFERRED_MIME[] = mime_str - else - error("Mime not supported: $(mime_str). Supported mimes: $(supported_mimes)") - end - return -end function _backend_showable(mime::MIME{SYM}) where SYM Backend = current_backend() if ismissing(Backend) return Symbol("text/plain") == SYM end - backend_support = backend_showable(Backend.Screen, mime) - if PREFERRED_MIME[] isa Automatic - return backend_support - else - return backend_support && string(SYM) == PREFERRED_MIME[] - end + return backend_showable(Backend.Screen, mime) end Base.showable(mime::MIME, fig::FigureLike) = _backend_showable(mime) From 0f374176dcadd59510116729ef5ef22575bf852b Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Mon, 10 Oct 2022 21:28:05 +0200 Subject: [PATCH 48/56] remove set_preferred_mime! from tests etc --- GLMakie/src/screen.jl | 1 - GLMakie/test/runtests.jl | 1 - src/display.jl | 1 - test/display.jl | 94 ++++++++++++++++++++-------------------- 4 files changed, 47 insertions(+), 50 deletions(-) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index abef49f266c..afeb3e51eca 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -112,7 +112,6 @@ function activate!(; screen_config...) if haskey(screen_config, :pause_rendering) error("pause_rendering got renamed to pause_renderloop.") end - Makie.set_preferred_mime!() # reset mime Makie.set_screen_config!(GLMakie, screen_config) Makie.set_active_backend!(GLMakie) Makie.set_glyph_resolution!(Makie.High) diff --git a/GLMakie/test/runtests.jl b/GLMakie/test/runtests.jl index cb4eb0564ba..4e4a7b89bff 100644 --- a/GLMakie/test/runtests.jl +++ b/GLMakie/test/runtests.jl @@ -13,7 +13,6 @@ using ReferenceTests GLMakie.activate!(framerate=1.0) @testset "mimes" begin - Makie.set_preferred_mime!() f, ax, pl = scatter(1:4) @test showable("image/png", f) @test showable("image/jpeg", f) diff --git a/src/display.jl b/src/display.jl index 84537a0bc71..6567165c411 100644 --- a/src/display.jl +++ b/src/display.jl @@ -114,7 +114,6 @@ function Base.display(screen::MakieScreen, figlike::FigureLike; display_attribut return screen end - function _backend_showable(mime::MIME{SYM}) where SYM Backend = current_backend() if ismissing(Backend) diff --git a/test/display.jl b/test/display.jl index 2fc0b76a3b6..2fdf7393ac4 100644 --- a/test/display.jl +++ b/test/display.jl @@ -1,53 +1,53 @@ -module TestBackend - using Makie - struct Screen <: MakieScreen end - Makie.backend_showable(::Type{Screen}, ::MIME"text/html") = true - Makie.backend_showable(::Type{Screen}, ::MIME"image/png") = true -end +# module TestBackend +# using Makie +# struct Screen <: MakieScreen end +# Makie.backend_showable(::Type{Screen}, ::MIME"text/html") = true +# Makie.backend_showable(::Type{Screen}, ::MIME"image/png") = true +# end -@testset "set_preferred_mime 'constructor'" begin - @test_throws ErrorException Makie.set_preferred_mime!("text/htmle") - @test nothing == Makie.set_preferred_mime!(MIME"text/html"()) - @test nothing == Makie.set_preferred_mime!(Symbol("text/html")) - @test nothing == Makie.set_preferred_mime!() -end +# @testset "set_preferred_mime 'constructor'" begin +# @test_throws ErrorException Makie.set_preferred_mime!("text/htmle") +# @test nothing == Makie.set_preferred_mime!(MIME"text/html"()) +# @test nothing == Makie.set_preferred_mime!(Symbol("text/html")) +# @test nothing == Makie.set_preferred_mime!() +# end -@testset "preferred mime without backend" begin - # Only text/plain - Makie.set_active_backend!(missing) - Makie.set_preferred_mime!("text/html") - s = Scene(); - @test Base.showable("text/plain", s) - @test !Base.showable("text/html", s) -end +# @testset "preferred mime without backend" begin +# # Only text/plain +# Makie.set_active_backend!(missing) +# Makie.set_preferred_mime!("text/html") +# s = Scene(); +# @test Base.showable("text/plain", s) +# @test !Base.showable("text/html", s) +# end -@testset "with Backend + preferred mime" begin - Makie.set_active_backend!(TestBackend) - Makie.set_preferred_mime!("text/html") - s = Scene(); - @test Base.showable("text/html", s) - @test !Base.showable("image/png", s) -end +# @testset "with Backend + preferred mime" begin +# Makie.set_active_backend!(TestBackend) +# Makie.set_preferred_mime!("text/html") +# s = Scene(); +# @test Base.showable("text/html", s) +# @test !Base.showable("image/png", s) +# end -@testset "without preferred mime, backend capabilities should be returned" begin - Makie.set_preferred_mime!() - s = Scene(); - @test Base.showable("text/html", s) - @test Base.showable("image/png", s) - @test !Base.showable("text/plain", s) - @test !Base.showable("image/jpeg", s) -end +# @testset "without preferred mime, backend capabilities should be returned" begin +# Makie.set_preferred_mime!() +# s = Scene(); +# @test Base.showable("text/html", s) +# @test Base.showable("image/png", s) +# @test !Base.showable("text/plain", s) +# @test !Base.showable("image/jpeg", s) +# end -@testset "only plain for missing backend" begin - Makie.set_active_backend!(missing) - s = Scene(); - @test Base.showable("text/plain", s) - @test !Base.showable("image/png", s) - @test !Base.showable("text/html", s) -end +# @testset "only plain for missing backend" begin +# Makie.set_active_backend!(missing) +# s = Scene(); +# @test Base.showable("text/plain", s) +# @test !Base.showable("image/png", s) +# @test !Base.showable("text/html", s) +# end -@testset "preferred mime without backend should return false" begin - Makie.set_preferred_mime!("text/html") - s = Scene(); - @test !Base.showable("text/html", s) -end +# @testset "preferred mime without backend should return false" begin +# Makie.set_preferred_mime!("text/html") +# s = Scene(); +# @test !Base.showable("text/html", s) +# end From 4bbd47e42d9239bbdc349581a0be8c0f852f836e Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 11 Oct 2022 14:20:47 +0200 Subject: [PATCH 49/56] fix mime test --- CairoMakie/src/CairoMakie.jl | 2 +- CairoMakie/src/display.jl | 50 ++++++++++++++++++++++++++---------- CairoMakie/src/screen.jl | 2 ++ 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/CairoMakie/src/CairoMakie.jl b/CairoMakie/src/CairoMakie.jl index b670147c1c2..b5995fe1f33 100644 --- a/CairoMakie/src/CairoMakie.jl +++ b/CairoMakie/src/CairoMakie.jl @@ -34,6 +34,6 @@ function __init__() activate!() end -# include("precompiles.jl") +include("precompiles.jl") end diff --git a/CairoMakie/src/display.jl b/CairoMakie/src/display.jl index cae2c4634b0..f2aa93cfe23 100644 --- a/CairoMakie/src/display.jl +++ b/CairoMakie/src/display.jl @@ -114,6 +114,27 @@ function Makie.backend_showable(::Type{Screen}, ::MIME{SYM}) where SYM return string(SYM) in supported_mimes end +""" + to_mime_string(mime::Union{String, Symbol, MIME}) + +Converts anything like `"png", :png, "image/png", MIME"image/png"()` to `"image/png`. +""" +function to_mime_string(mime::Union{String, Symbol, MIME}) + if mime isa MIME + mime_str = string(mime) + if !(mime_str in SUPPORTED_MIMES) + error("Mime $(mime) not supported by CairoMakie") + end + return mime_str + else + mime_str = string(mime) + if !(mime_str in SUPPORTED_MIMES) + mime_str = string(to_mime(convert(RenderType, mime_str))) + end + return mime_str + end +end + """ disable_mime!(mime::Union{String, Symbol, MIME}...) @@ -129,21 +150,22 @@ function disable_mime!(mimes::Union{String, Symbol, MIME}...) end mime_strings = Set{String}() for mime in mimes - if mime isa MIME - mime_str = string(mime) - if !(mime_str in SUPPORTED_MIMES) - error("Mime $(mime) not supported by CairoMakie") - end - else - mime_str = string(mime) - if !(mime_str in SUPPORTED_MIMES) - mime_str = string(to_mime(convert(RenderType, mime_str))) - end - end - push!(mime_strings, mime_str) + push!(mime_strings, to_mime_string(mime)) end - - union!(DISABLED_MIMES, mime_strings) return end + +function enable_only_mime!(mimes::Union{String, Symbol, MIME}...) + empty!(DISABLED_MIMES) # always start from 0 + if isempty(mimes) + # Reset disabled mimes when called with no arguments + return + end + mime_strings = Set{String}() + for mime in mimes + push!(mime_strings, to_mime_string(mime)) + end + union!(DISABLED_MIMES, setdiff(SUPPORTED_MIMES, mime_strings)) + return +end diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 9d746222782..36702a6e799 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -123,6 +123,8 @@ function activate!(; screen_config...) # SVG is usually pretty high up the priority, so we can just enable all mimes # If we implement html display for CairoMakie, we might need to disable that. disable_mime!() + else + enable_only_mime!(type) end Makie.set_active_backend!(CairoMakie) From 5a494e052381dc7a4a598b089270f80f42df73a8 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 11 Oct 2022 20:43:04 +0200 Subject: [PATCH 50/56] last clean up & address review --- CairoMakie/src/display.jl | 2 +- CairoMakie/src/screen.jl | 34 +++++++++++------------- CairoMakie/test/runtests.jl | 23 ++++++++++++++++ CairoMakie/test/saving.jl | 40 ---------------------------- GLMakie/src/screen.jl | 2 +- RPRMakie/src/RPRMakie.jl | 2 +- WGLMakie/src/WGLMakie.jl | 2 +- src/theming.jl | 1 - test/display.jl | 53 ------------------------------------- test/runtests.jl | 1 - 10 files changed, 42 insertions(+), 118 deletions(-) delete mode 100644 CairoMakie/test/saving.jl delete mode 100644 test/display.jl diff --git a/CairoMakie/src/display.jl b/CairoMakie/src/display.jl index f2aa93cfe23..eb7e28edba0 100644 --- a/CairoMakie/src/display.jl +++ b/CairoMakie/src/display.jl @@ -117,7 +117,7 @@ end """ to_mime_string(mime::Union{String, Symbol, MIME}) -Converts anything like `"png", :png, "image/png", MIME"image/png"()` to `"image/png`. +Converts anything like `"png", :png, "image/png", MIME"image/png"()` to `"image/png"`. """ function to_mime_string(mime::Union{String, Symbol, MIME}) if mime isa MIME diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 36702a6e799..517e375b0dc 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -2,6 +2,7 @@ using Base.Docs: doc @enum RenderType SVG IMAGE PDF EPS +Base.convert(::Type{RenderType}, ::MIME{SYM}) where SYM = mime_to_rendertype(SYM) function Base.convert(::Type{RenderType}, type::String) if type == "png" return IMAGE @@ -81,39 +82,34 @@ to_cairo_antialias(aa::Int) = aa * `visible::Bool`: if true, a browser/image viewer will open to display rendered output. """ struct ScreenConfig - type::RenderType px_per_unit::Float64 pt_per_unit::Float64 antialias::Symbol visible::Bool - start_renderloop::Bool - function ScreenConfig(type, px_per_unit::Number, pt_per_unit::Number, anitalias::Symbol, visible::Bool, start_renderloop::Bool) - return new(convert(RenderType, type), px_per_unit, pt_per_unit, anitalias, visible, start_renderloop) - end + start_renderloop::Bool # Only used to satisfy the interface for record using `Screen(...; start_renderloop=false)` for GLMakie end -function device_scaling_factor(sc::ScreenConfig) - if is_vector_backend(sc.type) - return sc.pt_per_unit - else - return sc.px_per_unit - end +function device_scaling_factor(rendertype, sc::ScreenConfig) + isv = is_vector_backend(convert(RenderType, rendertype)) + return isv ? sc.pt_per_unit : sc.px_per_unit +end + +function device_scaling_factor(surface::Cairo.CairoSurface, sc::ScreenConfig) + return is_vector_backend(surface) ? sc.pt_per_unit : sc.px_per_unit end """ CairoMakie.activate!(; screen_config...) Sets CairoMakie as the currently active backend and also allows to quickly set the `screen_config`. -Note, that the `screen_config` can also be set via permanently via `Makie.set_theme!(CairoMakie=(screen_config...,))`. +Note, that the `screen_config` can also be set permanently via `Makie.set_theme!(CairoMakie=(screen_config...,))`. # Arguments one can pass via `screen_config`: $(Base.doc(ScreenConfig)) """ -function activate!(; screen_config...) - config = Makie.set_screen_config!(CairoMakie, screen_config) - type = config.type[] - +function activate!(; type="png", screen_config...) + Makie.set_screen_config!(CairoMakie, screen_config) if type == "png" # So this is a bit counter intuitive, since the display system doesn't let us prefer a mime. # Instead, any IDE with rich output usually has a priority list of mimes, which it iterates to figure out the best mime. @@ -194,14 +190,14 @@ Screen(scene::Scene; screen_config...) = Screen(scene, nothing, IMAGE; screen_co function Screen(scene::Scene, io_or_path::Union{Nothing, String, IO}, typ::Union{MIME, Symbol, RenderType}; screen_config...) config = Makie.merge_screen_config(ScreenConfig, screen_config) # the surface size is the scene size scaled by the device scaling factor - w, h = round.(Int, size(scene) .* device_scaling_factor(config)) + w, h = round.(Int, size(scene) .* device_scaling_factor(typ, config)) surface = surface_from_output_type(typ, io_or_path, w, h) return Screen(scene, surface, config) end function Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_config...) config = Makie.merge_screen_config(ScreenConfig, screen_config) - w, h = round.(Int, size(scene) .* device_scaling_factor(config)) + w, h = round.(Int, size(scene) .* config.px_per_unit) # create an image surface to draw onto the image img = Matrix{ARGB32}(undef, w, h) surface = Cairo.CairoImageSurface(img) @@ -210,7 +206,7 @@ end function Screen(scene::Scene, surface::Cairo.CairoSurface, config::ScreenConfig) # the surface size is the scene size scaled by the device scaling factor - dsf = device_scaling_factor(config) + dsf = device_scaling_factor(surface, config) surface_set_device_scale(surface, dsf) ctx = Cairo.CairoContext(surface) aa = to_cairo_antialias(config.antialias) diff --git a/CairoMakie/test/runtests.jl b/CairoMakie/test/runtests.jl index 75101be4867..29cbc02d29c 100644 --- a/CairoMakie/test/runtests.jl +++ b/CairoMakie/test/runtests.jl @@ -30,6 +30,29 @@ include(joinpath(@__DIR__, "rasterization_tests.jl")) @test showable("image/png", f) # see https://github.com/MakieOrg/Makie.jl/pull/2167 @test !showable("blaaa", f) + + CairoMakie.activate!(type="png") + @test showable("image/png", Scene()) + @test !showable("image/svg+xml", Scene()) + # setting svg should leave png as showable, since it's usually lower in the display stack priority + CairoMakie.activate!(type="svg") + @test showable("image/png", Scene()) + @test showable("image/svg+xml", Scene()) +end + +@testset "VideoStream & screen options" begin + N = 3 + points = Observable(Point2f[]) + f, ax, pl = scatter(points, axis=(type=Axis, aspect=DataAspect(), limits=(0.4, N + 0.6, 0.4, N + 0.6),), figure=(resolution=(600, 800),)) + vio = Makie.VideoStream(f; format="mp4", px_per_unit=2.0, backend=CairoMakie) + @test vio.screen isa CairoMakie.Screen{CairoMakie.IMAGE} + @test size(vio.screen) == size(f.scene) .* 2 + @test vio.screen.device_scaling_factor == 2.0 + + Makie.recordframe!(vio) + save("test.mp4", vio) + @test isfile("test.mp4") # Make sure no error etc + rm("test.mp4") end using ReferenceTests diff --git a/CairoMakie/test/saving.jl b/CairoMakie/test/saving.jl deleted file mode 100644 index 20f1a0118d0..00000000000 --- a/CairoMakie/test/saving.jl +++ /dev/null @@ -1,40 +0,0 @@ -database = MakieGallery.load_database(["short_tests.jl"]); - -filter!(database) do example - !("3d" ∈ example.tags) -end - -format_save_path = joinpath(@__DIR__, "test_formats") -isdir(format_save_path) && rm(format_save_path, recursive = true) -mkpath(format_save_path) -savepath(uid, fmt) = joinpath(format_save_path, "$uid.$fmt") - -@testset "Saving formats" begin - for example in database - scene = MakieGallery.eval_example(example) - for fmt in ("png", "pdf", "svg") - @test try - save(savepath(example.title, fmt), scene) - true - catch e - @warn "Saving $(example.title) in format `$fmt` failed!" exception=(e, Base.catch_backtrace()) - false - end - end - end -end - -@testset "VideoStream & screen options" begin - N = 3 - points = Observable(Point2f[]) - f, ax, pl = scatter(points, axis=(type=Axis, aspect=DataAspect(), limits=(0.4, N + 0.6, 0.4, N + 0.6),), figure=(resolution=(600, 800),)) - vio = Makie.VideoStream(f; format="mp4", px_per_unit=2.0, backend=CairoMakie) - @test vio.screen isa CairoMakie.Screen{CairoMakie.IMAGE} - @test size(vio.screen) == size(f.scene) .* 2 - @test vio.screen.device_scaling_factor == 2.0 - - Makie.recordframe!(vio) - save("test.mp4", vio) - @test isfile("test.mp4") # Make sure no error etc - rm("test.mp4") -end diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index afeb3e51eca..01be06a4071 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -102,7 +102,7 @@ end GLMakie.activate!(; screen_config...) Sets GLMakie as the currently active backend and also allows to quickly set the `screen_config`. -Note, that the `screen_config` can also be set via permanently via `Makie.set_theme!(GLMakie=(screen_config...,))`. +Note, that the `screen_config` can also be set permanently via `Makie.set_theme!(GLMakie=(screen_config...,))`. # Arguments one can pass via `screen_config`: diff --git a/RPRMakie/src/RPRMakie.jl b/RPRMakie/src/RPRMakie.jl index 1dc79f75aee..b3ddead9794 100644 --- a/RPRMakie/src/RPRMakie.jl +++ b/RPRMakie/src/RPRMakie.jl @@ -44,7 +44,7 @@ include("volume.jl") Sets RPRMakie as the currently active backend and also allows to quickly set the `screen_config`. -Note, that the `screen_config` can also be set via permanently via `Makie.set_theme!(RPRMakie=(screen_config...,))`. +Note, that the `screen_config` can also be set permanently via `Makie.set_theme!(RPRMakie=(screen_config...,))`. # Arguments one can pass via `screen_config`: diff --git a/WGLMakie/src/WGLMakie.jl b/WGLMakie/src/WGLMakie.jl index 3a6e1649b23..1c80b9a9d32 100644 --- a/WGLMakie/src/WGLMakie.jl +++ b/WGLMakie/src/WGLMakie.jl @@ -48,7 +48,7 @@ include("display.jl") WGLMakie.activate!(; screen_config...) Sets WGLMakie as the currently active backend and also allows to quickly set the `screen_config`. -Note, that the `screen_config` can also be set via permanently via `Makie.set_theme!(WGLMakie=(screen_config...,))`. +Note, that the `screen_config` can also be set permanently via `Makie.set_theme!(WGLMakie=(screen_config...,))`. # Arguments one can pass via `screen_config`: diff --git a/src/theming.jl b/src/theming.jl index 3c2c421a891..6fe3216a2ef 100644 --- a/src/theming.jl +++ b/src/theming.jl @@ -94,7 +94,6 @@ const minimal_default = Attributes( inspectable = true, CairoMakie = Attributes( - type = "png", px_per_unit = 1.0, pt_per_unit = 0.75, antialias = :best, diff --git a/test/display.jl b/test/display.jl deleted file mode 100644 index 2fdf7393ac4..00000000000 --- a/test/display.jl +++ /dev/null @@ -1,53 +0,0 @@ -# module TestBackend -# using Makie -# struct Screen <: MakieScreen end -# Makie.backend_showable(::Type{Screen}, ::MIME"text/html") = true -# Makie.backend_showable(::Type{Screen}, ::MIME"image/png") = true -# end - -# @testset "set_preferred_mime 'constructor'" begin -# @test_throws ErrorException Makie.set_preferred_mime!("text/htmle") -# @test nothing == Makie.set_preferred_mime!(MIME"text/html"()) -# @test nothing == Makie.set_preferred_mime!(Symbol("text/html")) -# @test nothing == Makie.set_preferred_mime!() -# end - -# @testset "preferred mime without backend" begin -# # Only text/plain -# Makie.set_active_backend!(missing) -# Makie.set_preferred_mime!("text/html") -# s = Scene(); -# @test Base.showable("text/plain", s) -# @test !Base.showable("text/html", s) -# end - -# @testset "with Backend + preferred mime" begin -# Makie.set_active_backend!(TestBackend) -# Makie.set_preferred_mime!("text/html") -# s = Scene(); -# @test Base.showable("text/html", s) -# @test !Base.showable("image/png", s) -# end - -# @testset "without preferred mime, backend capabilities should be returned" begin -# Makie.set_preferred_mime!() -# s = Scene(); -# @test Base.showable("text/html", s) -# @test Base.showable("image/png", s) -# @test !Base.showable("text/plain", s) -# @test !Base.showable("image/jpeg", s) -# end - -# @testset "only plain for missing backend" begin -# Makie.set_active_backend!(missing) -# s = Scene(); -# @test Base.showable("text/plain", s) -# @test !Base.showable("image/png", s) -# @test !Base.showable("text/html", s) -# end - -# @testset "preferred mime without backend should return false" begin -# Makie.set_preferred_mime!("text/html") -# s = Scene(); -# @test !Base.showable("text/html", s) -# end diff --git a/test/runtests.jl b/test/runtests.jl index 75a47bfc107..b59b768a46d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -19,7 +19,6 @@ using Makie: volume end include("record.jl") - include("display.jl") include("scenes.jl") include("conversions.jl") include("quaternions.jl") From 03dc6f97737b8c69755ae2f341e838b6c094d2ed Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 11 Oct 2022 22:05:37 +0200 Subject: [PATCH 51/56] update docs --- docs/documentation/backends/cairomakie.md | 12 +++++++++- docs/documentation/backends/glmakie.md | 27 +++++++++-------------- docs/documentation/backends/rprmakie.md | 19 ++++++++-------- docs/documentation/backends/wglmakie.md | 2 +- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/docs/documentation/backends/cairomakie.md b/docs/documentation/backends/cairomakie.md index 7d772bd19ed..d31748bcd57 100644 --- a/docs/documentation/backends/cairomakie.md +++ b/docs/documentation/backends/cairomakie.md @@ -3,7 +3,17 @@ [CairoMakie](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) uses Cairo.jl to draw vector graphics to SVG and PDF. You should use it if you want to achieve the highest-quality plots for publications, as the rendering process of the GL backends works via bitmaps and is geared more towards speed than pixel-perfection. -### Special CairoMakie Properties +## Activation and screen config + +Activate the backend by calling `CairoMakie.activate!()` with the following options: +```julia:docs +# hideall +using CairoMakie, Markdown +println("~~~") +println(Markdown.html(@doc CairoMakie.activate!)) +println("~~~") +``` +\textoutput{docs} #### Inline Plot Type diff --git a/docs/documentation/backends/glmakie.md b/docs/documentation/backends/glmakie.md index da6718dfb18..c270749dc8c 100644 --- a/docs/documentation/backends/glmakie.md +++ b/docs/documentation/backends/glmakie.md @@ -3,24 +3,17 @@ [GLMakie](https://github.com/MakieOrg/Makie.jl/tree/master/GLMakie) is the native, desktop-based backend, and is the most feature-complete. It requires an OpenGL enabled graphics card with OpenGL version 3.3 or higher. -### Special GLMakie Properties - -#### Window Parameters - -You can set parameters of the window with the function `set_window_config!` which only takes effect when opening a new window. - -```julia -activate!(; - renderloop = renderloop, - vsync = false, - framerate = 30.0, - float = false, - pause_rendering = false, - focus_on_show = false, - decorated = true, - title = "Makie" -) +## Activation and screen config + +Activate the backend by calling `GLMakie.activate!()` with the following options: +```julia:docs +# hideall +using GLMakie, Markdown +println("~~~") +println(Markdown.html(@doc GLMakie.activate!)) +println("~~~") ``` +\textoutput{docs} #### Multiple Windows diff --git a/docs/documentation/backends/rprmakie.md b/docs/documentation/backends/rprmakie.md index cc104d135b1..a9081d67362 100644 --- a/docs/documentation/backends/rprmakie.md +++ b/docs/documentation/backends/rprmakie.md @@ -11,10 +11,9 @@ using RadeonProRender RadeonProRender.Context() ``` -## Activating and working with RPMakie - -To use the backend, just call `activate!` like with all other backends. There are a few extra parameters for RPRMakie: +## Activation and screen config +Activate the backend by calling `RPRMakie.activate!()` with the following options: ```julia:docs # hideall using RPRMakie, Markdown @@ -57,12 +56,12 @@ mesh!(ax, Sphere(Point3f, 0), material=mat) # There are three main ways to turn a Makie scene into a picture: # Get the colorbuffer of the Screen. Screen also has `show` overloaded for the mime `image\png` so it should display in IJulia/Jupyter/VSCode. image = colorbuffer(screen)::Matrix{RGB{N0f8}} -# Replace a specific (sub) LScene with RPR, and display the whole scene interactively in GLMakie -using GLMakie +# Replace a specific (sub) LScene with RPR, and display the whole scene interactively in RPRMakie +using RPRMakie refres = Observable(nothing) # Optional observable that triggers -GLMakie.activate!(); display(fig) # Make sure to display scene first in GLMakie +RPRMakie.activate!(); display(fig) # Make sure to display scene first in RPRMakie # Replace the scene with an interactively rendered RPR output. -# See more about this in the GLMakie interop example +# See more about this in the RPRMakie interop example context, task = RPRMakie.replace_scene_rpr!(ax.scene, screen; refresh=refresh) # If one doesn't create the Screen manually to create custom materials, # display(ax.scene), show(io, MIME"image/png", ax.scene), save("rpr.png", ax.scene) @@ -206,7 +205,7 @@ save("topographie.png", ax.scene) ~~~ -## GLMakie interop (opengl_interop.jl) +## RPRMakie interop (opengl_interop.jl) RPRMakie doesn't support layouting and sub scenes yet, but you can replace a single scene with a RPR rendered, interactive window. This is especially handy, to show 2d graphics and interactive UI elements next to a ray traced scene and interactively tune camera and material parameters. @@ -215,7 +214,7 @@ This is especially handy, to show 2d graphics and interactive UI elements next t ~~~ ```julia -using GLMakie, GeometryBasics, RPRMakie, RadeonProRender +using RPRMakie, GeometryBasics, RPRMakie, RadeonProRender using Colors, FileIO using Colors: N0f8 @@ -286,7 +285,7 @@ for (key, (obs, input)) in pairs(sliders) end fig[1, 2] = grid!(hcat(labels, inputs); width=500) -GLMakie.activate!() +RPRMakie.activate!() cam = cameracontrols(ax.scene) cam.eyeposition[] = Vec3f(22, 0, 17) diff --git a/docs/documentation/backends/wglmakie.md b/docs/documentation/backends/wglmakie.md index 6fc988723d5..924dee48bec 100644 --- a/docs/documentation/backends/wglmakie.md +++ b/docs/documentation/backends/wglmakie.md @@ -3,7 +3,7 @@ [WGLMakie](https://github.com/MakieOrg/Makie.jl/tree/master/WGLMakie) is the Web-based backend, and is still experimental (though relatively feature-complete). WGLMakie uses [JSServe](https://github.com/SimonDanisch/JSServe.jl) to generate the HTML and JS for the Makie plots. -## Activation +## Activation and screen config Activate the backend by calling `WGLMakie.activate!()` with the following options: ```julia:docs From 8642146aedfcbe797f85ee85cc1113704ff21067 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 11 Oct 2022 22:05:55 +0200 Subject: [PATCH 52/56] initialize array --- CairoMakie/src/screen.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 517e375b0dc..26b49011f53 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -173,7 +173,6 @@ function Base.show(io::IO, ::MIME"text/plain", screen::Screen{S}) where S println(io, "CairoMakie.Screen{$S}") end - function path_to_type(path) type = splitext(path)[2][2:end] return convert(RenderType, type) @@ -199,7 +198,7 @@ function Screen(scene::Scene, ::Makie.ImageStorageFormat; screen_config...) config = Makie.merge_screen_config(ScreenConfig, screen_config) w, h = round.(Int, size(scene) .* config.px_per_unit) # create an image surface to draw onto the image - img = Matrix{ARGB32}(undef, w, h) + img = fill(ARGB32(0, 0, 0, 0), w, h) surface = Cairo.CairoImageSurface(img) return Screen(scene, surface, config) end @@ -225,7 +224,7 @@ function Makie.colorbuffer(screen::Screen) # get resolution w, h = size(screen) # preallocate an image matrix - img = Matrix{ARGB32}(undef, w, h) + img = fill(ARGB32(0, 0, 0, 0), w, h) # create an image surface to draw onto the image surf = Cairo.CairoImageSurface(img) s = Screen(scene, surf; device_scaling_factor=screen.device_scaling_factor, antialias=screen.antialias) From c7d6e5f5401b12ac2cd12128d8f570853b120c23 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 11 Oct 2022 22:06:18 +0200 Subject: [PATCH 53/56] deprecate set_window_config! properly --- GLMakie/src/GLMakie.jl | 2 ++ GLMakie/test/glmakie_refimages.jl | 38 +++++++++++++++---------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/GLMakie/src/GLMakie.jl b/GLMakie/src/GLMakie.jl index 03e2c38f9e8..3ceb3a073a5 100644 --- a/GLMakie/src/GLMakie.jl +++ b/GLMakie/src/GLMakie.jl @@ -51,6 +51,8 @@ function __init__() activate!() end +Base.@deprecate set_window_config!(; screen_config...) GLMakie.activate!(; screen_config...) + include("precompiles.jl") end diff --git a/GLMakie/test/glmakie_refimages.jl b/GLMakie/test/glmakie_refimages.jl index 20f1f5cb876..85406e4217a 100644 --- a/GLMakie/test/glmakie_refimages.jl +++ b/GLMakie/test/glmakie_refimages.jl @@ -70,25 +70,25 @@ end end end -# @reference_test "Explicit frame rendering" begin -# set_window_config!(renderloop=(screen) -> nothing) -# function update_loop(m, buff, screen) -# for i = 1:20 -# GLFW.PollEvents() -# buff .= RNG.rand.(Point3f) .* 20f0 -# m[1] = buff -# GLMakie.render_frame(screen) -# GLFW.SwapBuffers(GLMakie.to_native(screen)) -# glFinish() -# end -# end -# fig, ax, meshplot = meshscatter(RNG.rand(Point3f, 10^4) .* 20f0) -# screen = display(GLMakie.Screen(), fig.scene) -# buff = RNG.rand(Point3f, 10^4) .* 20f0; -# update_loop(meshplot, buff, screen) -# set_window_config!(renderloop=GLMakie.renderloop) -# fig -# end +@reference_test "Explicit frame rendering" begin + function update_loop(m, buff, screen) + for i = 1:20 + GLFW.PollEvents() + buff .= RNG.rand.(Point3f) .* 20f0 + m[1] = buff + GLMakie.render_frame(screen) + GLFW.SwapBuffers(GLMakie.to_native(screen)) + glFinish() + end + end + fig, ax, meshplot = meshscatter(RNG.rand(Point3f, 10^4) .* 20f0) + screen = display(GLMakie.Screen(;renderloop=(screen) -> nothing, start_renderloop=false), fig.scene) + buff = RNG.rand(Point3f, 10^4) .* 20f0; + update_loop(meshplot, buff, screen) + @test !isassigned(screen.rendertask) + GLMakie.destroy!(screen) + fig +end @reference_test "Contour and isosurface with correct depth" begin # Make sure shaders can recompile From e164fe90609ec3a163c892bb0567d7f8aba6f502 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 11 Oct 2022 23:09:51 +0200 Subject: [PATCH 54/56] forgot another constructor --- CairoMakie/src/screen.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 26b49011f53..3d1719fbf8c 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -56,7 +56,7 @@ function surface_from_output_type(type::RenderType, io, w, h) elseif type === EPS return Cairo.CairoEPSSurface(io, w, h) elseif type === IMAGE - img = Matrix{ARGB32}(undef, w, h) + img = fill(ARGB32(0, 0, 0, 0), w, h) return Cairo.CairoImageSurface(img) else error("No available Cairo surface for mode $type") From 5e92ee192f854141cd3b7368d958f7fb505f09cd Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 12 Oct 2022 12:10:49 +0200 Subject: [PATCH 55/56] small clean up and doc fixes --- CairoMakie/src/infrastructure.jl | 2 +- CairoMakie/src/screen.jl | 9 ++++++++ GLMakie/src/screen.jl | 39 ++++++++++++++++---------------- src/theming.jl | 2 +- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/CairoMakie/src/infrastructure.jl b/CairoMakie/src/infrastructure.jl index 8620e64a90d..2700dffd780 100644 --- a/CairoMakie/src/infrastructure.jl +++ b/CairoMakie/src/infrastructure.jl @@ -126,7 +126,7 @@ function draw_plot_as_image(scene::Scene, screen::Screen, primitive::Combined, s w, h = Int.(scene.px_area[].widths) # Create a new Screen which renders directly to an image surface, # specifically for the plot's parent scene. - scr = Screen(scene; device_scaling_factor = scale) + scr = Screen(scene; px_per_unit = scale) # Draw the plot to the screen, in the normal way draw_plot(scene, scr, primitive) diff --git a/CairoMakie/src/screen.jl b/CairoMakie/src/screen.jl index 26b49011f53..792ff2af669 100644 --- a/CairoMakie/src/screen.jl +++ b/CairoMakie/src/screen.jl @@ -158,6 +158,15 @@ function Base.empty!(screen::Screen) Cairo.restore(ctx) end +# function clear(screen::Screen) +# ctx = screen.context +# Cairo.save(ctx) +# Cairo.set_operator(ctx, Cairo.OPERATOR_SOURCE) +# Cairo.set_source_rgba(ctx, rgbatuple(screen.scene.backgroundcolor[])...) +# Cairo.paint_with_alpha(ctx, 0.0) +# Cairo.restore(ctx) +# end + Base.size(screen::Screen) = round.(Int, (screen.surface.width, screen.surface.height)) # we render the scene directly, since we have # no screen dependent state like in e.g. opengl diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 01be06a4071..65f00200d41 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -28,32 +28,33 @@ function renderloop end * `debugging = false`: Starts the GLFW.Window/OpenGL context with debug output. * `monitor::Union{Nothing, GLFW.Monitor} = nothing`: Sets the monitor on which the Window should be opened. -## Preproccessor -* `oit = true`: Enles order independent transparency for the window. +## Postprocessor +* `oit = false`: Enles order independent transparency for the window. * `fxaa = true`: Enables fxaa (anti-aliasing) for the window. -* `ssao = true`: Enables screen space occlusion, which gives 3D meshes a soft shadow towards their edges. - +* `ssao = true`: Enables screen space ambient occlusion, which simulates natural shadowing at inner edges and crevices. +* `transparency_weight_scale = 1000f0`: This adjusts a factor in the rendering shaders for order independent transparency. + This should be the same for all of them (within one rendering pipeline) otherwise depth "order" will be broken. """ mutable struct ScreenConfig # Renderloop - renderloop::Function # GLMakie.renderloop, + renderloop::Function pause_renderloop::Bool - vsync::Bool# = false, - framerate::Float64# = 30.0, + vsync::Bool + framerate::Float64 # GLFW window attributes - float::Bool# = false, - focus_on_show::Bool# = false, - decorated::Bool# = true, - title::String# = "Makie", - fullscreen::Bool# = false, - debugging::Bool# = false, - monitor::Union{Nothing, GLFW.Monitor}# = nothing, - - # Preproccessor - oit::Bool# = true, - fxaa::Bool# = true, - ssao::Bool# = true + float::Bool + focus_on_show::Bool + decorated::Bool + title::String + fullscreen::Bool + debugging::Bool + monitor::Union{Nothing, GLFW.Monitor} + + # Postprocessor + oit::Bool + fxaa::Bool + ssao::Bool transparency_weight_scale::Float32 function ScreenConfig( diff --git a/src/theming.jl b/src/theming.jl index 6fe3216a2ef..ea27dee7611 100644 --- a/src/theming.jl +++ b/src/theming.jl @@ -117,7 +117,7 @@ const minimal_default = Attributes( debugging = false, monitor = nothing, - # Preproccessor + # Postproccessor oit = true, fxaa = true, ssao = false, From d5b163061795c4cd2dab44ed686ee3af5c9f1c70 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 12 Oct 2022 12:20:25 +0200 Subject: [PATCH 56/56] small doc fixes --- NEWS.md | 5 ++--- src/display.jl | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index c45be21ae01..0cef41f1a24 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,10 +14,9 @@ Added single image marker support to WGLMakie [#979](https://github.com/MakieOrg/Makie.jl/pull/979). - Allow `CairoMakie` to render `scatter` with images as markers [#2080](https://github.com/MakieOrg/Makie.jl/pull/2080). - Reworked text drawing and added ability to draw special characters via glyph indices in order to draw more LaTeX math characters with MathTeXEngine v0.5 [#2139](https://github.com/MakieOrg/Makie.jl/pull/2139). -- Recording and colorbuffers use screen size instead of scene size, allowing e.g. the `px_per_unit` argument in CairoMakie to take effect. - It is sufficient to set the global `px_per_unit` setting by calling `CairoMakie.activate!(px_per_unit=3)` or whichever scaling factor is desired. - GLMakie and WGLMakie are currently unaffected by this. [#2012](https://github.com/JuliaPlots/Makie.jl/pull/2116). - Allow text to be copy/pasted into textbox [#2281](https://github.com/MakieOrg/Makie.jl/pull/2281) +- refactor `display`, `record`, `colorbuffer` and `screens` to be faster and more consistent [#2306](https://github.com/MakieOrg/Makie.jl/pull/2306#issuecomment-1275918061). + ## v0.17.13 diff --git a/src/display.jl b/src/display.jl index 6567165c411..4ae4adb23bf 100644 --- a/src/display.jl +++ b/src/display.jl @@ -283,7 +283,7 @@ function getscreen(f::Function, scene::Scene, backend::Module) end """ - colorbuffer(scene, format::ImageStorageFormat = JuliaNative) + colorbuffer(scene, format::ImageStorageFormat = JuliaNative; backend=current_backend(), screen_config...) colorbuffer(screen, format::ImageStorageFormat = JuliaNative) Returns the content of the given scene or screen rasterised to a Matrix of @@ -293,6 +293,7 @@ or RGBA. - `format = JuliaNative` : Returns a buffer in the format of standard julia images (dims permuted and one reversed) - `format = GLNative` : Returns a more efficient format buffer for GLMakie which can be directly used in FFMPEG without conversion +- `screen_config`: Backend dependend, look up via `?Backend.Screen`/`Base.doc(Backend.Screen)` """ function colorbuffer(fig::FigureLike, format::ImageStorageFormat = JuliaNative; backend = current_backend(), screen_config...) scene = get_scene(fig)