When transforming back from a complex transform to a simpler transform,
the resulting clip might turn out to clip everything, because clips can
grow while transforming, but the scissor rect won't. So when this
process happens, we can end up with an empty clip by transforming:
1. Set a clip that also sets the scissor
2. transform in a way that grows the clip, say rotate(45)
3. modify the clip to shrink it
4. transform in a way that simplifies the transform, say another
rotate(45)
5. Figure out that this clip and the scissor rect do no longer overlap
Catch this case and avoid drawing anything.
Add a new GskShadow2 struct that has a GdkColor instead of
a GdkRGBA, and a new constructor and getter to go along with
it.
With this commit, my_color_get_depth is no longer used and
has been dropped.
We know it at begin_frame() time, so if we pass it there instead of
end_frame(), we can use it then to make decisions about opacity.
For example, we could notice that the whole surface is opaque and choose
an RGBx format.
We don't do that yet, but now we could.
This is poking into the surface directly, so not a good idea.
And I want to hide that struct in the priv member.
Technically, this code should look at the opaque region, but I am lazy
and the GL renderer is on its way out, so I think it's not worth doing.
Make sure both GL renderers don't leave their contexts alive via the
current context, but ensure they dispose of them properly.
Fixes issues when the corresponding GL resources in the surfaces they
were attached to go away.
GLContexts marked as surface_attached are always attached to the surface
in make_current().
Other contexts continue to only get attached to their surface between
begin_frame() and end_frame().
All our renderer use surface-attached contexts now.
Public API only gives out non-surface-attached contexts.
The benefit here is that we can now choose whenever we want to
call make_current() because it will not cause a re-make_current() if we
call it outside vs inside the begin/end_frame() region.
Or in other words: I want to call make_current() before begin_frame()
without a performance penalty, and now I can.
... and pass the opaque region of the node.
We don't do anything with it yet, this is just the plumbing.
The original function still exists, it passes NULL which is the value
for no opaque region at all.
If we apply a rounded clip, we might change the clip in a way that makes
it intersectable with the scissor again, if both had diverged before.
So try and intersect with the clip.
In the case of no offloading, we want to pass through to the child
(which is likely a big texture doing occlusion).
In the case of punching a hole, we want to punch the hole and not draw
anything behind it, so we start an occlusion pass with transparency.
And in the final case with offloading active, we don't draw anything,
so we don't draw anything.
This should fix concerns about drawing the background behind the video
as mentioned for example in
https://github.com/Rafostar/clapper/issues/343#issuecomment-1445425004
Container nodes save their opaque region, so it's quick to access. Use
that to check if the largest opaque region even qualifies for culling -
and if not, just exit.
Speeds up walking node trees by a lot.
Now that we can specify the min size for an occlusion pass, we can
specify that we want the full clip rect to be occluded for occlusion to
trigger.
The benefit of this is that for partial redraws we almost
always get the background color to cover the redrawn rectangle, so
occlusion will kick in.
When trying to cull, try culling from the largest rectangle of the
remaining draw region first. That region has the biggest chance of
containing a large area to skip.
As a side effect, we can stop trying to cull once the largest rectangle
isn't big enough anymore to contain anything worth culling.
When querying clip bounds, also check the scissor rect, because
sometimes that one is tighter than the clip bounds, because the clip
bounds need to track some larger rounded corners.
Makes a few tests harder to break.
Instead of requiring an occlusion pass to cover the whole given scissor
rect, allow using a smaller rect to start the pass.
When starting such a pass, we adjust the scissor rect to the size of
that pass and do not grow it again until the pass is done.
The rectangle subtraction at the end will then take care of subtraction
that rectangle from the remaining pixels.
To not end up with lots of tiny occlusion passes, add a limit for how
small such a pass may be.
For now that limit is arbitrarily chosen at 100k pixels.
gsk_gpu_node_processor_rect_to_device() is a useful function to have,
even if it has to return FALSE sometimes when there is no simple 1:1
mapping - ie when the modelview contains a rotation.
Instead of just iterating over all the rectangles of the region,
always draw the first rectangle of the region and subtract it when done.
This sounds more complicated, but it will allow us to modify the
rectangle in future commits.
Add a function that tracks whether a render node's content is
in a wide gamut color state (in practice, that means non-sRGB).
This will be used in render_texture to determine the color
state to use when creating a texture.
When creating images for use with different colorstates, ensure that
they have the depth of that colorstate. Otherwise we might lose accuracy
due to quantization.
Fixes mipmaps in rec2100 being rendered as RGBA8.
Pass the ccs, opacity and GdkColors to the op to let it make
decisions about color conversion. Also, reorder the offset to
follow the same order as the color ops.
Update the callers.
We can't use gsk_gpu_node_processor_color_states_self() for ops which
apply alt to colors that don't come from textures, since those are
always unpremultiplied.
This fixes the + and - in disabled spin buttons appearing completely
white.
Allow defining cicp color states with an @-rule:
@cicp "jpeg" {
primaries: 1;
transfer: 13;
matrix: 6;
range: full;
}
And allow using them in color() like this:
color("jpeg" 50% 0.5 1 / 75%)
Note that custom color states use a string, unlike default color
states which use an ident.
Test included.
And allow using color states for colors with a syntax similar
to modern css color syntax.
color(srgb 50% 0.5 1 / 75%)
Both floating point numbers and percentages can be used.
Currently, this is only supported for color nodes.
Test included.
Use the color state returned by this function instead of assuming
the color of a color node is always sRGB.
Node colors are converted to the css on the cpu. That is necessary
since we don't know if they are in one of the default color states,
and our shaders can't deal with non-default color states.
Make color-related ops take the ccs and a GdkColor, and make
decisions about color conversion on the cpu vs the gpu.
This makes the node processor code simpler, and lets use convert
the color directly into the op instance without extra copying.
We also pass opacity to the op, so it can be applied when we
write the color into the instance.
Lastly, rorder the offset to come right after the opacity argument.
Treat the color and rounded color ops the same way.
Update all callers.
With this, the prepare_color apis in gskgpunodeprocessor.c are
no longer used and have been dropped.
We want to reuse gsk_gpu_color_to_float() for use with GdkColor and this
function will be replaced. But until that's fully done, we need 2
different names.
So rename this one to something else
It turns out the "step" variable could up as 0 when p.y ~= 3.0 ||
p.y ~= r.y - 3.0
That was not enough to trigger it though because if "start" and "end"
were the same value, the "y <= end" check in the loop would immediately
terminate it.
However, if start + epsilon == end so that end != start but (end - start)
/ 7 == 0, then step would end up as 0 and the loop would never
terminate.
And if that happened, it would bring down GPUs.
So recode this whole machinery to make it impossible to infloop.
Fixes#6896
The fix in commit 5e7f227d broke shadows while trying to make them
faster.
So use a better way to make them faster.
With the normalized blur radius, we can now conclude that all the values
too far from p.y will cause the gauss() call to return close to 0, so we
can skip any y value that is too far from p.y.
And that allows us to put an upper limit on the loop iterations.
Tests included
Fixes#6888
Instead of doing complicated math, normalize the values to a sigma
of 1.0, and then use that.
This should also be beneficial for shader performance, because 1.0 is a
constant and constant-elimination can kick in on the inlined functions.
When we allocate a graphene_point_t on the stack, there's no guarantee
that it will be aligned at an 8-byte boundary, which is an assumption
made by gsk_pathop_encode() (which wants to use the lowest 3 bits to
encode the operation). In the places where it matters, force the
points on the stack and embedded in structs to be nicely aligned.
By using a distinct type for this (a union with a suitable size and
alignment), we ensure that the compiler will warn or error whenever we
can't prove that a particular point is, in fact, suitably aligned.
We can go from a `GskAlignedPoint *` to a `graphene_point_t *`
(which is always valid, because the `GskAlignedPoint` is aligned)
via &aligned_points[0].pt, but we cannot go back the other way
(which is not always valid, because the `graphene_point_t` is not
necessarily aligned nicely) without a cast.
In practice, it seems that a graphene_point_t on x86_64 *is* usually
placed at an 8-byte boundary, but this is not the case on 32-bit
architectures or on s390x.
In many cases we can avoid needing an explicit reference to the more
complicated type by making use of a transparent union. There's already
at least one transparent union in GSK's public API, so it's presumably
portable enough to match GTK's requirements.
Increasing the alignment of GskAlignedPoint also requires adjusting how
a GskStandardContour is allocated and initialized. This data structure
allocates extra memory to hold an array of GskAlignedPoint outside the
bounds of the struct itself, and that array now needs to be aligned
suitably. Previously the array started with at next byte after the
flexible array of gskpathop, but the alignment of a gskpathop is only
4 bytes on 32-bit architectures, so depending on the number of gskpathop
in the trailing flexible array, that pointer might be an unsuitable
location to allocate a GskAlignedPoint.
Resolves: https://gitlab.gnome.org/GNOME/gtk/-/issues/6395
Signed-off-by: Simon McVittie <smcv@debian.org>
Similar to the previous commit, to avoid undefined behaviour we need
to avoid evaluating out-of-bounds shifts, even if their result is going
to ignored by being multiplied by 0 later.
Detected by running a subset of the test suite with
-Dsanitize=address,undefined on x86_64.
Signed-off-by: Simon McVittie <smcv@debian.org>
If, for example, e == 0, it is undefined behaviour to compute an
expression involving an out-of-range shift by (125 - e), even if the
result is in fact irrelevant because it's going to be multiplied by 0.
This was already fixed for the memorytexture test in
commit 5d1b839 "testsuite: Fix another ubsan warning", so use the
implementation from that test everywhere. It's in the header as an
inline function to keep the linking of the relevant tests simple:
its only caller in production code is fp16.c, so there will be no
duplication outside the test suite.
Detected by running a subset of the test suite with
-Dsanitize=address,undefined on x86_64.
Signed-off-by: Simon McVittie <smcv@debian.org>
With the changes in !7473 we now use sampler2D arguments in functions.
However, when there's a function we call with a samplerExternalOES -
which means we need to overload it with that shader variant.
We were using slightly different numbers here, which isn't good.
The matrices in gdkcolordefs.h are tested in the colorstate-internal
tests, so they are at least properly inverse, and the products match.
It would be better to generate the glsl definitions, somehow.
When we the image color state is not a default one, use the cicp
convert op to convert it to the ccs. And when the target color
state is a non-default one, use the shader in the reverse direction.
This shader receives cicp parameters via uniforms, and converts
the texture data from or to the output colorstate. It computes
the matrix in the vertex shader, and then picks the eotf/oetf
according to the cicp parameters in the fragment shader.
We were passing the wrong rect to the clip mode computation, resulting
in a rounded rect every time, even though it should pretty much always
be unclipped.
The visual results are unaffected, because the clip sent to the shader
was still correct.
Instead of allocating one large descriptor pool and hoping we never run
out of descriptors, allocate small ones dynamically, so we know we never
run out.
Test incldued, though the test doesn't fail in CI, because llvmpipe
doesn't care about pool size limits. It does fail on my AMD though.
A fun side note about that test is that the GL renderer handles it best
in normal operationbecause it caches offscreens per node and we draw the
same node repeatedly.
But, the replay test expands them to duplicated unique nodes, and then
the GL renderer runs out of command queue length, so I had to disable
the test on it.
There is now a GskGpuYcbcr struct that maintains all the Vulkan
machinery related to YCbCrConversions.
It's a GskGpuCached, so it will make itself go away when it is no longer
used, ie a video stopped playing.
Now that we don't use the fancy features anymore, we don't need to
enable them.
And that also means we don't need an env var to disable it for testing.
Now that we don't do fancy texture stuff anymore, we don't need fancy
shaders either, so we can just compile against Vulkan 1.0 again.
And that means we need no fallback shaders for Vulkan 1.0 anymore.
Instead of trying to cram all descriptors into one large array and only
binding it at the start, we now keep 1 descriptor set per image+sampler
combo and just rebind it every time we switch textures.
This is the very dumb solution that essentially maps to what GL does,
but the performance impact is negligible compared to the complicated
dance we were attempting before.
Rewrite all shaders to use 2 predefined samplers called GSK_TEXTURE0 and
GSK_TEXTURE1 instead of wrapper functions.
On GL and Vulkan compat mode, these map directly to samplers.
On Vulkan proper, they map to 2 indices into the texture array, like
before.
From now on, the old nvidia GPUs - ie the 3xx drivers - should start
working again.
Fixes: #6564Fixes: #6574Fixes: #6654
This allows GskGpuFrame implementations to store data per vertex
attribute.
This is just the plumbing, no actual implementation is done in this
commit.
This guarantees that the images get ID 0 and 1 (on GL), which is going
to be quite important for the next steps.
Just for funsies, here's fps numbers on my desktop for this change:
NGL 1500 => 1400
Vulkan 2650 => 2250
This by itself is just more work refcounting all those images, but
there's actually a goal here, that will become visible in future
commits.
But this is split out for correctness and benchmarking purposes (the
overhead from refcounting seems to be negligible on my computer).
Just define GSK_N_TEXTURES in every glsl file, extract that #define in
the python parser and emit a static const uint variable
"{shader_name}_n_textures" in the generated header.
It's a struct collecting all relevant info for a texture passed to a
shader.
The ultimate goal is to get rid of the descriptors and let ops
manage them on thir own.
If GskGpuCache has an idea of what time it is, cached items can use that
time to update their last-use time instead of having to carry it around
throught function calls everywhere.
Port an optimization of the GL renderer where it fast-paths crossfades
with progress <= 0 and >=1 - which should really never happen because
nobody should emit them in the first place, but oh well.
We no longer hardcode the few different classes we have, but generically
walk over all classes.
As a side effect we now get new classes added to stats automatically.
The content itself did not change.
Commit 1580490670 included a reordering of
acquiring the frame before making the context current.
Sometimes (like at startup) new frames need to be created.
Setting up a new frame assumed the GL context was current.
Change it so that we delay the one GL setup we do in frames until later.
Vulkan requires us waiting on the image acquired from
vkAcquireNextImageKHR() before we start rendering to it, as that
function is allowed to return images that are still in use by the
compositor.
Because of that requirement, vkAcquireNextImageKHR() requires a
semaphore or fence to be passed that it can signal once it's done.
We now use a side channel to begin_frame() - calling
set_draw_semaphore() - to pass that semaphore so that the
vkAcquireNextImageKHR() call inside begin_frame() can use it, and then
we can wait on it later when we submit.
And yes, this is insanely convoluted, the Vulkan developers should
totally have thought about GTK's internal designs before coming up
with that idea.
These are just factoring out gdk_draw_context_begin/end_frame() so I can
add one tiny thing there later.
And I did both even though I only need one, because it felt wrong to
just do one.
Make the function look like that:
1. handle special case
2. maybe GC
3. draw
4. queue next gc
5. cleanup
This seems like the sanest approach to avoid gc() collecting things
necessary for drawing in the future.
And I need to refactor stuff, so having it out of the way is a good
idea.
... and plumb the color state through the downloading machinery, where
no matter what path it takes it ends up in
gdk_memory_convert_color_state() or gdk_memory_convert().
The 2nd of those has been expanded to optionally do colorstate
conversion when the 2 colorstates are different.
When a cache item is invalid, don't move it into the hash table.
Instead, just delete it.
Something like this could happen:
1. A texture is cached
In the case of #6867 this would be a webpage in epiphany.
2. The texture cache item is garbage-collected
For example, epiphany might switch to a new tab, and the previous page's
texture will remain. After 15s or so, we collect our item for that
texture.
3. The texture is cached again, but in the target colorspace
We now decide we need the texture again, but not in any colorspace, we
need it in the target colorspace. This might be because we run an
effect on it (like a crossfade) or because we want mipmaps (like in the
overview map, where its zoomed out).
4. The old invalid item is transitioned into the hash table
We now have an invalid item in the hash table. This is extra bad,
because it had only one reference (from the texture), but we treat it
like it has 2 (from us in the hash table and from the texture).
So depending on if the texture is freed before we reuse it, we get
different results: If it was free, we get invalid memory accesses, if it
was not freed, we treat it like a valid cache item and think the image
inside is still valid.
Fixes#6867
gsk_gpu_device_gc() may release the last ref on the GskGpuDevice,
leading to memory corruption when setting priv->cache_gc_source = 0.
Includes a bit of refactoring, so the ref/unref wraps nicely around the
actual code.
Fixes crashes seen after using the inspector and closing the window,
thereby closing all windows of a display and releasing all references to
the device.
Fixes#6861
Change the glsl convert_color function to proceed in stages:
- first unpremultiply
- then linearize
- then transform linearly
- then delinearize
- then premultiply
All the steps are only taken if needed.
We need to make sure our clear values are in the right colorstate, not
in sRGB.
The occluision culling managed to sneak through the big transition for
that.
This is actually the node Loupe is using, so having tiling work with it
is important.
Because of the previous commit, different filters are supported fine.
Fixes: #6324
This allows mipmapping if downscaled a lot, like we do for non-tiled
images.
A side effect is that due to the simpler caching for tiles, we can only
cache the mipmapped images in one colorstate. But we need to pick a
potentially non-default one, because we want to mipmap in a linear
colorstate.
So this is somewhat suboptimal. Patches with improvements accepted.
Use the new cache feature to split oversized textures into tiles the
size given by the new device API.
Then number those tiles from left to right and top to bottom and use
that number as the tile id.
When we draw large images, we absolutely do not want to keep memory that
we do not need. So do a GC run after every tile. That otentially slows
down things, but it also improves the chances of not running out of
memory.
Here's the node for the image I managed to create after I applied this
patch:
repeat {
bounds: 0 0 50000 50000;
child: text {
font: "Noto Color Emoji 10000px";
glyphs: 661 0 0 0 color;
offset: 0 10000;
hint-style: none;
}
}
Functions should behave as I expect, and I just spent an hour debugging
a refcount issue because I assumed our image creation functions return
refrences. Which is a very sane assumption.
The texture and texture-scale node code is creating image copies
for mipmaps and to adapt to the compositing colorstate.
Those texture should be cached.
We want to cache textures in the compositing color state, not in their
original color state. However, the compositing color state may change
(think multimonitor setups).
So we additionally keep a cache per colorstate.
That means texture lookup is now a 3-step process:
1. Look up in the compositing colorstate's cache
2. Look up in the general cache
3. Upload
GL_SRGB is doing postmultiplied alpha, so if the texture is
premultiplied, we can't use this optimization.
The optimization still works for unpremultiplied and opaque images,
because those don't do that step.
No colorstate conversions allowed here, though technically we could use
the alternate color state for the source most of the time, as the mask's
colorstate is only relevant for luminance.
This is a function that's meant to be used whenever both color states
of the shader are equal. In that case no colorspace conversion code
needs to be created and shaders can be shared.
The GL renderer is using FLOAT32 instead of GL_SRGB, which is screwing
up the node-editor by making it turn on high bit depth unconditionally.
So until someone fixes the GL renderer properly, do this quickfix.
That way, we can use it in one other place where we want to use mipmaps.
I don't really like it because it adds yet another argument,
but then the one new caller was selecting suboptimal shaders, and that's
worse.
The colormatrix shade does a whole matrix multiplication, which is
absolutely not necessary.
The convert shader has builtin opacity handling and when the colorstates
match will do no conversion.
The alternative color state is used as the interpolation color state.
Colors are transformed into that space on the CPU.
For now we set the interpolation color state to SRGB, because ultimately
we want to let callers specify it, so having something that's easy to
map to that behavior is desirable.
Otherwise we might have chosen to interpolate in the compositing
colorstate.
It also means that we need to premultiply colors on the CPU now because
of the limitations of the shader colorstates APIs.
This makes use of the GskGpuColorStates by setting the ccs as output
colorstate and the color's colorstate as alternative color state.
The shader adaption is very straightforward because of that.
This is the first op to obey the compositing color state. This means
from now on until all ops obey the ccs rendering is broken when ccs is
not set to linear.
I'll keep individual ops in seperate commits for easier review, because
they all need different adaptations.
Render to an offscreen and add a final conversion if the target
colorstate is not a rendering colorstate.
This now allows the GPU renderer to render to any colorstate.
Makes the verbose output (a lot) more verbose, but it makes the
colorstates used in the shaders very visible.
And it will be relevant once people start using different colorstates
everywhere (like oklab for gradients/colors and so on).
This adds the following functions:
output_color_from_alt()
alt_color_from_output()
Converts between the two colors
output_color_alpha()
alt_color_alpha()
Multiplies a color with an alpha value
This adds a GdkColorStates that encodes 2 of the default GdkColorStates
and wether their values are premultiplied or not.
Neither do the shaders do anything with this information yet, nor do the
shaders do anything with it yet, this is just the plumbing.
If desired, try creating GL_SRGB images. Pass a try_srgb boolean down to
the image creation functions and have them attempt to create images like
that.
When it is not possible to create srgb images in the given format, just
fall back to regular images. The calling code is meant to check the
GSK_GPU_IMAGE_SRGB flags to determine the actual format of the resulting
image.