diff --git a/Project.toml b/Project.toml index 0a0b9c1..b9046f8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "GLMakie" uuid = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a" -version = "0.0.6" +version = "0.0.7" [deps] AbstractPlotting = "537997a7-5e4e-5d89-9595-2241ea00577e" diff --git a/src/GLAbstraction/GLTypes.jl b/src/GLAbstraction/GLTypes.jl index dbcb482..ec17d0e 100644 --- a/src/GLAbstraction/GLTypes.jl +++ b/src/GLAbstraction/GLTypes.jl @@ -195,6 +195,11 @@ julia2glenum(x::Type{GLint}) = GL_INT julia2glenum(x::Type{GLfloat}) = GL_FLOAT julia2glenum(x::Type{GLdouble}) = GL_DOUBLE julia2glenum(x::Type{Float16}) = GL_HALF_FLOAT + +struct DepthStencil_24_8 <: Real end +Base.eltype(::Type{<: DepthStencil_24_8}) = DepthStencil_24_8 +julia2glenum(x::Type{DepthStencil_24_8}) = GL_UNSIGNED_INT_24_8 + function julia2glenum(::Type{T}) where T error("Type: $T not supported as opengl number datatype") end diff --git a/src/drawing_primitives.jl b/src/drawing_primitives.jl index 95de84f..4115f63 100644 --- a/src/drawing_primitives.jl +++ b/src/drawing_primitives.jl @@ -378,19 +378,20 @@ function surface_contours(volume::Volume) shader = makieshader(paths..., frag) model = volume[:model] x, y, z, vol = volume[1], volume[2], volume[3], volume[4] - model2 = lift(model, x, y, z) do m, xyz... + model2 = lift(x, y, z) do xyz... mi = minimum.(xyz) maxi = maximum.(xyz) w = maxi .- mi - m2 = Mat4f0( + return Mat4f0( w[1], 0, 0, 0, 0, w[2], 0, 0, 0, 0, w[3], 0, mi[1], mi[2], mi[3], 1 ) - convert(Mat4f0, m) * m2 end - modelinv = lift(inv, model2) + + modelinv = lift((a,b)-> inv(b) * inv(a), model, model2) + model2 = lift(*, model, model2) hull = AABB{Float32}(Vec3f0(0), Vec3f0(1)) gl_data = Dict( :hull => GLUVWMesh(hull), diff --git a/src/glwindow.jl b/src/glwindow.jl index 66953cd..66a23c0 100644 --- a/src/glwindow.jl +++ b/src/glwindow.jl @@ -19,8 +19,6 @@ function (sp::PostprocessPrerender)() glDepthMask(GL_TRUE) glDisable(GL_DEPTH_TEST) glDisable(GL_BLEND) - glDisable(GL_STENCIL_TEST) - glStencilMask(0xff) glDisable(GL_CULL_FACE) nothing end @@ -31,7 +29,7 @@ mutable struct GLFramebuffer id ::NTuple{2, GLuint} color ::Texture{RGBA{N0f8}, 2} objectid ::Texture{Vec{2, GLushort}, 2} - depth ::Texture{Float32, 2} + depth ::Texture{GLAbstraction.DepthStencil_24_8, 2} color_luma ::Texture{RGBA{N0f8}, 2} postprocess::NTuple{3, PostProcessROBJ} end @@ -104,16 +102,17 @@ function GLFramebuffer(fb_size::NTuple{2, Int}) objectid_buffer = Texture(Vec{2, GLushort}, fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge) depth_buffer = Texture( - Float32, fb_size, + Ptr{GLAbstraction.DepthStencil_24_8}(C_NULL), fb_size, minfilter = :nearest, x_repeat = :clamp_to_edge, - internalformat = GL_DEPTH_COMPONENT32F, - format = GL_DEPTH_COMPONENT + internalformat = GL_DEPTH24_STENCIL8, + format = GL_DEPTH_STENCIL ) attach_framebuffer(color_buffer, GL_COLOR_ATTACHMENT0) attach_framebuffer(objectid_buffer, GL_COLOR_ATTACHMENT1) attach_framebuffer(depth_buffer, GL_DEPTH_ATTACHMENT) + attach_framebuffer(depth_buffer, GL_STENCIL_ATTACHMENT) status = glCheckFramebufferStatus(GL_FRAMEBUFFER) @assert status == GL_FRAMEBUFFER_COMPLETE diff --git a/src/rendering.jl b/src/rendering.jl index 1b5505c..922a236 100644 --- a/src/rendering.jl +++ b/src/rendering.jl @@ -1,24 +1,31 @@ function renderloop(screen::Screen; framerate = 1/30, prerender = () -> nothing) try while isopen(screen) - t = time() - GLFW.PollEvents() # GLFW poll - prerender() - make_context_current(screen) - render_frame(screen) - GLFW.SwapBuffers(to_native(screen)) - diff = framerate - (time() - t) - if diff > 0 - sleep(diff) - else # if we don't sleep, we need to yield explicitely - yield() + # Somehow errors get sometimes ignored, so we at least print them here + try + t = time() + GLFW.PollEvents() # GLFW poll + prerender() + make_context_current(screen) + render_frame(screen) + GLFW.SwapBuffers(to_native(screen)) + diff = framerate - (time() - t) + if diff > 0 + sleep(diff) + else # if we don't sleep, we need to yield explicitely + yield() + end + catch e + @error "Error in renderloop!" exception=e + rethrow(e) end end catch e - destroy!(screen) + @error "Error in renderloop!" exception=e rethrow(e) + finally + destroy!(screen) end - destroy!(screen) return end @@ -35,11 +42,14 @@ function setup!(screen) a = rect[] rt = (minimum(a)..., widths(a)...) glViewport(rt...) + bits = GL_STENCIL_BUFFER_BIT + glClearStencil(id) if clear[] c = color[] glScissor(rt...) glClearColor(red(c), green(c), blue(c), alpha(c)) - glClear(GL_COLOR_BUFFER_BIT) + bits |= GL_COLOR_BUFFER_BIT + glClear(bits) end end end @@ -60,15 +70,24 @@ function render_frame(screen::Screen) wh = Int.(framebuffer_size(nw)) resize!(fb, wh) w, h = wh - glDisable(GL_STENCIL_TEST) + glEnable(GL_STENCIL_TEST) #prepare for geometry in need of anti aliasing glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) # color framebuffer glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) + glEnable(GL_STENCIL_TEST) + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE) + glStencilMask(0xff) + glClearStencil(0) glClearColor(0,0,0,0) glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT) setup!(screen) + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE) + glStencilMask(0x00) GLAbstraction.render(screen, true) + glDisable(GL_STENCIL_TEST) + # transfer color to luma buffer and apply fxaa glBindFramebuffer(GL_FRAMEBUFFER, fb.id[2]) # luma framebuffer glDrawBuffer(GL_COLOR_ATTACHMENT0) @@ -85,12 +104,11 @@ function render_frame(screen::Screen) #prepare for non anti aliased pass glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) + glEnable(GL_STENCIL_TEST) + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE) + glStencilMask(0x00) GLAbstraction.render(screen, false) - #Read all the selection queries - glReadBuffer(GL_COLOR_ATTACHMENT1) - for query_func in selection_queries - query_func(fb.objectid, w, h) - end + glDisable(GL_STENCIL_TEST) glBindFramebuffer(GL_FRAMEBUFFER, 0) # transfer back to window glViewport(0, 0, w, h) glClearColor(0, 0, 0, 0) @@ -102,23 +120,39 @@ end function id2rect(screen, id1) # TODO maybe we should use a different data structure for (id2, rect, clear, color) in screen.screens - id1 == id2 && return true, rect + id1 == id2 && return true, rect, clear[] end - false, IRect(0,0,0,0) + false, IRect(0,0,0,0), false end function GLAbstraction.render(screen::Screen, fxaa::Bool) - for (zindex, screenid, elem) in screen.renderlist - found, rect = id2rect(screen, screenid) - found || continue - a = rect[] - glViewport(minimum(a)..., widths(a)...) - if fxaa && elem[:fxaa][] - render(elem) - end - if !fxaa && !elem[:fxaa][] - render(elem) + # Somehow errors in here get ignored silently!? + try + # sort by overdraw, so that overdrawing objects get drawn last! + # sort!(screen.renderlist, by = ((zi, id, robj),)-> robj.prerenderfunction.overdraw[]) + for (zindex, screenid, elem) in screen.renderlist + found, rect, clear = id2rect(screen, screenid) + found || continue + a = rect[] + glViewport(minimum(a)..., widths(a)...) + if clear + glStencilFunc(GL_EQUAL, screenid, 0xff) + else + # if we don't clear, that means we have a screen that is overlaid + # on top of another, which means it doesn't have a stencil value + # so we can't do the stencil test + glStencilFunc(GL_ALWAYS, screenid, 0xff) + end + if fxaa && elem[:fxaa][] + render(elem) + end + if !fxaa && !elem[:fxaa][] + render(elem) + end end + catch e + @error "Error while rendering!" exception=e + rethrow(e) end return end diff --git a/src/screen.jl b/src/screen.jl index 4d935c3..b468d9f 100644 --- a/src/screen.jl +++ b/src/screen.jl @@ -45,7 +45,7 @@ end GeometryTypes.widths(x::Screen) = size(x.framebuffer.color) Base.wait(x::Screen) = isassigned(x.rendertask) && wait(x.rendertask[]) -Base.wait(scene::Scene) = wait(global_gl_screen()) # TODO per scene screen +Base.wait(scene::Scene) = wait(AbstractPlotting.getscreen(scene)) Base.show(io::IO, screen::Screen) = print(io, "GLMakie.Screen(...)") Base.size(x::Screen) = size(x.framebuffer) @@ -337,18 +337,14 @@ function global_gl_screen(resolution::Tuple, visibility::Bool, tries = 1) screen end -# TODO per scene screen -getscreen(scene) = global_gl_screen() - -function pick_native(scene::SceneLike, xy::VectorTypes{2}, sid = Base.RefValue{SelectionID{UInt16}}()) - screen = getscreen(scene) - screen == nothing && return SelectionID{Int}(0, 0) +function pick_native(screen::Screen, xy::Vec{2, Float64}) + sid = Base.RefValue{SelectionID{UInt16}}() window_size = widths(screen) fb = screen.framebuffer buff = fb.objectid glBindFramebuffer(GL_FRAMEBUFFER, fb.id[1]) glReadBuffer(GL_COLOR_ATTACHMENT1) - x, y = Int.(floor.(xy)) + x, y = floor.(Int, xy) w, h = window_size if x > 0 && y > 0 && x <= w && y <= h glReadPixels(x, y, 1, 1, buff.format, buff.pixeltype, sid) @@ -357,87 +353,18 @@ function pick_native(scene::SceneLike, xy::VectorTypes{2}, sid = Base.RefValue{S return SelectionID{Int}(0, 0) end -pick(scene::SceneLike, xy...) = pick(scene, Float64.(xy)) - -function pick(scene::SceneLike, xy::VectorTypes{2}) - sid = pick_native(scene, xy) - screen = getscreen(scene) - if screen != nothing && haskey(screen.cache2plot, sid.id) +function AbstractPlotting.pick(scene::SceneLike, screen::Screen, xy::Vec{2, Float64}) + sid = pick_native(screen, xy) + if haskey(screen.cache2plot, sid.id) plot = screen.cache2plot[sid.id] return (plot, sid.index) - end - return (nothing, 0) -end - -# TODO does this actually needs to be a global? -const _mouse_selection_id = Base.RefValue{SelectionID{UInt16}}() -function mouse_selection_native(scene::SceneLike) - function query_mouse(buff, w, h) - xy = events(scene).mouseposition[] - x, y = Int.(floor.(xy)) - if x > 0 && y > 0 && x <= w && y <= h - glReadPixels(x, y, 1, 1, buff.format, buff.pixeltype, _mouse_selection_id) - end - return - end - if !(query_mouse in selection_queries) - push!(selection_queries, query_mouse) - # TODO, this is not optimal since it does way more - # than calling query_mouse() on first click, - # but otherwise it might get into an inconsistent state. - render_frame(getscreen(scene)) - end - convert(SelectionID{Int}, _mouse_selection_id[]) -end - -function mouse_selection(scene::SceneLike) - sid = mouse_selection_native(scene) - screen = getscreen(scene) - if screen != nothing && haskey(screen.cache2plot, sid.id) - plot = screen.cache2plot[sid.id] - return (plot, sid.index) - end - return (nothing, 0) -end - -function mouseover(scene::SceneLike, plots::AbstractPlot...) - p, idx = mouse_selection(scene) - p in flatten_plots(plots) -end - -function flatten_plots(x::Atomic, plots = AbstractPlot[]) - if isempty(x.plots) - push!(plots, x) else - flatten_plots(x.plots, plots) - end - plots -end - -function flatten_plots(x::Combined, plots = AbstractPlot[]) - for elem in x.plots - flatten_plots(elem, plots) - end - plots -end - -function flatten_plots(array, plots = AbstractPlot[]) - for elem in array - flatten_plots(elem, plots) + return (nothing, 0) end - plots end -function onpick(f, scene::SceneLike, plots::AbstractPlot...) - fplots = flatten_plots(plots) - map_once(events(scene).mouseposition) do mp - p, idx = mouse_selection(scene) - (p in fplots) && f(idx) - return - end -end -function pick(screen::Screen, rect::IRect2D) +function AbstractPlotting.pick(screen::Screen, rect::IRect2D) window_size = widths(screen) buff = screen.framebuffer.objectid sid = zeros(SelectionID{UInt16}, widths(rect)...) @@ -448,8 +375,8 @@ function pick(screen::Screen, rect::IRect2D) if x > 0 && y > 0 && x <= w && y <= h glReadPixels(x, y, rw, rh, buff.format, buff.pixeltype, sid) return map(unique(vec(SelectionID{Int}.(sid)))) do sid - screen.cache2plot[sid.id], Int(sid.index) + (screen.cache2plot[sid.id], sid.index) end end - return SelectionID{Int}[] + return Tuple{AbstractPlot, Int}[] end