mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Desktop GPU: patched wlroots with a Vulkan DMA-BUF allocator; new windows take the keys
wlroots 0.20.2 (Arch's wlroots0.20, what the desktop's labwc links) gains an
allocator that exports Vulkan images as DMA-BUFs for a GPU node that is not a
DRM device - the Adreno KGSL stand-in - where gbm and the DRM lease path fail
('unable to create allocator'). linux-dmabuf skips its GEM import check on such
a node so client buffers are not all refused. Built by build-wlroots.yml.
Compositor: without a Wine desktop, a window that opens on top takes keyboard
focus (a gamescope started from the Linux desktop).
This commit is contained in:
1 parent
59123f1c89
commit
7dc00d9b23
4 files changed
+657
No files matched your search
@@ -0,0 +1,46 @@
|
||||
name: Build wlroots (patched)
|
||||
|
||||
# The desktop's wlroots (Arch Linux ARM's wlroots0.20, what the desktop package's labwc links)
|
||||
# rebuilt with tools/wlroots/patches: a Vulkan DMA-BUF allocator for the Adreno node, which is not
|
||||
# a DRM device, so labwc's vulkan and gles2 renderers can start. Built in an Arch Linux ARM
|
||||
# container on an arm64 runner and published as a release asset the apk build fetches
|
||||
# (tools/wlroots/release.env names the tag and sha). The hosted desktop package is never changed.
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'tools/wlroots/**'
|
||||
- '.github/workflows/build-wlroots.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to publish as (e.g. wlroots-0.20.2-p1); empty = artifact only'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build in Arch Linux ARM
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker run --rm -v "$PWD:/work" -w /work menci/archlinuxarm:base-devel bash tools/wlroots/build-in-arch.sh
|
||||
sudo chown -R "$(id -u):$(id -g)" .
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wlroots-patched
|
||||
path: |
|
||||
wlroots.tzst
|
||||
wlroots.tzst.sha256
|
||||
- name: Publish
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release create "${{ inputs.tag }}" --title "${{ inputs.tag }}" --notes "wlroots 0.20.2 with tools/wlroots/patches (Vulkan DMA-BUF allocator for non-DRM GPU nodes); staged by the apk at /usr/local/lib/droiddeck-wlroots for the desktop's GPU renderers. sha256 $(cut -d' ' -f1 wlroots.tzst.sha256)" wlroots.tzst wlroots.tzst.sha256 || gh release upload "${{ inputs.tag }}" wlroots.tzst wlroots.tzst.sha256 --clobber
|
||||
@@ -589,6 +589,10 @@ static void map_toplevel(struct surface *s) {
|
||||
s->placed ? "" : " (no desktop position yet)");
|
||||
}
|
||||
wl_list_insert(g_toplevels.prev, &s->toplevel_link); /* new windows start on top */
|
||||
/* Without a Wine desktop the clients are whole programs (the Linux desktop, a gamescope a
|
||||
* program on it started over it): a window that opens on top takes the keys too, as it would
|
||||
* on any desktop, rather than the one clicked last that is now underneath it. */
|
||||
if (!g_desktop) g_key_target = s;
|
||||
apply_zorder();
|
||||
}
|
||||
|
||||
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/bash
|
||||
# Runs INSIDE an Arch Linux ARM container (menci/archlinuxarm:base-devel) on an arm64 runner.
|
||||
# Builds Arch's own wlroots0.20 package - the wlroots the desktop package's labwc links - with the
|
||||
# patches in ./patches added, keeps only the library, and packs it as wlroots.tzst
|
||||
# (usr/local/lib/droiddeck-wlroots/libwlroots-0.20.so) for the apk to stage. The desktop launcher
|
||||
# puts that directory first on labwc's library path for the GPU renderers only.
|
||||
set -euxo pipefail
|
||||
VERSION=${WLROOTS_VERSION:-0.20.2}
|
||||
WORK=/work
|
||||
cd "$WORK"
|
||||
# Same container workarounds as tools/gamescope/build-in-arch.sh.
|
||||
grep -q '^DisableSandbox' /etc/pacman.conf || sed -i 's/^\[options\]/[options]\nDisableSandbox/' /etc/pacman.conf
|
||||
{ for m in https://ca.us.mirror.archlinuxarm.org https://fl.us.mirror.archlinuxarm.org https://de3.mirror.archlinuxarm.org https://nl.mirror.archlinuxarm.org; do echo "Server = $m/\$arch/\$repo"; done; cat /etc/pacman.d/mirrorlist; } > /etc/pacman.d/mirrorlist.new
|
||||
mv /etc/pacman.d/mirrorlist.new /etc/pacman.d/mirrorlist
|
||||
pacman -Syu --noconfirm --needed git sudo zstd binutils
|
||||
id builder >/dev/null 2>&1 || useradd -m builder
|
||||
echo 'builder ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/builder
|
||||
rm -rf pkg && mkdir pkg && cp -r tools/wlroots/patches pkg/ && chown -R builder pkg
|
||||
sudo -u builder bash -c "
|
||||
set -euxo pipefail
|
||||
cd pkg
|
||||
for rel in 1 2 3 4; do
|
||||
if git clone -q --depth 1 --branch ${VERSION}-\$rel https://gitlab.archlinux.org/archlinux/packaging/packages/wlroots0.20.git arch 2>/dev/null; then break; fi
|
||||
done
|
||||
test -f arch/PKGBUILD
|
||||
cd arch
|
||||
grep -q '^pkgver=${VERSION}$' PKGBUILD
|
||||
cp ../patches/*.patch .
|
||||
PATCHES=\$(ls *.patch | sort)
|
||||
{ echo; echo 'source+=('; for p in \$PATCHES; do echo \" \$p\"; done; echo ')'; for p in \$PATCHES; do echo \"sha256sums+=('SKIP')\"; done; } >> PKGBUILD
|
||||
# Arch's PKGBUILD has no prepare(); ours applies the patches to the checkout.
|
||||
cat >> PKGBUILD <<'PREP'
|
||||
|
||||
prepare() {
|
||||
cd \"\$srcdir/\$pkgname\"
|
||||
for p in \$(ls \"\$srcdir\"/*.patch | sort); do
|
||||
echo \"applying \$(basename \"\$p\")\"
|
||||
patch -p1 --no-backup-if-mismatch < \"\$p\"
|
||||
done
|
||||
}
|
||||
PREP
|
||||
makepkg -A -s --noconfirm --skipchecksums --skippgpcheck
|
||||
ls -l *.pkg.tar.*
|
||||
"
|
||||
cd "$WORK"
|
||||
rm -rf out && mkdir -p out/usr/local/lib/droiddeck-wlroots
|
||||
PKG=$(ls pkg/arch/wlroots0.20-*.pkg.tar.* | grep -v -- '-debug-' | head -1)
|
||||
mkdir -p x && tar --use-compress-program=unzstd -xf "$PKG" -C x
|
||||
SO=x/usr/lib/libwlroots-0.20.so
|
||||
test -f "$SO"
|
||||
cp -L "$SO" out/usr/local/lib/droiddeck-wlroots/libwlroots-0.20.so
|
||||
strip --strip-unneeded out/usr/local/lib/droiddeck-wlroots/libwlroots-0.20.so || true
|
||||
# The patch is in: the allocator's symbol and its log line.
|
||||
grep -q 'Created Vulkan DMA-BUF allocator' out/usr/local/lib/droiddeck-wlroots/libwlroots-0.20.so
|
||||
readelf -d out/usr/local/lib/droiddeck-wlroots/libwlroots-0.20.so | grep -E 'NEEDED|SONAME'
|
||||
(cd out && tar --use-compress-program='zstd -19' -cf ../wlroots.tzst usr)
|
||||
sha256sum wlroots.tzst | tee wlroots.tzst.sha256
|
||||
ls -l wlroots.tzst out/usr/local/lib/droiddeck-wlroots/
|
||||
@@ -0,0 +1,549 @@
|
||||
diff --git a/include/render/vulkan.h b/include/render/vulkan.h
|
||||
index f96619f2..9f945f87 100644
|
||||
--- a/include/render/vulkan.h
|
||||
+++ b/include/render/vulkan.h
|
||||
@@ -461,6 +461,10 @@ struct wlr_vk_descriptor_pool *vulkan_alloc_blend_ds(
|
||||
// Frees the given descriptor set from the pool its pool.
|
||||
void vulkan_free_ds(struct wlr_vk_renderer *renderer,
|
||||
struct wlr_vk_descriptor_pool *pool, VkDescriptorSet ds);
|
||||
+// A DMA-BUF allocator on a Vulkan device of its own, for a DRM fd gbm cannot use (see
|
||||
+// render/vulkan/allocator.c).
|
||||
+struct wlr_allocator *vulkan_allocator_create(int drm_fd);
|
||||
+
|
||||
struct wlr_vk_format_props *vulkan_format_props_from_drm(
|
||||
struct wlr_vk_device *dev, uint32_t drm_format);
|
||||
struct wlr_vk_renderer *vulkan_get_renderer(struct wlr_renderer *r);
|
||||
diff --git a/render/allocator/allocator.c b/render/allocator/allocator.c
|
||||
index b7dbf3d6..f3162d47 100644
|
||||
--- a/render/allocator/allocator.c
|
||||
+++ b/render/allocator/allocator.c
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
+#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <wlr/backend.h>
|
||||
#include <wlr/config.h>
|
||||
@@ -13,6 +14,10 @@
|
||||
#include "render/allocator/shm.h"
|
||||
#include "render/wlr_renderer.h"
|
||||
|
||||
+#if WLR_HAS_VULKAN_RENDERER
|
||||
+#include "render/vulkan.h"
|
||||
+#endif
|
||||
+
|
||||
#if WLR_HAS_GBM_ALLOCATOR
|
||||
#include "render/allocator/gbm.h"
|
||||
#endif
|
||||
@@ -32,6 +37,29 @@ void wlr_allocator_init(struct wlr_allocator *alloc,
|
||||
wl_signal_init(&alloc->events.destroy);
|
||||
}
|
||||
|
||||
+/* Whether the fd is a DRM device at all. The GPU node on Android (KGSL, for Adreno) stands in for
|
||||
+ * a render node so that Vulkan and libdrm's device lookup can name the GPU, but it answers no DRM
|
||||
+ * ioctl: drmIsMaster() takes that for mastership, and gbm cannot allocate on it. */
|
||||
+static bool is_drm_device(int fd) {
|
||||
+ drmVersion *version = drmGetVersion(fd);
|
||||
+ if (version == NULL) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ drmFreeVersion(version);
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+#if WLR_HAS_VULKAN_RENDERER
|
||||
+static struct wlr_allocator *try_vulkan_allocator(int drm_fd) {
|
||||
+ wlr_log(WLR_DEBUG, "Trying to create Vulkan allocator");
|
||||
+ struct wlr_allocator *alloc = vulkan_allocator_create(drm_fd);
|
||||
+ if (alloc == NULL) {
|
||||
+ wlr_log(WLR_DEBUG, "Failed to create Vulkan allocator");
|
||||
+ }
|
||||
+ return alloc;
|
||||
+}
|
||||
+#endif
|
||||
+
|
||||
/* Re-open the DRM node to avoid GEM handle ref'counting issues. See:
|
||||
* https://gitlab.freedesktop.org/mesa/drm/-/merge_requests/110
|
||||
*/
|
||||
@@ -109,8 +137,20 @@ struct wlr_allocator *wlr_allocator_autocreate(struct wlr_backend *backend,
|
||||
struct wlr_allocator *alloc = NULL;
|
||||
|
||||
uint32_t gbm_caps = WLR_BUFFER_CAP_DMABUF;
|
||||
+ bool drm_device = drm_fd >= 0 && is_drm_device(drm_fd);
|
||||
+ const char *forced = getenv("WLR_ALLOCATOR");
|
||||
+ bool vk_first = forced != NULL && strcmp(forced, "vulkan") == 0;
|
||||
+#if WLR_HAS_VULKAN_RENDERER
|
||||
+ // A GPU node that is not a DRM device (and WLR_ALLOCATOR=vulkan): buffers come from Vulkan.
|
||||
+ if ((backend_caps & gbm_caps) && (renderer_caps & gbm_caps) && drm_fd >= 0
|
||||
+ && (vk_first || !drm_device)) {
|
||||
+ if ((alloc = try_vulkan_allocator(drm_fd)) != NULL) {
|
||||
+ return alloc;
|
||||
+ }
|
||||
+ }
|
||||
+#endif
|
||||
if ((backend_caps & gbm_caps) && (renderer_caps & gbm_caps)
|
||||
- && drm_fd >= 0) {
|
||||
+ && drm_fd >= 0 && drm_device) {
|
||||
#if WLR_HAS_GBM_ALLOCATOR
|
||||
wlr_log(WLR_DEBUG, "Trying to create gbm allocator");
|
||||
int gbm_fd = reopen_drm_node(drm_fd, true);
|
||||
@@ -124,6 +164,11 @@ struct wlr_allocator *wlr_allocator_autocreate(struct wlr_backend *backend,
|
||||
wlr_log(WLR_DEBUG, "Failed to create gbm allocator");
|
||||
#else
|
||||
wlr_log(WLR_DEBUG, "Skipping gbm allocator: disabled at compile-time");
|
||||
+#endif
|
||||
+#if WLR_HAS_VULKAN_RENDERER
|
||||
+ if (!vk_first && (alloc = try_vulkan_allocator(drm_fd)) != NULL) {
|
||||
+ return alloc;
|
||||
+ }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -138,7 +183,7 @@ struct wlr_allocator *wlr_allocator_autocreate(struct wlr_backend *backend,
|
||||
|
||||
uint32_t drm_caps = WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_DATA_PTR;
|
||||
if ((backend_caps & drm_caps) && (renderer_caps & drm_caps)
|
||||
- && drm_fd >= 0 && drmIsMaster(drm_fd)) {
|
||||
+ && drm_fd >= 0 && drm_device && drmIsMaster(drm_fd)) {
|
||||
wlr_log(WLR_DEBUG, "Trying to create drm dumb allocator");
|
||||
int dumb_fd = reopen_drm_node(drm_fd, false);
|
||||
if (dumb_fd < 0) {
|
||||
diff --git a/render/vulkan/allocator.c b/render/vulkan/allocator.c
|
||||
new file mode 100644
|
||||
index 00000000..4c00d361
|
||||
--- /dev/null
|
||||
+++ b/render/vulkan/allocator.c
|
||||
@@ -0,0 +1,392 @@
|
||||
+#include <assert.h>
|
||||
+#include <drm_fourcc.h>
|
||||
+#include <fcntl.h>
|
||||
+#include <inttypes.h>
|
||||
+#include <stdlib.h>
|
||||
+#include <unistd.h>
|
||||
+#include <wlr/interfaces/wlr_buffer.h>
|
||||
+#include <wlr/render/allocator.h>
|
||||
+#include <wlr/render/drm_format_set.h>
|
||||
+#include <wlr/util/log.h>
|
||||
+#include "render/vulkan.h"
|
||||
+
|
||||
+/*
|
||||
+ * DMA-BUFs allocated by Vulkan, for a GPU whose device node is not a DRM device and so cannot be
|
||||
+ * given to gbm - Adreno through KGSL on Android, where the node Turnip opens stands in for a
|
||||
+ * render node. Each buffer is an exportable image with an explicit DRM format modifier, created
|
||||
+ * the way vulkan_import_dmabuf() imports one for rendering, so the renderer and the parent
|
||||
+ * compositor see the same layout. The device is our own, on the physical device the DRM fd names:
|
||||
+ * this works under the gles2 renderer too, as long as its EGL can import the result.
|
||||
+ */
|
||||
+
|
||||
+struct wlr_vk_allocator {
|
||||
+ struct wlr_allocator base;
|
||||
+ struct wlr_vk_device *dev;
|
||||
+ PFN_vkGetMemoryFdKHR get_memory_fd;
|
||||
+ PFN_vkGetImageDrmFormatModifierPropertiesEXT get_modifier_props;
|
||||
+ struct wl_list buffers; // wlr_vk_allocator_buffer.link
|
||||
+};
|
||||
+
|
||||
+struct wlr_vk_allocator_buffer {
|
||||
+ struct wlr_buffer base;
|
||||
+ struct wlr_vk_allocator *alloc; // NULL once the allocator is gone
|
||||
+ struct wl_list link;
|
||||
+ VkImage image;
|
||||
+ VkDeviceMemory memory;
|
||||
+ struct wlr_dmabuf_attributes dmabuf;
|
||||
+};
|
||||
+
|
||||
+static const struct wlr_buffer_impl buffer_impl;
|
||||
+
|
||||
+static struct wlr_vk_allocator_buffer *buffer_from_buffer(struct wlr_buffer *wlr_buffer) {
|
||||
+ assert(wlr_buffer->impl == &buffer_impl);
|
||||
+ struct wlr_vk_allocator_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
|
||||
+ return buffer;
|
||||
+}
|
||||
+
|
||||
+static void buffer_release_vk(struct wlr_vk_allocator_buffer *buffer) {
|
||||
+ if (buffer->alloc == NULL) {
|
||||
+ return;
|
||||
+ }
|
||||
+ VkDevice dev = buffer->alloc->dev->dev;
|
||||
+ // The exported DMA-BUF keeps the memory alive for whoever imported it.
|
||||
+ if (buffer->image != VK_NULL_HANDLE) {
|
||||
+ vkDestroyImage(dev, buffer->image, NULL);
|
||||
+ }
|
||||
+ if (buffer->memory != VK_NULL_HANDLE) {
|
||||
+ vkFreeMemory(dev, buffer->memory, NULL);
|
||||
+ }
|
||||
+ buffer->image = VK_NULL_HANDLE;
|
||||
+ buffer->memory = VK_NULL_HANDLE;
|
||||
+ wl_list_remove(&buffer->link);
|
||||
+ buffer->alloc = NULL;
|
||||
+}
|
||||
+
|
||||
+static void buffer_destroy(struct wlr_buffer *wlr_buffer) {
|
||||
+ struct wlr_vk_allocator_buffer *buffer = buffer_from_buffer(wlr_buffer);
|
||||
+ wlr_buffer_finish(wlr_buffer);
|
||||
+ buffer_release_vk(buffer);
|
||||
+ wlr_dmabuf_attributes_finish(&buffer->dmabuf);
|
||||
+ free(buffer);
|
||||
+}
|
||||
+
|
||||
+static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer,
|
||||
+ struct wlr_dmabuf_attributes *dmabuf) {
|
||||
+ struct wlr_vk_allocator_buffer *buffer = buffer_from_buffer(wlr_buffer);
|
||||
+ *dmabuf = buffer->dmabuf;
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+static const struct wlr_buffer_impl buffer_impl = {
|
||||
+ .destroy = buffer_destroy,
|
||||
+ .get_dmabuf = buffer_get_dmabuf,
|
||||
+};
|
||||
+
|
||||
+// One vkCreateImage over the modifiers that share the same mutable-sRGB setting, the one thing
|
||||
+// in the import that depends on the modifier. The driver picks the modifier.
|
||||
+static VkImage create_image(struct wlr_vk_allocator *alloc,
|
||||
+ const struct wlr_vk_format_props *props, int width, int height,
|
||||
+ const uint64_t *mods, uint32_t mods_len, bool mutable_srgb) {
|
||||
+ VkImageCreateInfo img_info = {
|
||||
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
+ .imageType = VK_IMAGE_TYPE_2D,
|
||||
+ .format = props->format.vk,
|
||||
+ .mipLevels = 1,
|
||||
+ .arrayLayers = 1,
|
||||
+ .samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
+ .extent = (VkExtent3D){ width, height, 1 },
|
||||
+ .usage = vulkan_render_usage,
|
||||
+ .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
|
||||
+ };
|
||||
+ if (mutable_srgb) {
|
||||
+ img_info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||
+ }
|
||||
+
|
||||
+ VkExternalMemoryImageCreateInfo eimg = {
|
||||
+ .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
||||
+ .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
|
||||
+ };
|
||||
+ img_info.pNext = &eimg;
|
||||
+
|
||||
+ VkImageDrmFormatModifierListCreateInfoEXT mod_list = {
|
||||
+ .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT,
|
||||
+ .drmFormatModifierCount = mods_len,
|
||||
+ .pDrmFormatModifiers = mods,
|
||||
+ };
|
||||
+ eimg.pNext = &mod_list;
|
||||
+
|
||||
+ VkFormat view_formats[] = {
|
||||
+ props->format.vk,
|
||||
+ props->format.vk_srgb,
|
||||
+ };
|
||||
+ VkImageFormatListCreateInfoKHR list_info = {
|
||||
+ .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR,
|
||||
+ .pViewFormats = view_formats,
|
||||
+ .viewFormatCount = sizeof(view_formats) / sizeof(view_formats[0]),
|
||||
+ };
|
||||
+ if (mutable_srgb) {
|
||||
+ mod_list.pNext = &list_info;
|
||||
+ }
|
||||
+
|
||||
+ VkImage image = VK_NULL_HANDLE;
|
||||
+ VkResult res = vkCreateImage(alloc->dev->dev, &img_info, NULL, &image);
|
||||
+ if (res != VK_SUCCESS) {
|
||||
+ wlr_vk_error("vkCreateImage", res);
|
||||
+ return VK_NULL_HANDLE;
|
||||
+ }
|
||||
+ return image;
|
||||
+}
|
||||
+
|
||||
+static struct wlr_buffer *allocator_create_buffer(struct wlr_allocator *wlr_allocator,
|
||||
+ int width, int height, const struct wlr_drm_format *format) {
|
||||
+ struct wlr_vk_allocator *alloc = wl_container_of(wlr_allocator, alloc, base);
|
||||
+ VkDevice dev = alloc->dev->dev;
|
||||
+
|
||||
+ const struct wlr_vk_format_props *props =
|
||||
+ vulkan_format_props_from_drm(alloc->dev, format->format);
|
||||
+ if (props == NULL) {
|
||||
+ wlr_log(WLR_ERROR, "Vulkan allocator: format 0x%08"PRIX32" not supported",
|
||||
+ format->format);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ // The modifiers asked for that this device can render to at this size, split by whether the
|
||||
+ // import will make the image mutable-format. INVALID (implicit) is not something Vulkan can
|
||||
+ // allocate; LINEAR stands in for it when it is all there is.
|
||||
+ uint64_t *mutable_mods = calloc(format->len + 1, sizeof(uint64_t));
|
||||
+ uint64_t *plain_mods = calloc(format->len + 1, sizeof(uint64_t));
|
||||
+ if (mutable_mods == NULL || plain_mods == NULL) {
|
||||
+ free(mutable_mods);
|
||||
+ free(plain_mods);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ uint32_t mutable_len = 0, plain_len = 0;
|
||||
+ bool implicit_only = format->len > 0;
|
||||
+ for (size_t i = 0; i <= format->len; i++) {
|
||||
+ uint64_t mod;
|
||||
+ if (i < format->len) {
|
||||
+ mod = format->modifiers[i];
|
||||
+ if (mod == DRM_FORMAT_MOD_INVALID) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ implicit_only = false;
|
||||
+ } else if (implicit_only) {
|
||||
+ mod = DRM_FORMAT_MOD_LINEAR;
|
||||
+ } else {
|
||||
+ break;
|
||||
+ }
|
||||
+ const struct wlr_vk_format_modifier_props *mp =
|
||||
+ vulkan_format_props_find_modifier(props, mod, true);
|
||||
+ if (mp == NULL || (uint32_t)width > mp->max_extent.width ||
|
||||
+ (uint32_t)height > mp->max_extent.height) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (mp->has_mutable_srgb) {
|
||||
+ mutable_mods[mutable_len++] = mod;
|
||||
+ } else {
|
||||
+ plain_mods[plain_len++] = mod;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ VkImage image = VK_NULL_HANDLE;
|
||||
+ if (mutable_len > 0) {
|
||||
+ image = create_image(alloc, props, width, height, mutable_mods, mutable_len, true);
|
||||
+ }
|
||||
+ if (image == VK_NULL_HANDLE && plain_len > 0) {
|
||||
+ image = create_image(alloc, props, width, height, plain_mods, plain_len, false);
|
||||
+ }
|
||||
+ free(mutable_mods);
|
||||
+ free(plain_mods);
|
||||
+ if (image == VK_NULL_HANDLE) {
|
||||
+ wlr_log(WLR_ERROR, "Vulkan allocator: no usable modifier for %dx%d format 0x%08"PRIX32,
|
||||
+ width, height, format->format);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ struct wlr_vk_allocator_buffer *buffer = calloc(1, sizeof(*buffer));
|
||||
+ if (buffer == NULL) {
|
||||
+ vkDestroyImage(dev, image, NULL);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ wlr_buffer_init(&buffer->base, &buffer_impl, width, height);
|
||||
+ buffer->alloc = alloc;
|
||||
+ buffer->image = image;
|
||||
+ wl_list_insert(&alloc->buffers, &buffer->link);
|
||||
+ for (int i = 0; i < WLR_DMABUF_MAX_PLANES; i++) {
|
||||
+ buffer->dmabuf.fd[i] = -1;
|
||||
+ }
|
||||
+
|
||||
+ VkMemoryRequirements reqs;
|
||||
+ vkGetImageMemoryRequirements(dev, image, &reqs);
|
||||
+ int mem_type = vulkan_find_mem_type(alloc->dev,
|
||||
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, reqs.memoryTypeBits);
|
||||
+ if (mem_type < 0) {
|
||||
+ mem_type = vulkan_find_mem_type(alloc->dev, 0, reqs.memoryTypeBits);
|
||||
+ }
|
||||
+ if (mem_type < 0) {
|
||||
+ wlr_log(WLR_ERROR, "Vulkan allocator: no memory type for the image");
|
||||
+ goto error;
|
||||
+ }
|
||||
+
|
||||
+ VkMemoryDedicatedAllocateInfo dedicated = {
|
||||
+ .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
|
||||
+ .image = image,
|
||||
+ };
|
||||
+ VkExportMemoryAllocateInfo export = {
|
||||
+ .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
|
||||
+ .pNext = &dedicated,
|
||||
+ .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
|
||||
+ };
|
||||
+ VkMemoryAllocateInfo mem_info = {
|
||||
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
+ .pNext = &export,
|
||||
+ .allocationSize = reqs.size,
|
||||
+ .memoryTypeIndex = mem_type,
|
||||
+ };
|
||||
+ VkResult res = vkAllocateMemory(dev, &mem_info, NULL, &buffer->memory);
|
||||
+ if (res != VK_SUCCESS) {
|
||||
+ wlr_vk_error("vkAllocateMemory", res);
|
||||
+ goto error;
|
||||
+ }
|
||||
+ res = vkBindImageMemory(dev, image, buffer->memory, 0);
|
||||
+ if (res != VK_SUCCESS) {
|
||||
+ wlr_vk_error("vkBindImageMemory", res);
|
||||
+ goto error;
|
||||
+ }
|
||||
+
|
||||
+ VkImageDrmFormatModifierPropertiesEXT mod_props = {
|
||||
+ .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT,
|
||||
+ };
|
||||
+ res = alloc->get_modifier_props(dev, image, &mod_props);
|
||||
+ if (res != VK_SUCCESS) {
|
||||
+ wlr_vk_error("vkGetImageDrmFormatModifierPropertiesEXT", res);
|
||||
+ goto error;
|
||||
+ }
|
||||
+ const struct wlr_vk_format_modifier_props *mp =
|
||||
+ vulkan_format_props_find_modifier(props, mod_props.drmFormatModifier, true);
|
||||
+ if (mp == NULL) {
|
||||
+ wlr_log(WLR_ERROR, "Vulkan allocator: driver chose modifier 0x%016"PRIX64
|
||||
+ " which it cannot render to", mod_props.drmFormatModifier);
|
||||
+ goto error;
|
||||
+ }
|
||||
+ uint32_t n_planes = mp->props.drmFormatModifierPlaneCount;
|
||||
+ if (n_planes == 0 || n_planes > WLR_DMABUF_MAX_PLANES) {
|
||||
+ wlr_log(WLR_ERROR, "Vulkan allocator: %"PRIu32" memory planes", n_planes);
|
||||
+ goto error;
|
||||
+ }
|
||||
+
|
||||
+ VkMemoryGetFdInfoKHR fd_info = {
|
||||
+ .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
|
||||
+ .memory = buffer->memory,
|
||||
+ .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
|
||||
+ };
|
||||
+ int fd = -1;
|
||||
+ res = alloc->get_memory_fd(dev, &fd_info, &fd);
|
||||
+ if (res != VK_SUCCESS || fd < 0) {
|
||||
+ wlr_vk_error("vkGetMemoryFdKHR", res);
|
||||
+ goto error;
|
||||
+ }
|
||||
+
|
||||
+ buffer->dmabuf = (struct wlr_dmabuf_attributes){
|
||||
+ .width = width,
|
||||
+ .height = height,
|
||||
+ .format = format->format,
|
||||
+ .modifier = mod_props.drmFormatModifier,
|
||||
+ .n_planes = n_planes,
|
||||
+ };
|
||||
+ for (int i = 0; i < WLR_DMABUF_MAX_PLANES; i++) {
|
||||
+ buffer->dmabuf.fd[i] = -1;
|
||||
+ }
|
||||
+ static const VkImageAspectFlagBits plane_aspects[] = {
|
||||
+ VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT,
|
||||
+ VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT,
|
||||
+ VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT,
|
||||
+ VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT,
|
||||
+ };
|
||||
+ for (uint32_t i = 0; i < n_planes; i++) {
|
||||
+ VkImageSubresource sub = { .aspectMask = plane_aspects[i] };
|
||||
+ VkSubresourceLayout layout;
|
||||
+ vkGetImageSubresourceLayout(dev, image, &sub, &layout);
|
||||
+ buffer->dmabuf.offset[i] = layout.offset;
|
||||
+ buffer->dmabuf.stride[i] = layout.rowPitch;
|
||||
+ // Every plane lives in the one allocation; each gets its own fd, as
|
||||
+ // wlr_dmabuf_attributes_finish() closes them one by one.
|
||||
+ buffer->dmabuf.fd[i] = i == 0 ? fd : fcntl(fd, F_DUPFD_CLOEXEC, 0);
|
||||
+ if (buffer->dmabuf.fd[i] < 0) {
|
||||
+ wlr_log_errno(WLR_ERROR, "Vulkan allocator: dup failed");
|
||||
+ goto error;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ wlr_log(WLR_DEBUG, "Vulkan allocator: %dx%d format 0x%08"PRIX32" modifier 0x%016"PRIX64
|
||||
+ ", %"PRIu32" plane(s)", width, height, format->format, mod_props.drmFormatModifier, n_planes);
|
||||
+ return &buffer->base;
|
||||
+
|
||||
+error:
|
||||
+ buffer_destroy(&buffer->base);
|
||||
+ return NULL;
|
||||
+}
|
||||
+
|
||||
+static void allocator_destroy(struct wlr_allocator *wlr_allocator) {
|
||||
+ struct wlr_vk_allocator *alloc = wl_container_of(wlr_allocator, alloc, base);
|
||||
+ // Buffers may outlive their allocator; their DMA-BUF fds stay valid without the device.
|
||||
+ struct wlr_vk_allocator_buffer *buffer, *tmp;
|
||||
+ wl_list_for_each_safe(buffer, tmp, &alloc->buffers, link) {
|
||||
+ buffer_release_vk(buffer);
|
||||
+ }
|
||||
+ struct wlr_vk_instance *ini = alloc->dev->instance;
|
||||
+ vulkan_device_destroy(alloc->dev);
|
||||
+ vulkan_instance_destroy(ini);
|
||||
+ free(alloc);
|
||||
+}
|
||||
+
|
||||
+static const struct wlr_allocator_interface allocator_impl = {
|
||||
+ .destroy = allocator_destroy,
|
||||
+ .create_buffer = allocator_create_buffer,
|
||||
+};
|
||||
+
|
||||
+struct wlr_allocator *vulkan_allocator_create(int drm_fd) {
|
||||
+ struct wlr_vk_instance *ini = vulkan_instance_create(false);
|
||||
+ if (ini == NULL) {
|
||||
+ wlr_log(WLR_ERROR, "Vulkan allocator: could not create an instance");
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ VkPhysicalDevice phdev = vulkan_find_drm_phdev(ini, drm_fd);
|
||||
+ if (phdev == VK_NULL_HANDLE) {
|
||||
+ wlr_log(WLR_ERROR, "Vulkan allocator: no Vulkan device for the DRM fd");
|
||||
+ vulkan_instance_destroy(ini);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ struct wlr_vk_device *dev = vulkan_device_create(ini, phdev);
|
||||
+ if (dev == NULL) {
|
||||
+ wlr_log(WLR_ERROR, "Vulkan allocator: could not create a device");
|
||||
+ vulkan_instance_destroy(ini);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ struct wlr_vk_allocator *alloc = calloc(1, sizeof(*alloc));
|
||||
+ if (alloc == NULL) {
|
||||
+ vulkan_device_destroy(dev);
|
||||
+ vulkan_instance_destroy(ini);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ alloc->dev = dev;
|
||||
+ wl_list_init(&alloc->buffers);
|
||||
+ alloc->get_memory_fd = (PFN_vkGetMemoryFdKHR)
|
||||
+ vkGetDeviceProcAddr(dev->dev, "vkGetMemoryFdKHR");
|
||||
+ alloc->get_modifier_props = (PFN_vkGetImageDrmFormatModifierPropertiesEXT)
|
||||
+ vkGetDeviceProcAddr(dev->dev, "vkGetImageDrmFormatModifierPropertiesEXT");
|
||||
+ if (alloc->get_memory_fd == NULL || alloc->get_modifier_props == NULL) {
|
||||
+ wlr_log(WLR_ERROR, "Vulkan allocator: the device lacks DMA-BUF export");
|
||||
+ free(alloc);
|
||||
+ vulkan_device_destroy(dev);
|
||||
+ vulkan_instance_destroy(ini);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ wlr_allocator_init(&alloc->base, &allocator_impl, WLR_BUFFER_CAP_DMABUF);
|
||||
+ wlr_log(WLR_INFO, "Created Vulkan DMA-BUF allocator");
|
||||
+ return &alloc->base;
|
||||
+}
|
||||
diff --git a/render/vulkan/meson.build b/render/vulkan/meson.build
|
||||
index 36a822f9..abeb5337 100644
|
||||
--- a/render/vulkan/meson.build
|
||||
+++ b/render/vulkan/meson.build
|
||||
@@ -37,6 +37,7 @@ glslang_version_info = run_command(glslang, '--version', check: true).stdout()
|
||||
glslang_version = glslang_version_info.split('\n')[0].split(':')[-1]
|
||||
|
||||
wlr_files += files(
|
||||
+ 'allocator.c',
|
||||
'pass.c',
|
||||
'renderer.c',
|
||||
'texture.c',
|
||||
diff --git a/types/wlr_linux_dmabuf_v1.c b/types/wlr_linux_dmabuf_v1.c
|
||||
index 3165e880..a7fe060c 100644
|
||||
--- a/types/wlr_linux_dmabuf_v1.c
|
||||
+++ b/types/wlr_linux_dmabuf_v1.c
|
||||
@@ -901,11 +901,22 @@ static bool set_default_feedback(struct wlr_linux_dmabuf_v1 *linux_dmabuf,
|
||||
if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
|
||||
const char *name = device->nodes[DRM_NODE_RENDER];
|
||||
main_device_fd = open(name, O_RDWR | O_CLOEXEC);
|
||||
- drmFreeDevice(&device);
|
||||
if (main_device_fd < 0) {
|
||||
wlr_log_errno(WLR_ERROR, "Failed to open DRM device %s", name);
|
||||
+ drmFreeDevice(&device);
|
||||
goto error_compiled;
|
||||
}
|
||||
+ // A GPU node that stands in for a render node without being a DRM device (KGSL on
|
||||
+ // Android) cannot import a buffer as a GEM handle; every client's would be refused.
|
||||
+ drmVersion *version = drmGetVersion(main_device_fd);
|
||||
+ if (version == NULL) {
|
||||
+ wlr_log(WLR_INFO, "%s is not a DRM device, skipping DMA-BUF import checks", name);
|
||||
+ close(main_device_fd);
|
||||
+ main_device_fd = -1;
|
||||
+ } else {
|
||||
+ drmFreeVersion(version);
|
||||
+ }
|
||||
+ drmFreeDevice(&device);
|
||||
} else {
|
||||
// Likely a split display/render setup. Unfortunately we have no way to
|
||||
// get back the proper render node used by the renderer under-the-hood.
|
||||
Reference in new issue
Block a user