[PATCH 1/2] winevulkan: Unwrap object for VK_EXT_private_data when needed
Signed-off-by: Georg Lehmann <dadschoorse(a)gmail.com> --- dlls/winevulkan/make_vulkan | 4 ++++ dlls/winevulkan/vulkan.c | 37 +++++++++++++++++++++++++++++++++ dlls/winevulkan/vulkan_thunks.c | 12 ----------- dlls/winevulkan/vulkan_thunks.h | 2 ++ 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/dlls/winevulkan/make_vulkan b/dlls/winevulkan/make_vulkan index a0bf6b0859..dc1e03d326 100755 --- a/dlls/winevulkan/make_vulkan +++ b/dlls/winevulkan/make_vulkan @@ -217,6 +217,10 @@ FUNCTION_OVERRIDES = { # VK_KHR_device_group "vkGetDeviceGroupSurfacePresentModesKHR" : {"dispatch" : True, "driver" : True, "thunk" : True}, "vkGetPhysicalDevicePresentRectanglesKHR" : {"dispatch" : True, "driver" : True, "thunk" : True}, + + # VK_EXT_private_data + "vkGetPrivateDataEXT" : {"dispatch": True, "driver" : False, "thunk" : False}, + "vkSetPrivateDataEXT" : {"dispatch": True, "driver" : False, "thunk" : False}, } STRUCT_CHAIN_CONVERSIONS = [ diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c index d2da92cfa2..4642975ad0 100644 --- a/dlls/winevulkan/vulkan.c +++ b/dlls/winevulkan/vulkan.c @@ -1372,6 +1372,43 @@ void WINAPI wine_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR(VkPhysicalDev properties->externalSemaphoreFeatures = 0; } +static uint64_t unwrap_object_handle(VkObjectType type, uint64_t handle) +{ + switch (type) + { + case VK_OBJECT_TYPE_DEVICE: + return (uint64_t) (uintptr_t) ((VkDevice) (uintptr_t) handle)->device; + case VK_OBJECT_TYPE_QUEUE: + return (uint64_t) (uintptr_t) ((VkQueue) (uintptr_t) handle)->queue; + case VK_OBJECT_TYPE_COMMAND_BUFFER: + return (uint64_t) (uintptr_t) ((VkCommandBuffer) (uintptr_t) handle)->command_buffer; + case VK_OBJECT_TYPE_COMMAND_POOL: + return (uint64_t) wine_cmd_pool_from_handle(handle)->command_pool; + default: + return handle; + } +} + +VkResult WINAPI wine_vkSetPrivateDataEXT(VkDevice device, VkObjectType object_type, uint64_t object_handle, + VkPrivateDataSlotEXT private_data_slot, uint64_t data) +{ + TRACE("%p, %#x, 0x%s, 0x%s, 0x%s\n", device, object_type, wine_dbgstr_longlong(object_handle), + wine_dbgstr_longlong(private_data_slot), wine_dbgstr_longlong(data)); + + object_handle = unwrap_object_handle(object_type, object_handle); + return device->funcs.p_vkSetPrivateDataEXT(device->device, object_type, object_handle, private_data_slot, data); +} + +void WINAPI wine_vkGetPrivateDataEXT(VkDevice device, VkObjectType object_type, uint64_t object_handle, + VkPrivateDataSlotEXT private_data_slot, uint64_t *data) +{ + TRACE("%p, %#x, 0x%s, 0x%s, %p\n", device, object_type, wine_dbgstr_longlong(object_handle), + wine_dbgstr_longlong(private_data_slot), data); + + object_handle = unwrap_object_handle(object_type, object_handle); + device->funcs.p_vkGetPrivateDataEXT(device->device, object_type, object_handle, private_data_slot, data); +} + BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, void *reserved) { TRACE("%p, %u, %p\n", hinst, reason, reserved); diff --git a/dlls/winevulkan/vulkan_thunks.c b/dlls/winevulkan/vulkan_thunks.c index f8d171a6db..94aa1e3fb7 100644 --- a/dlls/winevulkan/vulkan_thunks.c +++ b/dlls/winevulkan/vulkan_thunks.c @@ -5066,12 +5066,6 @@ static VkResult WINAPI wine_vkGetPipelineExecutableStatisticsKHR(VkDevice device #endif } -static void WINAPI wine_vkGetPrivateDataEXT(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlotEXT privateDataSlot, uint64_t *pData) -{ - TRACE("%p, %#x, 0x%s, 0x%s, %p\n", device, objectType, wine_dbgstr_longlong(objectHandle), wine_dbgstr_longlong(privateDataSlot), pData); - device->funcs.p_vkGetPrivateDataEXT(device->device, objectType, objectHandle, privateDataSlot, pData); -} - VkResult WINAPI wine_vkGetQueryPoolResults(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, size_t dataSize, void *pData, VkDeviceSize stride, VkQueryResultFlags flags) { TRACE("%p, 0x%s, %u, %u, 0x%s, %p, 0x%s, %#x\n", device, wine_dbgstr_longlong(queryPool), firstQuery, queryCount, wine_dbgstr_longlong(dataSize), pData, wine_dbgstr_longlong(stride), flags); @@ -5264,12 +5258,6 @@ VkResult WINAPI wine_vkSetEvent(VkDevice device, VkEvent event) return device->funcs.p_vkSetEvent(device->device, event); } -static VkResult WINAPI wine_vkSetPrivateDataEXT(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlotEXT privateDataSlot, uint64_t data) -{ - TRACE("%p, %#x, 0x%s, 0x%s, 0x%s\n", device, objectType, wine_dbgstr_longlong(objectHandle), wine_dbgstr_longlong(privateDataSlot), wine_dbgstr_longlong(data)); - return device->funcs.p_vkSetPrivateDataEXT(device->device, objectType, objectHandle, privateDataSlot, data); -} - VkResult WINAPI wine_vkSignalSemaphore(VkDevice device, const VkSemaphoreSignalInfo *pSignalInfo) { #if defined(USE_STRUCT_CONVERSION) diff --git a/dlls/winevulkan/vulkan_thunks.h b/dlls/winevulkan/vulkan_thunks.h index 9580c6bbcd..7055d4fb13 100644 --- a/dlls/winevulkan/vulkan_thunks.h +++ b/dlls/winevulkan/vulkan_thunks.h @@ -40,7 +40,9 @@ VkResult WINAPI wine_vkGetPhysicalDeviceImageFormatProperties2(VkPhysicalDevice VkResult WINAPI wine_vkGetPhysicalDeviceImageFormatProperties2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2 *pImageFormatInfo, VkImageFormatProperties2 *pImageFormatProperties) DECLSPEC_HIDDEN; void WINAPI wine_vkGetPhysicalDeviceProperties2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2 *pProperties); void WINAPI wine_vkGetPhysicalDeviceProperties2KHR(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2 *pProperties) DECLSPEC_HIDDEN; +void WINAPI wine_vkGetPrivateDataEXT(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlotEXT privateDataSlot, uint64_t *pData) DECLSPEC_HIDDEN; VkResult WINAPI wine_vkQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo *pSubmits, VkFence fence); +VkResult WINAPI wine_vkSetPrivateDataEXT(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlotEXT privateDataSlot, uint64_t data) DECLSPEC_HIDDEN; /* Private thunks */ VkResult thunk_vkGetPhysicalDeviceImageFormatProperties2(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2 *pImageFormatInfo, VkImageFormatProperties2 *pImageFormatProperties) DECLSPEC_HIDDEN; -- 2.27.0
Signed-off-by: Georg Lehmann <dadschoorse(a)gmail.com> --- dlls/vulkan-1/tests/vulkan.c | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/dlls/vulkan-1/tests/vulkan.c b/dlls/vulkan-1/tests/vulkan.c index 6f9e9f115c..e71c467dde 100644 --- a/dlls/vulkan-1/tests/vulkan.c +++ b/dlls/vulkan-1/tests/vulkan.c @@ -387,6 +387,56 @@ static void test_unsupported_device_extensions(VkPhysicalDevice vk_physical_devi } } +static void test_private_data(VkPhysicalDevice vk_physical_device) +{ + PFN_vkDestroyPrivateDataSlotEXT pfn_vkDestroyPrivateDataSlotEXT; + PFN_vkCreatePrivateDataSlotEXT pfn_vkCreatePrivateDataSlotEXT; + VkPrivateDataSlotCreateInfoEXT data_create_info; + PFN_vkGetPrivateDataEXT pfn_vkGetPrivateDataEXT; + PFN_vkSetPrivateDataEXT pfn_vkSetPrivateDataEXT; + VkPrivateDataSlotEXT data_slot; + VkDevice vk_device; + uint64_t data; + VkResult vr; + + static const uint64_t data_value = 0x7A0D; + + static const char *ext_name = "VK_EXT_private_data"; + + if ((vr = create_device(vk_physical_device, 1, &ext_name, NULL, &vk_device)) < 0) + { + skip("Failed to create device with VK_EXT_private_data, VkResult %d.\n", vr); + return; + } + + pfn_vkDestroyPrivateDataSlotEXT = + (void*) vkGetDeviceProcAddr(vk_device, "vkDestroyPrivateDataSlotEXT"); + pfn_vkCreatePrivateDataSlotEXT = + (void*) vkGetDeviceProcAddr(vk_device, "vkCreatePrivateDataSlotEXT"); + pfn_vkGetPrivateDataEXT = + (void*) vkGetDeviceProcAddr(vk_device, "vkGetPrivateDataEXT"); + pfn_vkSetPrivateDataEXT = + (void*) vkGetDeviceProcAddr(vk_device, "vkSetPrivateDataEXT"); + + data_create_info.sType = VK_STRUCTURE_TYPE_PRIVATE_DATA_SLOT_CREATE_INFO_EXT; + data_create_info.pNext = NULL; + data_create_info.flags = 0; + vr = pfn_vkCreatePrivateDataSlotEXT(vk_device, &data_create_info, NULL, &data_slot); + ok(vr == VK_SUCCESS, "Failed to create private data slot, VkResult %d.\n", vr); + + vr = pfn_vkSetPrivateDataEXT(vk_device, VK_OBJECT_TYPE_DEVICE, + (uint64_t) (uintptr_t) vk_device, data_slot, data_value); + ok(vr == VK_SUCCESS, "Failed to set private data, VkResult %d.\n", vr); + + pfn_vkGetPrivateDataEXT(vk_device, VK_OBJECT_TYPE_DEVICE, + (uint64_t) (uintptr_t) vk_device, data_slot, &data); + ok(data == data_value, "Got unexpected private data, %s.\n", + wine_dbgstr_longlong(data)); + + pfn_vkDestroyPrivateDataSlotEXT(vk_device, data_slot, NULL); + vkDestroyDevice(vk_device, NULL); +} + static void for_each_device(void (*test_func)(VkPhysicalDevice)) { VkPhysicalDevice *vk_physical_devices; @@ -430,4 +480,5 @@ START_TEST(vulkan) for_each_device(test_destroy_command_pool); test_unsupported_instance_extensions(); for_each_device(test_unsupported_device_extensions); + for_each_device(test_private_data); } -- 2.27.0
Signed-off-by: Liam Middlebrook <lmiddlebrook(a)nvidia.com> On 7/7/20 3:40 PM, Georg Lehmann wrote:
Signed-off-by: Georg Lehmann <dadschoorse(a)gmail.com> --- dlls/vulkan-1/tests/vulkan.c | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+)
diff --git a/dlls/vulkan-1/tests/vulkan.c b/dlls/vulkan-1/tests/vulkan.c index 6f9e9f115c..e71c467dde 100644 --- a/dlls/vulkan-1/tests/vulkan.c +++ b/dlls/vulkan-1/tests/vulkan.c @@ -387,6 +387,56 @@ static void test_unsupported_device_extensions(VkPhysicalDevice vk_physical_devi } }
+static void test_private_data(VkPhysicalDevice vk_physical_device) +{ + PFN_vkDestroyPrivateDataSlotEXT pfn_vkDestroyPrivateDataSlotEXT; + PFN_vkCreatePrivateDataSlotEXT pfn_vkCreatePrivateDataSlotEXT; + VkPrivateDataSlotCreateInfoEXT data_create_info; + PFN_vkGetPrivateDataEXT pfn_vkGetPrivateDataEXT; + PFN_vkSetPrivateDataEXT pfn_vkSetPrivateDataEXT; + VkPrivateDataSlotEXT data_slot; + VkDevice vk_device; + uint64_t data; + VkResult vr; + + static const uint64_t data_value = 0x7A0D; + + static const char *ext_name = "VK_EXT_private_data"; + + if ((vr = create_device(vk_physical_device, 1, &ext_name, NULL, &vk_device)) < 0) + { + skip("Failed to create device with VK_EXT_private_data, VkResult %d.\n", vr); + return; + } + + pfn_vkDestroyPrivateDataSlotEXT = + (void*) vkGetDeviceProcAddr(vk_device, "vkDestroyPrivateDataSlotEXT"); + pfn_vkCreatePrivateDataSlotEXT = + (void*) vkGetDeviceProcAddr(vk_device, "vkCreatePrivateDataSlotEXT"); + pfn_vkGetPrivateDataEXT = + (void*) vkGetDeviceProcAddr(vk_device, "vkGetPrivateDataEXT"); + pfn_vkSetPrivateDataEXT = + (void*) vkGetDeviceProcAddr(vk_device, "vkSetPrivateDataEXT"); + + data_create_info.sType = VK_STRUCTURE_TYPE_PRIVATE_DATA_SLOT_CREATE_INFO_EXT; + data_create_info.pNext = NULL; + data_create_info.flags = 0; + vr = pfn_vkCreatePrivateDataSlotEXT(vk_device, &data_create_info, NULL, &data_slot); + ok(vr == VK_SUCCESS, "Failed to create private data slot, VkResult %d.\n", vr); + + vr = pfn_vkSetPrivateDataEXT(vk_device, VK_OBJECT_TYPE_DEVICE, + (uint64_t) (uintptr_t) vk_device, data_slot, data_value); + ok(vr == VK_SUCCESS, "Failed to set private data, VkResult %d.\n", vr); + + pfn_vkGetPrivateDataEXT(vk_device, VK_OBJECT_TYPE_DEVICE, + (uint64_t) (uintptr_t) vk_device, data_slot, &data); + ok(data == data_value, "Got unexpected private data, %s.\n", + wine_dbgstr_longlong(data)); + + pfn_vkDestroyPrivateDataSlotEXT(vk_device, data_slot, NULL); + vkDestroyDevice(vk_device, NULL); +} + static void for_each_device(void (*test_func)(VkPhysicalDevice)) { VkPhysicalDevice *vk_physical_devices; @@ -430,4 +480,5 @@ START_TEST(vulkan) for_each_device(test_destroy_command_pool); test_unsupported_instance_extensions(); for_each_device(test_unsupported_device_extensions); + for_each_device(test_private_data); }
I think it would be good to put a comment in make_vulkan's native_handle() so that if any new objects need to be wrapped the author of a future change will remember to expand the unwrap_object_handle() implementation you've made here. Alternatively, we could probably implement this make_vulkan with generated functions, but I think it's easier to understand how you've written it out below. Thanks, Liam Middlebrook On 7/7/20 3:40 PM, Georg Lehmann wrote:
Signed-off-by: Georg Lehmann <dadschoorse(a)gmail.com> --- dlls/winevulkan/make_vulkan | 4 ++++ dlls/winevulkan/vulkan.c | 37 +++++++++++++++++++++++++++++++++ dlls/winevulkan/vulkan_thunks.c | 12 ----------- dlls/winevulkan/vulkan_thunks.h | 2 ++ 4 files changed, 43 insertions(+), 12 deletions(-)
diff --git a/dlls/winevulkan/make_vulkan b/dlls/winevulkan/make_vulkan index a0bf6b0859..dc1e03d326 100755 --- a/dlls/winevulkan/make_vulkan +++ b/dlls/winevulkan/make_vulkan @@ -217,6 +217,10 @@ FUNCTION_OVERRIDES = { # VK_KHR_device_group "vkGetDeviceGroupSurfacePresentModesKHR" : {"dispatch" : True, "driver" : True, "thunk" : True}, "vkGetPhysicalDevicePresentRectanglesKHR" : {"dispatch" : True, "driver" : True, "thunk" : True}, + + # VK_EXT_private_data + "vkGetPrivateDataEXT" : {"dispatch": True, "driver" : False, "thunk" : False}, + "vkSetPrivateDataEXT" : {"dispatch": True, "driver" : False, "thunk" : False}, }
STRUCT_CHAIN_CONVERSIONS = [ diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c index d2da92cfa2..4642975ad0 100644 --- a/dlls/winevulkan/vulkan.c +++ b/dlls/winevulkan/vulkan.c @@ -1372,6 +1372,43 @@ void WINAPI wine_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR(VkPhysicalDev properties->externalSemaphoreFeatures = 0; }
+static uint64_t unwrap_object_handle(VkObjectType type, uint64_t handle) +{ + switch (type) + { + case VK_OBJECT_TYPE_DEVICE: + return (uint64_t) (uintptr_t) ((VkDevice) (uintptr_t) handle)->device; + case VK_OBJECT_TYPE_QUEUE: + return (uint64_t) (uintptr_t) ((VkQueue) (uintptr_t) handle)->queue; + case VK_OBJECT_TYPE_COMMAND_BUFFER: + return (uint64_t) (uintptr_t) ((VkCommandBuffer) (uintptr_t) handle)->command_buffer; + case VK_OBJECT_TYPE_COMMAND_POOL: + return (uint64_t) wine_cmd_pool_from_handle(handle)->command_pool; + default: + return handle; + } +} + +VkResult WINAPI wine_vkSetPrivateDataEXT(VkDevice device, VkObjectType object_type, uint64_t object_handle, + VkPrivateDataSlotEXT private_data_slot, uint64_t data) +{ + TRACE("%p, %#x, 0x%s, 0x%s, 0x%s\n", device, object_type, wine_dbgstr_longlong(object_handle), + wine_dbgstr_longlong(private_data_slot), wine_dbgstr_longlong(data)); + + object_handle = unwrap_object_handle(object_type, object_handle); + return device->funcs.p_vkSetPrivateDataEXT(device->device, object_type, object_handle, private_data_slot, data); +} + +void WINAPI wine_vkGetPrivateDataEXT(VkDevice device, VkObjectType object_type, uint64_t object_handle, + VkPrivateDataSlotEXT private_data_slot, uint64_t *data) +{ + TRACE("%p, %#x, 0x%s, 0x%s, %p\n", device, object_type, wine_dbgstr_longlong(object_handle), + wine_dbgstr_longlong(private_data_slot), data); + + object_handle = unwrap_object_handle(object_type, object_handle); + device->funcs.p_vkGetPrivateDataEXT(device->device, object_type, object_handle, private_data_slot, data); +} + BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, void *reserved) { TRACE("%p, %u, %p\n", hinst, reason, reserved); diff --git a/dlls/winevulkan/vulkan_thunks.c b/dlls/winevulkan/vulkan_thunks.c index f8d171a6db..94aa1e3fb7 100644 --- a/dlls/winevulkan/vulkan_thunks.c +++ b/dlls/winevulkan/vulkan_thunks.c @@ -5066,12 +5066,6 @@ static VkResult WINAPI wine_vkGetPipelineExecutableStatisticsKHR(VkDevice device #endif }
-static void WINAPI wine_vkGetPrivateDataEXT(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlotEXT privateDataSlot, uint64_t *pData) -{ - TRACE("%p, %#x, 0x%s, 0x%s, %p\n", device, objectType, wine_dbgstr_longlong(objectHandle), wine_dbgstr_longlong(privateDataSlot), pData); - device->funcs.p_vkGetPrivateDataEXT(device->device, objectType, objectHandle, privateDataSlot, pData); -} - VkResult WINAPI wine_vkGetQueryPoolResults(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, size_t dataSize, void *pData, VkDeviceSize stride, VkQueryResultFlags flags) { TRACE("%p, 0x%s, %u, %u, 0x%s, %p, 0x%s, %#x\n", device, wine_dbgstr_longlong(queryPool), firstQuery, queryCount, wine_dbgstr_longlong(dataSize), pData, wine_dbgstr_longlong(stride), flags); @@ -5264,12 +5258,6 @@ VkResult WINAPI wine_vkSetEvent(VkDevice device, VkEvent event) return device->funcs.p_vkSetEvent(device->device, event); }
-static VkResult WINAPI wine_vkSetPrivateDataEXT(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlotEXT privateDataSlot, uint64_t data) -{ - TRACE("%p, %#x, 0x%s, 0x%s, 0x%s\n", device, objectType, wine_dbgstr_longlong(objectHandle), wine_dbgstr_longlong(privateDataSlot), wine_dbgstr_longlong(data)); - return device->funcs.p_vkSetPrivateDataEXT(device->device, objectType, objectHandle, privateDataSlot, data); -} - VkResult WINAPI wine_vkSignalSemaphore(VkDevice device, const VkSemaphoreSignalInfo *pSignalInfo) { #if defined(USE_STRUCT_CONVERSION) diff --git a/dlls/winevulkan/vulkan_thunks.h b/dlls/winevulkan/vulkan_thunks.h index 9580c6bbcd..7055d4fb13 100644 --- a/dlls/winevulkan/vulkan_thunks.h +++ b/dlls/winevulkan/vulkan_thunks.h @@ -40,7 +40,9 @@ VkResult WINAPI wine_vkGetPhysicalDeviceImageFormatProperties2(VkPhysicalDevice VkResult WINAPI wine_vkGetPhysicalDeviceImageFormatProperties2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2 *pImageFormatInfo, VkImageFormatProperties2 *pImageFormatProperties) DECLSPEC_HIDDEN; void WINAPI wine_vkGetPhysicalDeviceProperties2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2 *pProperties); void WINAPI wine_vkGetPhysicalDeviceProperties2KHR(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2 *pProperties) DECLSPEC_HIDDEN; +void WINAPI wine_vkGetPrivateDataEXT(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlotEXT privateDataSlot, uint64_t *pData) DECLSPEC_HIDDEN; VkResult WINAPI wine_vkQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo *pSubmits, VkFence fence); +VkResult WINAPI wine_vkSetPrivateDataEXT(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlotEXT privateDataSlot, uint64_t data) DECLSPEC_HIDDEN;
/* Private thunks */ VkResult thunk_vkGetPhysicalDeviceImageFormatProperties2(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2 *pImageFormatInfo, VkImageFormatProperties2 *pImageFormatProperties) DECLSPEC_HIDDEN;
participants (2)
-
Georg Lehmann -
Liam Middlebrook