blob: f2055ab1bacbf990d9ea3739d1ea626110d0369b [file]
// Copyright 2020 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "WaylandSurfaceKHR.hpp"
#include "libWaylandClient.hpp"
#include "Vulkan/VkDeviceMemory.hpp"
#include "Vulkan/VkImage.hpp"
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
namespace {
// Create an anonymous, in-memory file suitable for backing a wl_shm pool.
// Prefers memfd_create() (no filesystem footprint, no cleanup on crash) and
// falls back to an immediately-unlinked temp file on kernels without memfd.
int createAnonymousFile(off_t size)
{
int fd = -1;
#if defined(SYS_memfd_create)
fd = static_cast<int>(syscall(SYS_memfd_create, "swiftshader-wayland", 0 /* flags; MFD_CLOEXEC unavailable everywhere */));
if(fd >= 0)
{
int flags = fcntl(fd, F_GETFD);
if(flags != -1)
{
fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
}
}
#endif
if(fd < 0)
{
char path[] = "/tmp/swiftshader-wayland-XXXXXX";
fd = mkstemp(path);
if(fd >= 0)
{
unlink(path);
}
}
if(fd < 0)
{
return -1;
}
if(ftruncate(fd, size) < 0)
{
close(fd);
return -1;
}
return fd;
}
// Maps the swapchain image format to a wl_shm pixel format. Only the formats
// advertised by SurfaceKHR::GetSurfaceFormats() (B8G8R8A8) need to be handled.
// VK_FORMAT_B8G8R8A8_* is a byte-order format (memory bytes B,G,R,A) and
// WL_SHM_FORMAT_XRGB8888 is the DRM fourcc XR24 with fixed memory bytes B,G,R,X;
// the two layouts coincide independently of host endianness, and the X/alpha
// byte is ignored as the surface only advertises VK_COMPOSITE_ALPHA_OPAQUE.
bool waylandShmFormat(VkFormat format, uint32_t *out)
{
switch(format)
{
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SRGB:
*out = WL_SHM_FORMAT_XRGB8888;
return true;
default:
return false;
}
}
} // anonymous namespace
namespace vk {
bool WaylandSurfaceKHR::isSupported()
{
return libWaylandClient.isPresent();
}
static void wl_registry_handle_global(void *data, struct wl_registry *registry, unsigned int name, const char *interface, unsigned int version)
{
struct wl_shm **pshm = (struct wl_shm **)data;
if(!strcmp(interface, "wl_shm"))
{
*pshm = static_cast<struct wl_shm *>(libWaylandClient->registry_bind(registry, name, libWaylandClient->wl_shm_interface, 1));
}
}
static void wl_registry_handle_global_remove(void *data, struct wl_registry *registry, unsigned int name)
{
}
static const struct wl_registry_listener wl_registry_listener = { wl_registry_handle_global, wl_registry_handle_global_remove };
static void wl_buffer_handle_release(void *data, struct wl_buffer *buffer)
{
static_cast<WaylandBuffer *>(data)->busy = false;
}
static const struct wl_buffer_listener wl_buffer_listener = { wl_buffer_handle_release };
WaylandSurfaceKHR::WaylandSurfaceKHR(const VkWaylandSurfaceCreateInfoKHR *pCreateInfo, void *mem)
: display(pCreateInfo->display)
, surface(pCreateInfo->surface)
{
// Drive all WSI traffic on a private event queue so wl_display_dispatch /
// roundtrip here never steal events belonging to the application's queue.
queue = libWaylandClient->wl_display_create_queue(display);
// Bind the registry (and everything created from it) to the private queue by
// issuing get_registry on a queue-assigned proxy wrapper of the display.
struct wl_display *displayWrapper = static_cast<struct wl_display *>(libWaylandClient->wl_proxy_create_wrapper(display));
libWaylandClient->wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(displayWrapper), queue);
registry = libWaylandClient->display_get_registry(displayWrapper);
libWaylandClient->wl_proxy_wrapper_destroy(displayWrapper);
libWaylandClient->registry_add_listener(registry, &wl_registry_listener, &shm);
// Roundtrip the private queue to receive the globals and bind wl_shm.
libWaylandClient->wl_display_roundtrip_queue(display, queue);
}
void WaylandSurfaceKHR::destroySurface(const VkAllocationCallbacks *pAllocator)
{
if(shm)
{
libWaylandClient->wl_proxy_destroy(reinterpret_cast<wl_proxy *>(shm));
shm = nullptr;
}
if(registry)
{
libWaylandClient->wl_proxy_destroy(reinterpret_cast<wl_proxy *>(registry));
registry = nullptr;
}
if(queue)
{
libWaylandClient->wl_event_queue_destroy(queue);
queue = nullptr;
}
}
size_t WaylandSurfaceKHR::ComputeRequiredAllocationSize(const VkWaylandSurfaceCreateInfoKHR *pCreateInfo)
{
return 0;
}
VkResult WaylandSurfaceKHR::getSurfaceCapabilities(const void *pSurfaceInfoPNext, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities, void *pSurfaceCapabilitiesPNext) const
{
// The size of a Wayland surface is determined by the client (the swapchain
// extent), so report the special "undefined" current extent.
pSurfaceCapabilities->currentExtent = { 0xFFFFFFFF, 0xFFFFFFFF };
pSurfaceCapabilities->minImageExtent = { 1, 1 };
pSurfaceCapabilities->maxImageExtent = { 0xFFFFFFFF, 0xFFFFFFFF };
SetCommonSurfaceCapabilities(pSurfaceInfoPNext, pSurfaceCapabilities, pSurfaceCapabilitiesPNext);
return VK_SUCCESS;
}
bool WaylandSurfaceKHR::createBuffer(WaylandBuffer *out, const VkExtent3D &extent, int stride, uint32_t shmFormat)
{
out->data = static_cast<uint8_t *>(MAP_FAILED);
size_t size = static_cast<size_t>(extent.height) * stride;
int fd = createAnonymousFile(size);
if(fd < 0)
{
return false;
}
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(data == MAP_FAILED)
{
close(fd);
return false;
}
struct wl_shm_pool *pool = libWaylandClient->shm_create_pool(shm, fd, static_cast<int32_t>(size));
out->buffer = libWaylandClient->shm_pool_create_buffer(pool, 0, extent.width, extent.height, stride, shmFormat);
libWaylandClient->shm_pool_destroy(pool);
close(fd);
if(!out->buffer)
{
munmap(data, size);
out->data = static_cast<uint8_t *>(MAP_FAILED);
return false;
}
out->data = static_cast<uint8_t *>(data);
out->size = size;
out->busy = false;
// Release events are delivered on the private queue (inherited from wl_shm).
libWaylandClient->buffer_add_listener(out->buffer, &wl_buffer_listener, out);
return true;
}
void WaylandSurfaceKHR::attachImage(PresentImage *image)
{
WaylandImage *wlImage = new WaylandImage;
imageMap[image] = wlImage;
uint32_t shmFormat;
if(!shm || !waylandShmFormat(image->getImage()->getFormat(VK_IMAGE_ASPECT_COLOR_BIT), &shmFormat))
{
return; // Leaves buffers unallocated; present() will fail gracefully.
}
const VkExtent3D &extent = image->getImage()->getExtent();
int stride = image->getImage()->rowPitchBytes(VK_IMAGE_ASPECT_COLOR_BIT, 0);
for(WaylandBuffer &buffer : wlImage->buffers)
{
createBuffer(&buffer, extent, stride, shmFormat);
}
}
void WaylandSurfaceKHR::detachImage(PresentImage *image)
{
auto it = imageMap.find(image);
if(it != imageMap.end())
{
WaylandImage *wlImage = it->second;
for(WaylandBuffer &buffer : wlImage->buffers)
{
if(buffer.data != MAP_FAILED)
{
munmap(buffer.data, buffer.size);
}
if(buffer.buffer)
{
libWaylandClient->buffer_destroy(buffer.buffer);
}
}
delete wlImage;
imageMap.erase(it);
}
}
VkResult WaylandSurfaceKHR::present(PresentImage *image)
{
auto it = imageMap.find(image);
if(it == imageMap.end())
{
return VK_SUCCESS;
}
WaylandImage *wlImage = it->second;
// Alternate between the image's two buffers so we commit a different
// wl_buffer than the one the compositor is currently showing; this releases
// the previously-committed buffer and avoids overwriting a buffer in use.
WaylandBuffer *buffer = &wlImage->buffers[wlImage->next];
wlImage->next ^= 1;
if(!buffer->buffer || buffer->data == MAP_FAILED)
{
return VK_ERROR_SURFACE_LOST_KHR;
}
// In the rare case the chosen buffer is still held by the compositor, wait
// for its release before reusing it. This cannot deadlock: the other buffer
// of the pair was committed since, so this one has been (or will be) released.
while(buffer->busy)
{
if(libWaylandClient->wl_display_dispatch_queue(display, queue) < 0)
{
return VK_ERROR_SURFACE_LOST_KHR;
}
}
const VkExtent3D &extent = image->getImage()->getExtent();
int bufferRowPitch = image->getImage()->rowPitchBytes(VK_IMAGE_ASPECT_COLOR_BIT, 0);
image->getImage()->copyTo(buffer->data, bufferRowPitch);
buffer->busy = true;
libWaylandClient->surface_attach(surface, buffer->buffer, 0, 0);
libWaylandClient->surface_damage(surface, 0, 0, extent.width, extent.height);
libWaylandClient->surface_commit(surface);
// EAGAIN merely means the compositor's socket buffer is full and not all data
// was flushed; it is not a fatal error (the rest is sent on the next flush).
if(libWaylandClient->wl_display_flush(display) < 0 && errno != EAGAIN)
{
return VK_ERROR_SURFACE_LOST_KHR;
}
return VK_SUCCESS;
}
} // namespace vk