Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Scale windows and resolution for HiDPI displays #2544

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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/glmakie.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: julia-actions/cache@v1
- 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
- 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 xsettingsd x11-xserver-utils
- name: Install Julia dependencies
shell: julia --project=monorepo {0}
run: |
Expand Down
2 changes: 1 addition & 1 deletion GLMakie/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Colors = "0.11, 0.12"
FileIO = "1.6"
FixedPointNumbers = "0.7, 0.8"
FreeTypeAbstraction = "0.10"
GLFW = "3"
GLFW = "3.3"
GeometryBasics = "0.4.1"
Makie = "=0.19.3"
MeshIO = "0.4"
Expand Down
12 changes: 8 additions & 4 deletions GLMakie/assets/shader/distance_shape.frag
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ uniform float stroke_width;
uniform float glow_width;
uniform int shape; // shape is a uniform for now. Making them a in && using them for control flow is expected to kill performance
uniform vec2 resolution;
uniform float px_per_unit;
uniform bool transparent_picking;

flat in float f_viewport_from_u_scale;
Expand Down Expand Up @@ -97,7 +98,9 @@ void stroke(vec4 strokecolor, float signed_distance, float width, inout vec4 col

void glow(vec4 glowcolor, float signed_distance, float inside, inout vec4 color){
if (glow_width > 0.0){
float outside = (abs(signed_distance)-stroke_width)/glow_width;
float s_stroke_width = px_per_unit * stroke_width;
float s_glow_width = px_per_unit * glow_width;
float outside = (abs(signed_distance)-s_stroke_width)/s_glow_width;
float alpha = 1-outside;
color = mix(vec4(glowcolor.rgb, glowcolor.a*alpha), color, inside);
}
Expand Down Expand Up @@ -145,13 +148,14 @@ void main(){
// See notes in geometry shader where f_viewport_from_u_scale is computed.
signed_distance *= f_viewport_from_u_scale;

float inside_start = max(-stroke_width, 0.0);
float s_stroke_width = px_per_unit * stroke_width;
float inside_start = max(-s_stroke_width, 0.0);
float inside = aastep(inside_start, signed_distance);
vec4 final_color = f_bg_color;

fill(f_color, image, tex_uv, inside, final_color);
stroke(f_stroke_color, signed_distance, -stroke_width, final_color);
glow(f_glow_color, signed_distance, aastep(-stroke_width, signed_distance), final_color);
stroke(f_stroke_color, signed_distance, -s_stroke_width, final_color);
glow(f_glow_color, signed_distance, aastep(-s_stroke_width, signed_distance), final_color);
// TODO: In 3D, we should arguably discard fragments outside the sprite
// But note that this may interfere with object picking.
// if (final_color == f_bg_color)
Expand Down
3 changes: 2 additions & 1 deletion GLMakie/assets/shader/sprites.geom
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ uniform float stroke_width;
uniform float glow_width;
uniform int shape; // for RECTANGLE hack below
uniform vec2 resolution;
uniform float px_per_unit;
uniform float depth_shift;

in int g_primitive_index[];
Expand Down Expand Up @@ -138,7 +139,7 @@ void main(void)
0.0, 1.0/vclip.w, 0.0, 0.0,
0.0, 0.0, 1.0/vclip.w, 0.0,
-vclip.xyz/(vclip.w*vclip.w), 0.0);
mat2 dxyv_dxys = diagm(0.5*resolution) * mat2(d_ndc_d_clip*trans);
mat2 dxyv_dxys = diagm(0.5*px_per_unit*resolution) * mat2(d_ndc_d_clip*trans);
// Now, our buffer size is expressed in viewport pixels but we get back to
// the sprite coordinate system using the scale factor of the
// transformation (for isotropic transformations). For anisotropic
Expand Down
1 change: 1 addition & 0 deletions GLMakie/src/drawing_primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ function cached_robj!(robj_func, screen, scene, x::AbstractPlot)
gl_attributes[:ambient] = ambientlight.color
end
gl_attributes[:track_updates] = screen.config.render_on_demand
gl_attributes[:px_per_unit] = screen.px_per_unit

robj = robj_func(gl_attributes)

Expand Down
114 changes: 49 additions & 65 deletions GLMakie/src/events.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,50 +37,50 @@ function Makie.disconnect!(window::GLFW.Window, ::typeof(window_open))
GLFW.SetWindowCloseCallback(window, nothing)
end

function window_position(window::GLFW.Window)
xy = GLFW.GetWindowPos(window)
(xy.x, xy.y)
end
function Makie.window_area(scene::Scene, screen::Screen)
disconnect!(screen, window_area)

struct WindowAreaUpdater
window::GLFW.Window
dpi::Observable{Float64}
area::Observable{GeometryBasics.HyperRectangle{2, Int64}}
end
# TODO: Figure out which monitor the window is on and react to DPI changes
monitor = GLFW.GetPrimaryMonitor()
props = MonitorProperties(monitor)
scene.events.window_dpi[] = minimum(props.dpi)

function windowsizecb(window, width::Cint, height::Cint)
area = scene.events.window_area
sf = screen.scalefactor[]

function (x::WindowAreaUpdater)(::Nothing)
ShaderAbstractions.switch_context!(x.window)
rect = x.area[]
# TODO put back window position, but right now it makes more trouble than it helps#
# x, y = GLFW.GetWindowPos(window)
# if minimum(rect) != Vec(x, y)
# event[] = Recti(x, y, framebuffer_size(window))
# end
w, h = GLFW.GetFramebufferSize(x.window)
if Vec(w, h) != widths(rect)
monitor = GLFW.GetPrimaryMonitor()
props = MonitorProperties(monitor)
# dpi of a monitor should be the same in x y direction.
# if not, minimum seems to be a fair default
x.dpi[] = minimum(props.dpi)
x.area[] = Recti(minimum(rect), w, h)
ShaderAbstractions.switch_context!(window)
winscale = sf / (@static Sys.isapple() ? scale_factor(window) : 1)
winw, winh = round.(Int, (width, height) ./ winscale)
if Vec(winw, winh) != widths(area[])
area[] = Recti(minimum(area[]), winw, winh)
end
return
end
return
end
# TODO put back window position, but right now it makes more trouble than it helps
#function windowposcb(window, x::Cint, y::Cint)
# area = scene.events.window_area
# ShaderAbstractions.switch_context!(window)
# winscale = screen.scalefactor[] / (@static Sys.isapple() ? scale_factor(window) : 1)
# xs, ys = round.(Int, (x, y) ./ winscale)
# if Vec(xs, ys) != minimum(area[])
# area[] = Recti(xs, ys, widths(area[]))
# end
# return
#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)
window = to_native(screen)
GLFW.SetWindowSizeCallback(window, (win, w, h) -> windowsizecb(win, w, h))
#GLFW.SetWindowPosCallback(window, (win, x, y) -> windowposcb(win, x, y))

windowsizecb(window, Cint.(window_size(window))...)
return
end

function Makie.disconnect!(screen::Screen, ::typeof(window_area))
filter!(p -> !isa(p[2], WindowAreaUpdater), screen.render_tick.listeners)
window = to_native(screen)
#GLFW.SetWindowPosCallback(window, nothing)
GLFW.SetWindowSizeCallback(window, nothing)
return
end
function Makie.disconnect!(::GLFW.Window, ::typeof(window_area))
Expand Down Expand Up @@ -168,44 +168,28 @@ function Makie.disconnect!(window::GLFW.Window, ::typeof(unicode_input))
GLFW.SetCharCallback(window, nothing)
end

# TODO memoise? Or to bug ridden for the small performance gain?
function retina_scaling_factor(w, fb)
(w[1] == 0 || w[2] == 0) && return (1.0, 1.0)
fb ./ w
end

# TODO both of these methods are slow!
# ~90µs, ~80µs
# This is too slow for events that may happen 100x per frame
function framebuffer_size(window::GLFW.Window)
wh = GLFW.GetFramebufferSize(window)
(wh.width, wh.height)
end
function window_size(window::GLFW.Window)
wh = GLFW.GetWindowSize(window)
(wh.width, wh.height)
end
function retina_scaling_factor(window::GLFW.Window)
w, fb = window_size(window), framebuffer_size(window)
retina_scaling_factor(w, fb)
end

function correct_mouse(window::GLFW.Window, w, h)
ws, fb = window_size(window), framebuffer_size(window)
s = retina_scaling_factor(ws, fb)
(w * s[1], fb[2] - (h * s[2]))
function correct_mouse(screen::Screen, w, h)
nw = to_native(screen)
sf = screen.scalefactor[] / (@static Sys.isapple() ? scale_factor(nw) : 1)
_, winh = window_size(nw)
@static if Sys.isapple()
return w, (winh / sf) - h
else
return w / sf, (winh - h) / sf
end
end

struct MousePositionUpdater
window::GLFW.Window
screen::Screen
mouseposition::Observable{Tuple{Float64, Float64}}
hasfocus::Observable{Bool}
end

function (p::MousePositionUpdater)(::Nothing)
!p.hasfocus[] && return
x, y = GLFW.GetCursorPos(p.window)
pos = correct_mouse(p.window, x, y)
nw = to_native(p.screen)
x, y = GLFW.GetCursorPos(nw)
pos = correct_mouse(p.screen, x, y)
if pos != p.mouseposition[]
@print_error p.mouseposition[] = pos
# notify!(e.mouseposition)
Expand All @@ -222,7 +206,7 @@ which is not in scene coordinates, with the upper left window corner being 0
function Makie.mouse_position(scene::Scene, screen::Screen)
disconnect!(screen, mouse_position)
updater = MousePositionUpdater(
to_native(screen), scene.events.mouseposition, scene.events.hasfocus
screen, scene.events.mouseposition, scene.events.hasfocus
)
on(updater, screen.render_tick)
return
Expand Down
31 changes: 20 additions & 11 deletions GLMakie/src/glwindow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,13 @@ function GLFramebuffer(fb_size::NTuple{2, Int})
)
end

function Base.resize!(fb::GLFramebuffer, window_size)
ws = Int.((window_size[1], window_size[2]))
if ws != size(fb) && all(x-> x > 0, window_size)
for (name, buffer) in fb.buffers
resize_nocopy!(buffer, ws)
end
fb.resolution[] = ws
function Base.resize!(fb::GLFramebuffer, w::Int, h::Int)
(w > 0 && h > 0 && (w, h) != size(fb)) || return
for (name, buffer) in fb.buffers
resize_nocopy!(buffer, (w, h))
end
nothing
fb.resolution[] = (w, h)
return nothing
end


Expand Down Expand Up @@ -188,10 +186,21 @@ function destroy!(nw::GLFW.Window)
was_current && ShaderAbstractions.switch_context!()
end

function windowsize(nw::GLFW.Window)
function window_size(nw::GLFW.Window)
was_destroyed(nw) && return (0, 0)
return Tuple(GLFW.GetWindowSize(nw))
end
function window_position(nw::GLFW.Window)
was_destroyed(nw) && return (0, 0)
size = GLFW.GetFramebufferSize(nw)
return (size.width, size.height)
return Tuple(GLFW.GetWindowPos(window))
end
function framebuffer_size(nw::GLFW.Window)
was_destroyed(nw) && return (0, 0)
return Tuple(GLFW.GetFramebufferSize(nw))
end
function scale_factor(nw::GLFW.Window)
was_destroyed(nw) && return 1f0
return minimum(GLFW.GetWindowContentScale(nw))
end

function Base.isopen(window::GLFW.Window)
Expand Down
16 changes: 8 additions & 8 deletions GLMakie/src/picking.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
function pick_native(screen::Screen, rect::Rect2i)
isopen(screen) || return Matrix{SelectionID{Int}}(undef, 0, 0)
ShaderAbstractions.switch_context!(screen.glscreen)
window_size = size(screen)
fb = screen.framebuffer
buff = fb.buffers[:objectid]
glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1])
glReadBuffer(GL_COLOR_ATTACHMENT1)
rx, ry = minimum(rect)
rw, rh = widths(rect)
w, h = window_size
sid = zeros(SelectionID{UInt32}, widths(rect)...)
w, h = size(screen.root_scene)
if rx > 0 && ry > 0 && rx + rw <= w && ry + rh <= h
rx, ry, rw, rh = round.(Int, screen.px_per_unit[] .* (rx, ry, rw, rh))
sid = zeros(SelectionID{UInt32}, rw, rh)
glReadPixels(rx, ry, rw, rh, buff.format, buff.pixeltype, sid)
return sid
else
Expand All @@ -26,15 +26,15 @@ end
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 = size(screen)
fb = screen.framebuffer
buff = fb.buffers[:objectid]
glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1])
glReadBuffer(GL_COLOR_ATTACHMENT1)
x, y = floor.(Int, xy)
w, h = window_size
w, h = size(screen.root_scene)
if x > 0 && y > 0 && x <= w && y <= h
x, y = round.(Int, screen.px_per_unit[] .* (x, y))
sid = Base.RefValue{SelectionID{UInt32}}()
glReadPixels(x, y, 1, 1, buff.format, buff.pixeltype, sid)
return convert(SelectionID{Int}, sid[])
end
Expand Down Expand Up @@ -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 = size(screen)
w, h = size(scene)
((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h)) || return (nothing, 0)

x0, y0 = max.(1, floor.(Int, xy .- range))
Expand Down Expand Up @@ -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 = size(screen)
w, h = size(scene)
if !((1.0 <= xy[1] <= w) && (1.0 <= xy[2] <= h))
return Tuple{AbstractPlot, Int}[]
end
Expand Down
5 changes: 1 addition & 4 deletions GLMakie/src/postprocessing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -285,14 +285,11 @@ function to_screen_postprocessor(framebuffer, shader_cache, screen_fb_id = nothi
pass.postrenderfunction = () -> draw_fullscreen(pass.vertexarray.id)

full_render = screen -> begin
fb = screen.framebuffer
w, h = size(fb)

# transfer everything to the screen
default_id = isnothing(screen_fb_id) ? 0 : screen_fb_id[]
# GLFW uses 0, Gtk uses a value that we have to probe at the beginning of rendering
glBindFramebuffer(GL_FRAMEBUFFER, default_id)
glViewport(0, 0, w, h)
glViewport(0, 0, framebuffer_size(screen.glscreen)...)
glClear(GL_COLOR_BUFFER_BIT)
GLAbstraction.render(pass) # copy postprocess
end
Expand Down
20 changes: 10 additions & 10 deletions GLMakie/src/rendering.jl
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@

function setup!(screen)
function setup!(screen::Screen)
glEnable(GL_SCISSOR_TEST)
if isopen(screen)
glScissor(0, 0, size(screen)...)
if isopen(screen) && !isnothing(screen.root_scene)
sf = screen.px_per_unit[]
glScissor(0, 0, round.(Int, size(screen.root_scene) .* sf)...)
glClearColor(1, 1, 1, 1)
glClear(GL_COLOR_BUFFER_BIT)
for (id, scene) in screen.screens
if scene.visible[]
a = pixelarea(scene)[]
rt = (minimum(a)..., widths(a)...)
rt = (round.(Int, sf .* minimum(a))..., round.(Int, sf .* widths(a))...)
glViewport(rt...)
if scene.clear
c = scene.backgroundcolor[]
Expand Down Expand Up @@ -43,11 +43,10 @@ function render_frame(screen::Screen; resize_buffers=true)
# render order here may introduce artifacts because of that.

fb = screen.framebuffer
if resize_buffers
wh = Int.(framebuffer_size(nw))
resize!(fb, wh)
if resize_buffers && !isnothing(screen.root_scene)
sf = screen.px_per_unit[]
resize!(fb, round.(Int, sf .* size(screen.root_scene))...)
end
w, h = size(fb)

# prepare stencil (for sub-scenes)
glBindFramebuffer(GL_FRAMEBUFFER, fb.id)
Expand Down Expand Up @@ -119,8 +118,9 @@ function GLAbstraction.render(filter_elem_func, screen::Screen)
found, scene = id2scene(screen, screenid)
found || continue
scene.visible[] || continue
sf = screen.px_per_unit[]
a = pixelarea(scene)[]
glViewport(minimum(a)..., widths(a)...)
glViewport(round.(Int, sf .* minimum(a))..., round.(Int, sf .* widths(a))...)
render(elem)
end
catch e
Expand Down
Loading