diff --git a/meson.build b/meson.build index f6142bb11fc..66e14c211d2 100644 --- a/meson.build +++ b/meson.build @@ -17,6 +17,11 @@ project( ], ) +if host_machine.system() == 'darwin' + add_languages('objc', native : false) + add_global_arguments('-fobjc-arc', language : 'objc') +endif + cc = meson.get_compiler('c') cpp = meson.get_compiler('cpp') @@ -382,12 +387,14 @@ _platforms = get_option('platforms') if _platforms.contains('auto') if system_has_kms_drm _platforms = ['x11', 'wayland'] - elif ['darwin', 'cygwin'].contains(host_machine.system()) + elif host_machine.system() == 'cygwin' _platforms = ['x11'] - elif ['haiku'].contains(host_machine.system()) + elif host_machine.system() == 'haiku' _platforms = ['haiku'] elif host_machine.system() == 'windows' _platforms = ['windows'] + elif host_machine.system() == 'darwin' + _platforms = ['x11', 'macos'] else error('Unknown OS @0@. Please pass -Dplatforms to set platforms. Patches gladly accepted to fix this.'.format( host_machine.system())) @@ -400,6 +407,7 @@ with_platform_xcb = _platforms.contains('xcb') with_platform_wayland = _platforms.contains('wayland') with_platform_haiku = _platforms.contains('haiku') with_platform_windows = _platforms.contains('windows') +with_platform_macos = _platforms.contains('macos') with_glx = get_option('glx') if with_glx == 'auto' diff --git a/meson_options.txt b/meson_options.txt index 5ff42b8df2d..5de1bad27cd 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -12,7 +12,7 @@ option( type : 'array', value : ['auto'], choices : [ - 'auto', 'x11', 'wayland', 'haiku', 'android', 'windows', + 'auto', 'x11', 'wayland', 'haiku', 'android', 'windows', 'macos', ], description : 'window systems to support. If this is set to `auto`, all ' + 'platforms applicable will be enabled.' diff --git a/src/gallium/frontends/lavapipe/lvp_device.c b/src/gallium/frontends/lavapipe/lvp_device.c index 3918077d130..5a2945242bb 100644 --- a/src/gallium/frontends/lavapipe/lvp_device.c +++ b/src/gallium/frontends/lavapipe/lvp_device.c @@ -57,7 +57,8 @@ #if defined(VK_USE_PLATFORM_WAYLAND_KHR) || \ defined(VK_USE_PLATFORM_WIN32_KHR) || \ defined(VK_USE_PLATFORM_XCB_KHR) || \ - defined(VK_USE_PLATFORM_XLIB_KHR) + defined(VK_USE_PLATFORM_XLIB_KHR) || \ + defined(VK_USE_PLATFORM_METAL_EXT) #define LVP_USE_WSI_PLATFORM #endif #define LVP_API_VERSION VK_MAKE_VERSION(1, 3, VK_HEADER_VERSION) @@ -95,6 +96,9 @@ static const struct vk_instance_extension_table lvp_instance_extensions_supporte #ifdef VK_USE_PLATFORM_XLIB_KHR .KHR_xlib_surface = true, #endif +#ifdef VK_USE_PLATFORM_METAL_EXT + .EXT_metal_surface = true, +#endif #ifndef VK_USE_PLATFORM_WIN32_KHR .EXT_headless_surface = true, #endif diff --git a/src/vulkan/meson.build b/src/vulkan/meson.build index 69d35836a12..b0bc67a8a4f 100644 --- a/src/vulkan/meson.build +++ b/src/vulkan/meson.build @@ -68,9 +68,13 @@ endif if with_platform_windows vulkan_wsi_list += '-DVK_USE_PLATFORM_WIN32_KHR' endif -if host_machine.system() == 'darwin' +if with_platform_macos vulkan_wsi_list += '-DVK_USE_PLATFORM_MACOS_MVK' vulkan_wsi_list += '-DVK_USE_PLATFORM_METAL_EXT' + vulkan_wsi_deps += dependency( + 'appleframeworks', + modules : ['QuartzCore', 'Metal'] + ) endif idep_vulkan_wsi_defines = declare_dependency( diff --git a/src/vulkan/wsi/meson.build b/src/vulkan/wsi/meson.build index f2b21306029..f2a612ab9a5 100644 --- a/src/vulkan/wsi/meson.build +++ b/src/vulkan/wsi/meson.build @@ -29,6 +29,10 @@ else files_vulkan_wsi += files('wsi_common_headless.c') endif +if with_platform_macos + files_vulkan_wsi += files('wsi_common_metal.c', 'wsi_common_metal_layer.m') +endif + if system_has_kms_drm and not with_platform_android files_vulkan_wsi += files('wsi_common_display.c') endif diff --git a/src/vulkan/wsi/wsi_common.c b/src/vulkan/wsi/wsi_common.c index a6ebdf512ce..cd63a008c05 100644 --- a/src/vulkan/wsi/wsi_common.c +++ b/src/vulkan/wsi/wsi_common.c @@ -234,6 +234,12 @@ wsi_device_init(struct wsi_device *wsi, goto fail; #endif +#ifdef VK_USE_PLATFORM_METAL_EXT + result = wsi_metal_init_wsi(wsi, alloc, pdevice); + if (result != VK_SUCCESS) + goto fail; +#endif + #ifndef VK_USE_PLATFORM_WIN32_KHR result = wsi_headless_init_wsi(wsi, alloc, pdevice); if (result != VK_SUCCESS) @@ -314,6 +320,9 @@ wsi_device_finish(struct wsi_device *wsi, #if defined(VK_USE_PLATFORM_XCB_KHR) wsi_x11_finish_wsi(wsi, alloc); #endif +#if defined(VK_USE_PLATFORM_METAL_EXT) + wsi_metal_finish_wsi(wsi, alloc); +#endif } VKAPI_ATTR void VKAPI_CALL diff --git a/src/vulkan/wsi/wsi_common.h b/src/vulkan/wsi/wsi_common.h index 014cb718b17..eebf519e6bc 100644 --- a/src/vulkan/wsi/wsi_common.h +++ b/src/vulkan/wsi/wsi_common.h @@ -97,7 +97,7 @@ struct vk_instance; struct driOptionCache; -#define VK_ICD_WSI_PLATFORM_MAX (VK_ICD_WSI_PLATFORM_HEADLESS + 1) +#define VK_ICD_WSI_PLATFORM_MAX (VK_ICD_WSI_PLATFORM_METAL + 1) struct wsi_device { /* Allocator for the instance */ diff --git a/src/vulkan/wsi/wsi_common_metal.c b/src/vulkan/wsi/wsi_common_metal.c new file mode 100644 index 00000000000..d7f6f804072 --- /dev/null +++ b/src/vulkan/wsi/wsi_common_metal.c @@ -0,0 +1,526 @@ +/* + * Copyright 2024 Autodesk, Inc. + * + * SPDX-License-Identifier: MIT + */ + +#include "vk_instance.h" +#include "vk_physical_device.h" +#include "vk_util.h" + +#include "util/timespec.h" +#include "util/u_vector.h" + +#include "wsi_common_entrypoints.h" +#include "wsi_common_private.h" +#include "wsi_common_metal_layer.h" + +#include "vulkan/vulkan_core.h" + +#include + +struct wsi_metal { + struct wsi_interface base; + + struct wsi_device *wsi; + + const VkAllocationCallbacks *alloc; + VkPhysicalDevice physical_device; +}; + +static VkResult +wsi_metal_surface_get_support(VkIcdSurfaceBase *surface, + struct wsi_device *wsi_device, + uint32_t queueFamilyIndex, + VkBool32* pSupported) +{ + *pSupported = true; + return VK_SUCCESS; +} + +static const VkPresentModeKHR present_modes[] = { + VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_FIFO_KHR, +}; + +static VkResult +wsi_metal_surface_get_capabilities(VkIcdSurfaceBase *surface, + struct wsi_device *wsi_device, + VkSurfaceCapabilitiesKHR* caps) +{ + VkIcdSurfaceMetal *metal_surface = (VkIcdSurfaceMetal *)surface; + assert(metal_surface->pLayer); + + wsi_metal_layer_size(metal_surface->pLayer, + &caps->currentExtent.width, + &caps->currentExtent.height); + + caps->minImageCount = 2; + caps->maxImageCount = 3; + + caps->minImageExtent = (VkExtent2D) { 1, 1 }; + caps->maxImageExtent = (VkExtent2D) { + wsi_device->maxImageDimension2D, + wsi_device->maxImageDimension2D, + }; + + caps->supportedTransforms = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + caps->currentTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + caps->maxImageArrayLayers = 1; + + caps->supportedCompositeAlpha = + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR | + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; + + caps->supportedUsageFlags = wsi_caps_get_image_usage(); + + VK_FROM_HANDLE(vk_physical_device, pdevice, wsi_device->pdevice); + if (pdevice->supported_extensions.EXT_attachment_feedback_loop_layout) + caps->supportedUsageFlags |= VK_IMAGE_USAGE_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT; + + return VK_SUCCESS; +} + +static VkResult +wsi_metal_surface_get_capabilities2(VkIcdSurfaceBase *surface, + struct wsi_device *wsi_device, + const void *info_next, + VkSurfaceCapabilities2KHR* caps) +{ + assert(caps->sType == VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR); + + const VkSurfacePresentModeEXT *present_mode = + (const VkSurfacePresentModeEXT *)vk_find_struct_const(info_next, SURFACE_PRESENT_MODE_EXT); + + VkResult result = + wsi_metal_surface_get_capabilities(surface, wsi_device, + &caps->surfaceCapabilities); + + vk_foreach_struct(ext, caps->pNext) { + switch (ext->sType) { + case VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR: { + VkSurfaceProtectedCapabilitiesKHR *protected = (void *)ext; + protected->supportsProtected = VK_FALSE; + break; + } + + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_EXT: { + /* TODO: support scaling */ + VkSurfacePresentScalingCapabilitiesEXT *scaling = + (VkSurfacePresentScalingCapabilitiesEXT *)ext; + scaling->supportedPresentScaling = 0; + scaling->supportedPresentGravityX = 0; + scaling->supportedPresentGravityY = 0; + scaling->minScaledImageExtent = caps->surfaceCapabilities.minImageExtent; + scaling->maxScaledImageExtent = caps->surfaceCapabilities.maxImageExtent; + break; + } + + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT: { + /* Unsupported, just report the input present mode. */ + VkSurfacePresentModeCompatibilityEXT *compat = + (VkSurfacePresentModeCompatibilityEXT *)ext; + if (compat->pPresentModes) { + if (compat->presentModeCount) { + assert(present_mode); + compat->pPresentModes[0] = present_mode->presentMode; + compat->presentModeCount = 1; + } + } else { + if (!present_mode) + wsi_common_vk_warn_once("Use of VkSurfacePresentModeCompatibilityEXT " + "without a VkSurfacePresentModeEXT set. This is an " + "application bug.\n"); + compat->presentModeCount = 1; + } + break; + } + + default: + /* Ignored */ + break; + } + } + + return result; +} + +static const VkFormat available_surface_formats[] = { + VK_FORMAT_B8G8R8A8_SRGB, + VK_FORMAT_B8G8R8A8_UNORM, + VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, +}; + +static void +get_sorted_vk_formats(bool force_bgra8_unorm_first, VkFormat *sorted_formats) +{ + for (unsigned i = 0; i < ARRAY_SIZE(available_surface_formats); i++) + sorted_formats[i] = available_surface_formats[i]; + + if (force_bgra8_unorm_first) { + for (unsigned i = 0; i < ARRAY_SIZE(available_surface_formats); i++) { + if (sorted_formats[i] == VK_FORMAT_B8G8R8A8_UNORM) { + sorted_formats[i] = sorted_formats[0]; + sorted_formats[0] = VK_FORMAT_B8G8R8A8_UNORM; + break; + } + } + } +} + +static VkResult +wsi_metal_surface_get_formats(VkIcdSurfaceBase *icd_surface, + struct wsi_device *wsi_device, + uint32_t* pSurfaceFormatCount, + VkSurfaceFormatKHR* pSurfaceFormats) +{ + VK_OUTARRAY_MAKE_TYPED(VkSurfaceFormatKHR, out, pSurfaceFormats, pSurfaceFormatCount); + + VkFormat sorted_formats[ARRAY_SIZE(available_surface_formats)]; + get_sorted_vk_formats(wsi_device->force_bgra8_unorm_first, sorted_formats); + + for (unsigned i = 0; i < ARRAY_SIZE(sorted_formats); i++) { + vk_outarray_append_typed(VkSurfaceFormatKHR, &out, f) { + f->format = sorted_formats[i]; + f->colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + } + } + + return vk_outarray_status(&out); +} + +static VkResult +wsi_metal_surface_get_formats2(VkIcdSurfaceBase *icd_surface, + struct wsi_device *wsi_device, + const void *info_next, + uint32_t* pSurfaceFormatCount, + VkSurfaceFormat2KHR* pSurfaceFormats) +{ + VK_OUTARRAY_MAKE_TYPED(VkSurfaceFormat2KHR, out, pSurfaceFormats, pSurfaceFormatCount); + + VkFormat sorted_formats[ARRAY_SIZE(available_surface_formats)]; + get_sorted_vk_formats(wsi_device->force_bgra8_unorm_first, sorted_formats); + + for (unsigned i = 0; i < ARRAY_SIZE(sorted_formats); i++) { + vk_outarray_append_typed(VkSurfaceFormat2KHR, &out, f) { + assert(f->sType == VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR); + f->surfaceFormat.format = sorted_formats[i]; + f->surfaceFormat.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + } + } + + return vk_outarray_status(&out); +} + +static VkResult +wsi_metal_surface_get_present_modes(VkIcdSurfaceBase *surface, + struct wsi_device *wsi_device, + uint32_t* pPresentModeCount, + VkPresentModeKHR* pPresentModes) +{ + if (pPresentModes == NULL) { + *pPresentModeCount = ARRAY_SIZE(present_modes); + return VK_SUCCESS; + } + + *pPresentModeCount = MIN2(*pPresentModeCount, ARRAY_SIZE(present_modes)); + typed_memcpy(pPresentModes, present_modes, *pPresentModeCount); + + return *pPresentModeCount < ARRAY_SIZE(present_modes) ? VK_INCOMPLETE : VK_SUCCESS; +} + +static VkResult +wsi_metal_surface_get_present_rectangles(VkIcdSurfaceBase *surface, + struct wsi_device *wsi_device, + uint32_t* pRectCount, + VkRect2D* pRects) +{ + VK_OUTARRAY_MAKE_TYPED(VkRect2D, out, pRects, pRectCount); + + vk_outarray_append_typed(VkRect2D, &out, rect) { + /* We don't know a size so just return the usual "I don't know." */ + *rect = (VkRect2D) { + .offset = { 0, 0 }, + .extent = { UINT32_MAX, UINT32_MAX }, + }; + } + + return vk_outarray_status(&out); +} + +struct wsi_metal_image { + struct wsi_image base; + CAMetalDrawable *drawable; +}; + +struct wsi_metal_swapchain { + struct wsi_swapchain base; + + VkExtent2D extent; + VkFormat vk_format; + + struct u_vector modifiers; + + VkPresentModeKHR present_mode; + bool fifo_ready; + + VkIcdSurfaceMetal *surface; + + struct wsi_metal_layer_blit_context *blit_context; + + uint32_t current_image_index; + struct wsi_metal_image images[0]; +}; +VK_DEFINE_NONDISP_HANDLE_CASTS(wsi_metal_swapchain, base.base, VkSwapchainKHR, + VK_OBJECT_TYPE_SWAPCHAIN_KHR) + +static struct wsi_image * +wsi_metal_swapchain_get_wsi_image(struct wsi_swapchain *wsi_chain, + uint32_t image_index) +{ + struct wsi_metal_swapchain *chain = + (struct wsi_metal_swapchain *)wsi_chain; + return &chain->images[image_index].base; +} + +static VkResult +wsi_metal_swapchain_acquire_next_image(struct wsi_swapchain *wsi_chain, + const VkAcquireNextImageInfoKHR *info, + uint32_t *image_index) +{ + struct wsi_metal_swapchain *chain = + (struct wsi_metal_swapchain *)wsi_chain; + struct timespec start_time, end_time; + struct timespec rel_timeout; + + timespec_from_nsec(&rel_timeout, info->timeout); + + clock_gettime(CLOCK_MONOTONIC, &start_time); + timespec_add(&end_time, &rel_timeout, &start_time); + + while (1) { + /* Try to acquire an drawable. Unfortunately we might block for up to 1 second. */ + CAMetalDrawable *drawable = wsi_metal_layer_acquire_drawable(chain->surface->pLayer); + if (drawable) { + uint32_t i = (chain->current_image_index++) % chain->base.image_count; + *image_index = i; + chain->images[i].drawable = drawable; + return VK_SUCCESS; + } + + /* Check for timeout. */ + struct timespec current_time; + clock_gettime(CLOCK_MONOTONIC, ¤t_time); + if (timespec_after(¤t_time, &end_time)) + return VK_NOT_READY; + } +} + +static VkResult +wsi_metal_swapchain_queue_present(struct wsi_swapchain *wsi_chain, + uint32_t image_index, + uint64_t present_id, + const VkPresentRegionKHR *damage) +{ + struct wsi_metal_swapchain *chain = + (struct wsi_metal_swapchain *)wsi_chain; + + assert(image_index < chain->base.image_count); + + struct wsi_metal_image *image = &chain->images[image_index]; + + wsi_metal_layer_blit_and_present(chain->blit_context, + &image->drawable, + image->base.cpu_map, + chain->extent.width, chain->extent.height, + image->base.row_pitches[0]); + + return VK_SUCCESS; +} + +static VkResult +wsi_metal_swapchain_destroy(struct wsi_swapchain *wsi_chain, + const VkAllocationCallbacks *pAllocator) +{ + struct wsi_metal_swapchain *chain = + (struct wsi_metal_swapchain *)wsi_chain; + + for (uint32_t i = 0; i < chain->base.image_count; i++) { + wsi_metal_layer_cancel_present(chain->blit_context, &chain->images[i].drawable); + if (chain->images[i].base.image != VK_NULL_HANDLE) + wsi_destroy_image(&chain->base, &chain->images[i].base); + } + + u_vector_finish(&chain->modifiers); + + wsi_destroy_metal_layer_blit_context(chain->blit_context); + + wsi_swapchain_finish(&chain->base); + + vk_free(pAllocator, chain); + + return VK_SUCCESS; +} + +static VkResult +wsi_metal_surface_create_swapchain(VkIcdSurfaceBase *icd_surface, + VkDevice device, + struct wsi_device *wsi_device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + struct wsi_swapchain **swapchain_out) +{ + VkResult result; + + VkIcdSurfaceMetal *metal_surface = (VkIcdSurfaceMetal *)icd_surface; + assert(metal_surface->pLayer); + + assert(pCreateInfo->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR); + + MTLPixelFormat metal_format; + switch (pCreateInfo->imageFormat) + { + case VK_FORMAT_B8G8R8A8_SRGB: + metal_format = MTLPixelFormatBGRA8Unorm_sRGB; + break; + case VK_FORMAT_B8G8R8A8_UNORM: + metal_format = MTLPixelFormatBGRA8Unorm; + break; + case VK_FORMAT_R16G16B16A16_SFLOAT: + metal_format = MTLPixelFormatRGBA16Float; + break; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + metal_format = MTLPixelFormatRGB10A2Unorm; + break; + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: + metal_format = MTLPixelFormatBGR10A2Unorm; + break; + default: + return VK_ERROR_FORMAT_NOT_SUPPORTED; + } + + int num_images = pCreateInfo->minImageCount; + + struct wsi_metal_swapchain *chain; + size_t size = sizeof(*chain) + num_images * sizeof(chain->images[0]); + chain = vk_zalloc(pAllocator, size, 8, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + if (chain == NULL) + return VK_ERROR_OUT_OF_HOST_MEMORY; + + struct wsi_cpu_image_params cpu_params = { + .base.image_type = WSI_IMAGE_TYPE_CPU, + }; + + result = wsi_swapchain_init(wsi_device, &chain->base, device, + pCreateInfo, &cpu_params.base, pAllocator); + if (result != VK_SUCCESS) { + vk_free(pAllocator, chain); + return result; + } + + chain->base.destroy = wsi_metal_swapchain_destroy; + chain->base.get_wsi_image = wsi_metal_swapchain_get_wsi_image; + chain->base.acquire_next_image = wsi_metal_swapchain_acquire_next_image; + chain->base.queue_present = wsi_metal_swapchain_queue_present; + chain->base.present_mode = wsi_swapchain_get_present_mode(wsi_device, pCreateInfo); + chain->base.image_count = num_images; + chain->extent = pCreateInfo->imageExtent; + chain->vk_format = pCreateInfo->imageFormat; + chain->surface = metal_surface; + + wsi_metal_layer_configure(metal_surface->pLayer, + pCreateInfo->imageExtent.width, pCreateInfo->imageExtent.height, + num_images, metal_format, + pCreateInfo->compositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + pCreateInfo->presentMode == VK_PRESENT_MODE_IMMEDIATE_KHR); + + chain->current_image_index = 0; + for (uint32_t i = 0; i < chain->base.image_count; i++) { + result = wsi_create_image(&chain->base, &chain->base.image_info, + &chain->images[i].base); + if (result != VK_SUCCESS) + return result; + + chain->images[i].drawable = NULL; + } + + chain->blit_context = wsi_create_metal_layer_blit_context(); + + *swapchain_out = &chain->base; + + return VK_SUCCESS; +} + +VkResult +wsi_metal_init_wsi(struct wsi_device *wsi_device, + const VkAllocationCallbacks *alloc, + VkPhysicalDevice physical_device) +{ + struct wsi_metal *wsi; + VkResult result; + + wsi = vk_alloc(alloc, sizeof(*wsi), 8, + VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (!wsi) { + result = VK_ERROR_OUT_OF_HOST_MEMORY; + goto fail; + } + + wsi->physical_device = physical_device; + wsi->alloc = alloc; + wsi->wsi = wsi_device; + + wsi->base.get_support = wsi_metal_surface_get_support; + wsi->base.get_capabilities2 = wsi_metal_surface_get_capabilities2; + wsi->base.get_formats = wsi_metal_surface_get_formats; + wsi->base.get_formats2 = wsi_metal_surface_get_formats2; + wsi->base.get_present_modes = wsi_metal_surface_get_present_modes; + wsi->base.get_present_rectangles = wsi_metal_surface_get_present_rectangles; + wsi->base.create_swapchain = wsi_metal_surface_create_swapchain; + + wsi_device->wsi[VK_ICD_WSI_PLATFORM_METAL] = &wsi->base; + + return VK_SUCCESS; + +fail: + wsi_device->wsi[VK_ICD_WSI_PLATFORM_METAL] = NULL; + + return result; +} + +void +wsi_metal_finish_wsi(struct wsi_device *wsi_device, + const VkAllocationCallbacks *alloc) +{ + struct wsi_metal *wsi = + (struct wsi_metal *)wsi_device->wsi[VK_ICD_WSI_PLATFORM_METAL]; + if (!wsi) + return; + + vk_free(alloc, wsi); +} + +VKAPI_ATTR VkResult VKAPI_CALL +vkCreateMetalSurfaceEXT( + VkInstance _instance, + const VkMetalSurfaceCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSurfaceKHR* pSurface) +{ + VK_FROM_HANDLE(vk_instance, instance, _instance); + VkIcdSurfaceMetal *surface; + + surface = vk_alloc2(&instance->alloc, pAllocator, sizeof *surface, 8, + VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + if (surface == NULL) + return VK_ERROR_OUT_OF_HOST_MEMORY; + + surface->base.platform = VK_ICD_WSI_PLATFORM_METAL; + surface->pLayer = pCreateInfo->pLayer; + assert(surface->pLayer); + + *pSurface = VkIcdSurfaceBase_to_handle(&surface->base); + return VK_SUCCESS; +} diff --git a/src/vulkan/wsi/wsi_common_metal_layer.h b/src/vulkan/wsi/wsi_common_metal_layer.h new file mode 100644 index 00000000000..cd88cbe3409 --- /dev/null +++ b/src/vulkan/wsi/wsi_common_metal_layer.h @@ -0,0 +1,58 @@ +/* + * Copyright 2024 Autodesk, Inc. + * + * SPDX-License-Identifier: MIT + */ + +#ifndef WSI_COMMON_METAL_LAYER_H +#define WSI_COMMON_METAL_LAYER_H + +#include +#include + +#ifdef __OBJC__ +@class CAMetalLayer; +@class CAMetalDrawable; +typedef unsigned long NSUInteger; +typedef enum MTLPixelFormat : NSUInteger MTLPixelFormat; +#else +typedef void CAMetalLayer; +typedef void CAMetalDrawable; +typedef enum MTLPixelFormat : unsigned long +{ + MTLPixelFormatBGRA8Unorm = 80, + MTLPixelFormatBGRA8Unorm_sRGB = 81, + MTLPixelFormatRGB10A2Unorm = 90, + MTLPixelFormatBGR10A2Unorm = 94, + MTLPixelFormatRGBA16Float = 115, +} MTLPixelFormat; +#endif + +void +wsi_metal_layer_size(const CAMetalLayer *metal_layer, + uint32_t *width, uint32_t *height); + +void +wsi_metal_layer_configure(const CAMetalLayer *metal_layer, + uint32_t width, uint32_t height, uint32_t image_count, + MTLPixelFormat format, bool enable_opaque, bool enable_immediate); + +CAMetalDrawable * +wsi_metal_layer_acquire_drawable(const CAMetalLayer *metal_layer); + +struct wsi_metal_layer_blit_context; + +struct wsi_metal_layer_blit_context * +wsi_create_metal_layer_blit_context(); + +void +wsi_destroy_metal_layer_blit_context(struct wsi_metal_layer_blit_context *context); + +void +wsi_metal_layer_blit_and_present(struct wsi_metal_layer_blit_context *context, CAMetalDrawable **drawable_ptr, + void *buffer, uint32_t width, uint32_t height, uint32_t row_pitch); + +void +wsi_metal_layer_cancel_present(struct wsi_metal_layer_blit_context *context, CAMetalDrawable **drawable_ptr); + +#endif // WSI_COMMON_METAL_LAYER_H diff --git a/src/vulkan/wsi/wsi_common_metal_layer.m b/src/vulkan/wsi/wsi_common_metal_layer.m new file mode 100644 index 00000000000..9613253637f --- /dev/null +++ b/src/vulkan/wsi/wsi_common_metal_layer.m @@ -0,0 +1,134 @@ +/* + * Copyright 2024 Autodesk, Inc. + * + * SPDX-License-Identifier: MIT + */ + +#include "wsi_common_metal_layer.h" + +#import +#import + +void +wsi_metal_layer_size(const CAMetalLayer *metal_layer, + uint32_t *width, uint32_t *height) +{ + @autoreleasepool { + CGSize size = [metal_layer drawableSize]; + if (width) + *width = size.width; + if (height) + *height = size.height; + } +} + +void +wsi_metal_layer_configure(const CAMetalLayer *metal_layer, + uint32_t width, uint32_t height, uint32_t image_count, + MTLPixelFormat format, bool enable_opaque, bool enable_immediate) +{ + @autoreleasepool { + if (metal_layer.device == nil) { + metal_layer.device = metal_layer.preferredDevice; + } + + /* So acquire timeout works */ + metal_layer.allowsNextDrawableTimeout = YES; + /* So we can blit to the drawable */ + metal_layer.framebufferOnly = NO; + + metal_layer.maximumDrawableCount = image_count; + metal_layer.drawableSize = (CGSize){.width = width, .height = height}; + metal_layer.pixelFormat = format; + metal_layer.opaque = enable_opaque; + metal_layer.displaySyncEnabled = !enable_immediate; + } +} + +CAMetalDrawable * +wsi_metal_layer_acquire_drawable(const CAMetalLayer *metal_layer) +{ + @autoreleasepool { + id drawable = [metal_layer nextDrawable]; + return (CAMetalDrawable *)drawable; + } +} + +struct wsi_metal_layer_blit_context { + id device; + id commandQueue; +}; + +struct wsi_metal_layer_blit_context * +wsi_create_metal_layer_blit_context() +{ + @autoreleasepool { + struct wsi_metal_layer_blit_context *context = malloc(sizeof(struct wsi_metal_layer_blit_context)); + memset((void*)context, 0, sizeof(*context)); + + context->device = MTLCreateSystemDefaultDevice(); + context->commandQueue = [context->device newCommandQueue]; + + return context; + } +} + +void +wsi_destroy_metal_layer_blit_context(struct wsi_metal_layer_blit_context *context) +{ + @autoreleasepool { + context->device = nil; + context->commandQueue = nil; + free(context); + } +} + +void +wsi_metal_layer_blit_and_present(struct wsi_metal_layer_blit_context *context, CAMetalDrawable **drawable_ptr, + void *buffer, uint32_t width, uint32_t height, uint32_t row_pitch) +{ + @autoreleasepool { + id drawable = (id)*drawable_ptr; + + id commandBuffer = [context->commandQueue commandBuffer]; + id commandEncoder = [commandBuffer blitCommandEncoder]; + + NSUInteger image_size = height * row_pitch; + id image_buffer = [context->device newBufferWithBytesNoCopy:buffer + length:image_size + options:MTLResourceStorageModeShared + deallocator:nil]; + + [commandEncoder copyFromBuffer:image_buffer + sourceOffset:0 + sourceBytesPerRow:row_pitch + sourceBytesPerImage:image_size + sourceSize:MTLSizeMake(width, height, 1) + toTexture:drawable.texture + destinationSlice:0 + destinationLevel:0 + destinationOrigin:MTLOriginMake(0, 0, 0)]; + [commandEncoder endEncoding]; + [commandBuffer presentDrawable:drawable]; + [commandBuffer commit]; + + *drawable_ptr = nil; + } +} + +void +wsi_metal_layer_cancel_present(struct wsi_metal_layer_blit_context *context, CAMetalDrawable **drawable_ptr) +{ + @autoreleasepool { + id drawable = (id)*drawable_ptr; + if (drawable == nil) + return; + + /* We need to present the drawable to release it... */ + id commandBuffer = [context->commandQueue commandBuffer]; + [commandBuffer presentDrawable:drawable]; + [commandBuffer commit]; + + *drawable_ptr = nil; + } +} diff --git a/src/vulkan/wsi/wsi_common_private.h b/src/vulkan/wsi/wsi_common_private.h index b89e962c3ff..cdf5be65ae2 100644 --- a/src/vulkan/wsi/wsi_common_private.h +++ b/src/vulkan/wsi/wsi_common_private.h @@ -120,7 +120,7 @@ enum wsi_explicit_sync_timelines WSI_ES_ACQUIRE, WSI_ES_RELEASE, - WSI_ES_COUNT, + WSI_ES_COUNT, }; struct wsi_image_explicit_sync_timeline { @@ -183,7 +183,7 @@ struct wsi_swapchain { struct wsi_image_info image_info; uint32_t image_count; - + uint64_t present_serial; struct { @@ -416,6 +416,11 @@ VkResult wsi_win32_init_wsi(struct wsi_device *wsi_device, VkPhysicalDevice physical_device); void wsi_win32_finish_wsi(struct wsi_device *wsi_device, const VkAllocationCallbacks *alloc); +VkResult wsi_metal_init_wsi(struct wsi_device *wsi_device, + const VkAllocationCallbacks *alloc, + VkPhysicalDevice physical_device); +void wsi_metal_finish_wsi(struct wsi_device *wsi_device, + const VkAllocationCallbacks *alloc); VkResult