gsk_vulkan_render_download_target() currently resets the uploader
objects before downloading the image that it produces. This is
problematic because there might be unreleased buffers and images
in the command queue.
In particular, this can make validation layers complain about the
glyph atlas - of all things! - upload buffer being released while
still being used by the command queue.
Fix that by resetting the uploader after downloading the image.
The current implementation of the glyph cache deals with atlases by
padding them with 1 pixel at the beginning, at the end, and between
each glyph.
That's cool and all, however, there's a very subtle problem with
this approach: the contents of the atlas are garbage, so this padding
is filled with garbage memory!
Rework the Vulkan glyph cache to draw each and every glyph in a
surface that has 1 pixel border of padding around it. Ensure the
surface is completely black by drawing a rectangle before handing
it to Pango to draw the glyph. Update tx and ty to pick the texture
position adjusted to the 1 pixel padding. The atlas now starts at
position (0, 0), since each glyph individually contains its own padding.
To improve legibility, add a PADDING define and use it everywhere.
Vulkan renders text using VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA and
VK_BLEND_FACTOR_SRC_ALPHA, but that implies per-channel alpha
blending, which currently produces the wrong results when blending
glyphs with the images beneath them.
Use the default pipeline constructors, which implies using the
ONE and ONE_MINUS_SRC_ALPHA.
Basically what GL does, but without any debug or feature flag
to gatekeep it, since the Vulkan backend itself is experimental
already.
Ceil surface sizes, and floor coordinates, to the fractional scale
value.
The rects passed to the clip region are in buffer coordinates, and
must not be scaled. Consider the following scenario: Wayland, with
a 1024x768@2 window. That gives us a 2048x1536 raw image. To setup
the Vulkan render pass code, we'd scale 2048x1536 *again*, to an
unreasonable 4196x3072, which is (1) incorrect and (2) really
incorrect and (3) can lead to crashes at best, full GPU resets
at worst - and a GPU reset is incredibly not fun!
Now that we pass the right clip regions at the right coordinates
at all times, remove the extra scaling from the render pass.
This part of the Vulkan renderer is almost exactly equal to the GL
renderer, and the GL renderer already does that since at least
2a38cecd33. Copy that into the Vulkan renderer.
A nice side effect from this commit is that resizing a window now
actually works again.
Sneak in a trivial cleanup by using a variable to hold the draw
index.
This was a tricky one to figure out, but it's pretty simple to
understand (I hope!).
So, this AMD card I'm using requires buffer memory sizes to be
aligned to 16 bytes. Intel is aligned to 4 bytes I think, but
AMD - or at least this AMD model in particular - uses 16 bytes
for alignment.
When creating a a particular texture (I did not determin which one
specifically!) a buffer of size 1276 bytes is requested.
1276 / 16 = 79.75, which is clearly not aligned to the required
16 bytes.
We request Vulkan to create a buffer of 1276 bytes for us, it
figures out that it's not aligned, and creates a buffer of 1280
bytes, which is aligned. The extra 4 bytes are wasted, but that's
okay. We immediately query this buffer for this exact information,
using vkGetBufferMemoryRequirements(), and proceed to create actual
memory to back this buffer up.
The buffer tells us we must use 1280 bytes, so we pass 1280 bytes
and everyone is happy, right? Of course not. We pass 1276 bytes,
and Vulkan is subtly unhappy at us.
Fix that by passing the value that Vulkan asks us to use, i.e.,
the size returned by vkGetBufferMemoryRequirements().
This is what GL does, and for a reason: it can lead to width or
height for very small glyphs. Also, switch to dividing by a float
(1024.0) instead of an integer (1024).
This doesn't make any difference now, but will allow us to copy
subregions more easily. This is not obvious, but here's a quick
explanation:
Leaving 'bufferRowLength' and 'bufferImageHeight' implies that
Vulkan will assume the size passed in the 'imageExtent' field.
Right now, this assumption is correct - the only user of this
function is the glyph cache, and it only copies and uploads
exact rects. Next commits will change that assumption, so we
must pass 'buffer*' fields, and tell Vulkan, "this part of the
buffer represents an image of width x height, and I want the
subregion (x, y, smallerWidth, smallerHeight) of this image".
When creating an image using gsk_vulkan_image_new_for_framebuffer(),
it passes VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL.
However, this is a mistake. The spec demands that the initial
layout must be either VK_IMAGE_LAYOUT_UNDEFINED or
VK_IMAGE_LAYOUT_PREINITIALIZED.
Apparently this was an oversight from commit b97fb75146, since the
commit message even documents that, and all other calls pass either
VK_IMAGE_LAYOUT_UNDEFINED or VK_IMAGE_LAYOUT_PREINITIALIZED.
Create framebuffer images using VK_IMAGE_LAYOUT_UNDEFINED, which is
what was originally expected.
Instead of tracking a single scale, track x and y scales separately.
Factor out gsk_vulkan_render_pass_new() into a private function that
receives both scales, and pass 'scale_factor' for both.
This is mostly a cosmetic change, and the goal is twofold:
1. Make it easier to spot unimplemented render node types; and
2. Prepare for a small rework
The implementation for each node now lives in specific functions,
like the GL renderer; unlike the GL renderer, however, we use a
node type vtable to map GskRenderNodeType → implementation. Render
node without an implementation map to NULL, and use the fallback
implementation. Render nodes that fail any check and return FALSE
also use fallback implementation.
Add GskMaskNode, and support it in the render node
parser, in the inspector and in GtkSnapshot.
The rendering is just fallback for now.
Based on old work by Timm Bäder.
Having the initial layout set to VK_IMAGE_LAYOUT_GENERAL causes issues
when going from the final layout to the initial layout since the image
layout is expected to be the general layout. Setting the initial layout
to undefined doesn't have this restriction.
Move the resources of each renderer to its subdirectory.
We've previously done that for the ngl renderer, but it
is better to be consistent and do it for all the renderers.
Hook up the "Show fallback rendering" switch for Vulkan.
This brings home the sobering truth that the Vulkan renderer
is doing *all* fallback, since we switched from offset nodes
to transform nodes.
For vulkan/broadway this just means to ignore it, but for the gl
backend we support (with up to 4 texture inputs, which is similar to
what shadertoy does, so should be widely supported).
Language bindings—especially ones based on introspection—cannot deal
with custom type hiearchies. Luckily for us, GType has a derivable type
with low overhead: GTypeInstance.
By turning GskRenderNode into a GTypeInstance, and creating derived
types for each class of node, we can provide an introspectable API to
our non-C API consumers, with no functional change to the C API itself.
When we use if (GDK_PROFILER_IS_RUNNING) this means we get an
inlined if (FALSE) when the compiler support is not compiled in, which
gets rid of all the related code completely.
We also expand to G_UNLIKELY(gdk_profiler_is_running ()) in the supported
case which might cause somewhat better code generation.
UNDEFINED initial layouts may not preserve the contents
of the attachment after transitioning the layout. We want
them to be preserved because we do partial rendering.
Use GENERAL as the initial layout for render passes.
Multiple images in the before barrier array are defined with
VK_ACCESS_TRANSFER_WRITE_BIT and VK_ACCESS_TRANSFER_READ_BIT,
which requires passing VK_PIPELINE_STAGE_TRANSFER_BIT and
VK_PIPELINE_STAGE_HOST_BIT to vkCmdPipelineBarrier().
Pass these flags correctly.