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

Remove all global attributes from TextureAtlas implementation #2498

Merged
merged 22 commits into from Dec 20, 2022

Conversation

SimonDanisch
Copy link
Member

@SimonDanisch SimonDanisch commented Dec 13, 2022

That our texture atlas relied on globals for setting e.g. the resolution/glyph_padding/glyph_per_pixel, was always kind of insane, considering we're allowing backends to use different texture atlases (e.g. for smaller sizes in WGLMakie).
I suspected that this also causes #2480, but that turned out to be false so far :( although . does work now, but the marker is still rendered incorrectly! So looks like something goes wrong in FreeTypeAbstraction in the new Julia version or with caching binary.

I've also started using our own serialization format which may remove some invalidations making it easier to compile getting the texture atlas (lets see what the benchmarks say). But what it definitely helps with is, that we can now download the serialized format for all Julia versions and never need to cache fonts again :) I hope this will make a positive dent in the CI runtimes.

Todo:

  • use a stable hash

@MakieBot
Copy link
Collaborator

MakieBot commented Dec 13, 2022

Compile Times benchmark

Note, that these numbers may fluctuate on the CI servers, so take them with a grain of salt. All benchmark results are based on the mean time and negative percent mean faster than the base branch. Note, that GLMakie + WGLMakie run on an emulated GPU, so the runtime benchmark is much slower. Results are from running:

using_time = @ctime using Backend
# Compile time
create_time = @ctime fig = scatter(1:4; color=1:4, colormap=:turbo, markersize=20, visible=true)
display_time = @ctime Makie.colorbuffer(display(fig))
# Runtime
create_time = @benchmark fig = scatter(1:4; color=1:4, colormap=:turbo, markersize=20, visible=true)
display_time = @benchmark Makie.colorbuffer(display(fig))
using create display create display
GLMakie 30.16s (30.01, 30.35) 0.13+- 17.73s (17.53, 18.08) 0.18+- 16.72s (16.48, 16.99) 0.20+- 10.48ms (10.34, 10.61) 0.10+- 114.72ms (113.66, 115.33) 0.59+-
master 30.46s (30.32, 30.55) 0.09+- 17.72s (17.56, 17.96) 0.15+- 16.71s (16.44, 16.97) 0.19+- 10.49ms (10.34, 10.57) 0.08+- 113.20ms (112.10, 114.71) 1.03+-
evaluation -1.00%, -0.3s faster ✓ (-2.68d, 0.00p, 0.11std) +0.02%, 0.0s invariant (0.02d, 0.97p, 0.16std) +0.07%, 0.01s invariant (0.06d, 0.92p, 0.20std) -0.03%, -0.0ms invariant (-0.04d, 0.94p, 0.09std) +1.33%, 1.52ms slower X (1.81d, 0.01p, 0.81std)
CairoMakie 25.65s (25.46, 25.83) 0.12+- 17.89s (17.68, 18.01) 0.13+- 2.56s (2.52, 2.61) 0.03+- 10.50ms (10.30, 10.84) 0.18+- 4.18ms (4.09, 4.25) 0.05+-
master 26.19s (26.00, 26.35) 0.14+- 17.85s (17.63, 18.00) 0.15+- 2.57s (2.52, 2.65) 0.04+- 10.52ms (10.43, 10.60) 0.07+- 4.20ms (4.10, 4.32) 0.08+-
evaluation -2.10%, -0.54s faster ✓ (-4.07d, 0.00p, 0.13std) +0.26%, 0.05s invariant (0.33d, 0.54p, 0.14std) -0.21%, -0.01s invariant (-0.14d, 0.80p, 0.04std) -0.24%, -0.02ms invariant (-0.18d, 0.75p, 0.13std) -0.40%, -0.02ms invariant (-0.25d, 0.65p, 0.06std)
WGLMakie 45.38s (42.68, 46.57) 1.41+- 33.88s (31.78, 35.92) 1.68+- 55.01s (52.53, 57.24) 1.82+- 23.98ms (21.64, 26.76) 1.82+- 3.10s (3.01, 3.18) 0.06+-
master 42.10s (39.46, 43.47) 1.53+- 35.56s (34.93, 36.13) 0.37+- 57.19s (55.26, 59.34) 1.35+- 20.40ms (19.49, 22.04) 0.79+- 2.04s (1.97, 2.11) 0.05+-
evaluation +7.24%, 3.29s slower❌ (2.24d, 0.00p, 1.47std) -4.96%, -1.68s faster ✓ (-1.38d, 0.04p, 1.02std) -3.97%, -2.19s faster ✓ (-1.36d, 0.03p, 1.59std) +14.91%, 3.57ms slower❌ (2.55d, 0.00p, 1.30std) +34.19%, 1.06s slower❌ (19.54d, 0.00p, 0.05std)

@SimonDanisch
Copy link
Member Author

Ok, I finally fixed the issue with JuliaLang/julia#47184 (review) by, drumroll, changing Float16 in https://github.com/MakieOrg/Makie.jl/blob/master/src/utilities/texture_atlas.jl#L284, to Float32...
I can confirm the observation, that also just re-evaling the function fixes the issue as well...
I'm guessing we have some problems emitting function signatures for Float16 in the image?
I put the code_native in diff checker, but only found the function counters to change between the working and buggy version:
https://www.diffchecker.com/ou3VbpAO
@vchuravy do you have any ideas? 😅
MWE:

using GLMakie
atlas = Makie.get_texture_atlas();
p = Makie.render_path(Makie.to_spritemarker(:utriangle));
heatmap(Makie.sdistancefield(p, 5, 12), colorrange=(-40, 40)) # will be a flat image when not working

open("not-faulty.txt", "w") do io
    code_native(io, Makie.sdistancefield, Tuple{Matrix{UInt8}, Int, Int}, debuginfo=:none)
end

Should also Work with older makie versions, I suppose!

@SimonDanisch
Copy link
Member Author

Anyone against changing the arrow marker in arrows from '▲' to :utriangle? This needs a reference image update...

@SimonDanisch
Copy link
Member Author

This seems to be a real change:
Screencast from 2022-12-15 19-50-10.webm
@ffreyer how could we best check if this is a regression?
I wouldn't be surprised, if I actually fixed some bug with all these globals being referenced before..
Of course there's also a good chance I messed something up... We don't have tests checking the alignment in a way that's immediately visible, do we?

@ffreyer
Copy link
Collaborator

ffreyer commented Dec 16, 2022

I remember counting pixels for a few sizes, probably with rect marker. I guess we could check the exact sub-matrix for a marker in the atlas (width, height, values at a given pixelsize and font), and the relevant stuff GLMakie etc produces (uvs, quad scale, position)?

Comment on lines 55 to 56
# We use float max here to avoid texture bleed. See #2096
fill(Float16(0.5PIXELSIZE_IN_ATLAS[] + GLYPH_PADDING[]), initial_size...),
fill(Float16(0.5pix_per_glyph + glyph_padding), resolution, resolution),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like I didn't update this when I switched away from floatmax.

The fill value there is the maximum distance from the center of a glyph. With stroke- and glowwidth the background can become relevant during rendering and floatmax to some value can become a harsh cut-off. With this intermediate value it's less harsh (though still there). Basically this is closer to the values you'd get with more padding, which gives you softer transitions with large glow/strokewidths.

uv_width = Vec(lbrt[3] - lbrt[1], lbrt[4] - lbrt[2])
full_pixel_size_in_atlas = uv_width .* Vec2f(size(ta.data) .- 1)
return full_pixel_size_in_atlas ./ Makie.PIXELSIZE_IN_ATLAS[]
full_pixel_size_in_atlas = uv_width .* Vec2f(size(atlas) .- 1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be size(atlas) now as well, I think. Same with other divisions or multiplications by size(atlas) - 1).

# 0 based
idx_left_bottom = minimum(uv_pixel)
idx_right_top = maximum(uv_pixel)

# transform to normalized texture coordinates
# -1 for indexing offset
uv_left_bottom_pad = (idx_left_bottom) ./ tex_size
uv_right_top_pad = (idx_right_top .- 1) ./ tex_size
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh and this seems to work better 0.75 instead of 1, but I don't understand why. Might be specific to the example we were looking at?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the #define ANTIALIAS_RADIUS 0.8 be relevant in that context?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.8 works better though it doesn't seem to be sensitive to what ANTIALIAS_RADIUS is set to...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I currently have it set to 0 in sprites.geom and distance_shape.frag, testing with p = BezierPath([MoveTo(0, 0), LineTo(0.5, 0.5), LineTo(0.5, -0.5), LineTo(-0.5, -0.5)]) to throw in something else.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 0.8 also better in WGLMakie? I only checked for GLMakie

@vchuravy
Copy link
Contributor

@vchuravy do you have any ideas? 😅

If you can narrow it down to the behavior of a particular function that would help.

code_llvm will sadly lie to you and not give you the version that went into the pkgimage

@github-actions
Copy link
Contributor

Missing reference images

Found 1 new images without existing references.
Upload new reference images before merging this PR.

@github-actions
Copy link
Contributor

Missing reference images

Found 1 new images without existing references.
Upload new reference images before merging this PR.

@github-actions
Copy link
Contributor

Missing reference images

Found 1 new images without existing references.
Upload new reference images before merging this PR.

@ffreyer
Copy link
Collaborator

ffreyer commented Dec 19, 2022

To explain the changes I made:

The uv_right_top_pad = (idx_right_top .- 0.8) ./ tex_size is now just uv_right_top_pad = idx_right_top ./ tex_size. With this the uv coordinates are now flush with the outer pixel borders. Before we were excluding one pixel. For reference, this shows the texture atlas in a 0..1 range like opengl uses it with the uv rect of the "L" glyph. Before this was cutting out 80% (100% with -1) of the right/bottom part of the glyph.

ta = Makie.get_texture_atlas()
fig, ax, p = image(0..1, 0..1, ta.data, interpolate = false)
uv = Makie.glyph_uv_width!(ta, 'L', Makie.defaultfont())
x0, y0, x1, y1 = uv
r = Rect2f(x0, y0, x1-x0, y1-y0)
lines!(ax, r, color = :red)
fig

image

I also get_uv_img to match these changes.

The second problem here is with the downsampling in sdistancefield . If the input image is not dividable by downsample we expand (ceil) the image, which effectively shrinks the data shown in it. Using floor instead would shrink the image, making the magnifying the data.

For Bezierpaths we can avoid this problem by matching the generated image size to downsample which I did. This should fix the issues with Bezierpath markers being a couple pixels too small (or to large with floor) at large markersizes.

I'm not sure if this is a problem for characters but I guess it probably is. It might be more correct to set pix_per_glyph to a multiple of 5, or maybe it doesn't matter because the rendered character is an arbitrary size image? It shouldn't be a problem. We don't rely on the size of the rendered character, so even if downsampling introduces padding, it doesn't affect the later scaling factors. Those are based on pix_per_glyph and glyph_padding which are not changed. What should change depending on how downsampling goes is how large the quad is and that should compensate whatever changes are made there.

@github-actions
Copy link
Contributor

Missing reference images

Found 1 new images without existing references.
Upload new reference images before merging this PR.

@github-actions
Copy link
Contributor

Missing reference images

Found 1 new images without existing references.
Upload new reference images before merging this PR.

@ffreyer
Copy link
Collaborator

ffreyer commented Dec 20, 2022

Simon noticed that glow didn't work correctly anymore:
Screenshot from 2022-12-20 15-45-10

Afaik when opengl samples near the edge of a pixel it'll interpolate between that pixel and the neighboring pixel. So when we set the uv's for a glyph tight to the outer edges in the texture atlas, it'll consider the pixels around the glyph as well. (I forgot about this yesterday...)
Now in this case the glyph had other glyphs left and right of it, but not above and below. So the glow value decayed faster up and down. To fix that I moved the border to pixel centers, which does seem to fix that.

For reference, the uv Rect of the scatter marker now looks like this:
Screenshot from 2022-12-20 15-37-14

I guess this should also fix the line artifacts that are sometimes visible with glow and make the background value entirely irrelevant as we never interpolate to it!?

@SimonDanisch
Copy link
Member Author

Ok, I think this is finally ready! Thanks a ton @ffreyer! WGLMakie looks a bit broken, but I think that has been the case before and should get fixed in #2428.
@vchuravy, sorry, I'm running out of time here to reproduce this more succinctly :( I did try for a while, but it's really flaky.
E.g., Makie.TextureAtlas().data was producing the wrong values as well, which seemed like a great target for an MWE, since it's just:

function TextureAtlas(; resolution=2048, pix_per_glyph=64, glyph_padding=12, downsample=5)
    return TextureAtlas(
        RectanglePacker(Rect2{Int32}(0, 0, resolution, resolution)),
        Dict{UInt32, Int}(),
        # We use the maximum distance of a glyph as a background to reduce texture bleed. See #2096
        fill(Float16(0.5pix_per_glyph + glyph_padding), resolution, resolution),
        Vec4f[],
        pix_per_glyph,
        glyph_padding,
        downsample,
        Function[]
    )
end

and should return data == fill(Float16(44), resolution, resolution) but from time to time it returned fill(Float16(2.29e-5), resolution, resolution)... It always filled it with the same incorrect value, but as soon as I started trying to make an MWE or change something unrelated and recompile, it would start returning 44 correctly 🤷
Not sure what other kind of global state could be involved here to create this flaky behavior.

@SimonDanisch SimonDanisch merged commit 641023c into master Dec 20, 2022
@SimonDanisch SimonDanisch deleted the sd/texatlas-refactor branch December 20, 2022 18:48
@jkrumbiegel jkrumbiegel mentioned this pull request Dec 27, 2022
t-bltg pushed a commit to t-bltg/Makie.jl that referenced this pull request Dec 31, 2022
…rg#2498)

* uff

* remove globals from texture atlas code

* fix remaining problems + download from releases

* update test

* fix vectorized arguments

* using stable hashes

* fix test

* better packing + clean ups

* fix problems with native code caching

* small offset fix

* small fixes

* add alignment test

* change name

* fix scaling issues?

* match Bezier path render size to downsample

* update tests with glyph alignment

* update streamplot arrow sizes

* switch to pixel centered uv's

Co-authored-by: ffreyer <frederic481994@hotmail.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants