diff options
| author | Mark Lobodzinski <mark@lunarg.com> | 2017-06-09 16:35:43 -0600 |
|---|---|---|
| committer | Mark Lobodzinski <mark@lunarg.com> | 2017-06-12 07:32:50 -0600 |
| commit | fb005c48ca6d7effceb10dfa39999e2a3674c64d (patch) | |
| tree | c3910bcf21826622abc24a6927d4e431db625404 | |
| parent | 898f916d8468dcbccf9ee013fc451139fa1e92cb (diff) | |
| download | usermoji-fb005c48ca6d7effceb10dfa39999e2a3674c64d.tar.xz | |
layers: Move remaining Swapchain checks into CV
Also refactored modified routines for pre/post call structure.
Change-Id: I192e11c1d04effa8d2f3478c1341f6eedea7138a
| -rw-r--r-- | layers/core_validation.cpp | 207 | ||||
| -rw-r--r-- | layers/core_validation.h | 9 | ||||
| -rw-r--r-- | layers/core_validation_error_enums.h | 32 | ||||
| -rw-r--r-- | layers/core_validation_types.h | 8 |
4 files changed, 218 insertions, 38 deletions
diff --git a/layers/core_validation.cpp b/layers/core_validation.cpp index 2e31419d..62a7131d 100644 --- a/layers/core_validation.cpp +++ b/layers/core_validation.cpp @@ -8803,51 +8803,93 @@ VKAPI_ATTR void VKAPI_CALL DestroySwapchainKHR(VkDevice device, VkSwapchainKHR s if (!skip) dev_data->dispatch_table.DestroySwapchainKHR(device, swapchain, pAllocator); } -VKAPI_ATTR VkResult VKAPI_CALL GetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint32_t *pCount, - VkImage *pSwapchainImages) { - layer_data *dev_data = GetLayerDataPtr(get_dispatch_key(device), layer_data_map); - VkResult result = dev_data->dispatch_table.GetSwapchainImagesKHR(device, swapchain, pCount, pSwapchainImages); - - if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && pSwapchainImages != nullptr) { - // This should never happen and is checked by param checker. - if (!pCount) return result; +static bool PreCallValidateGetSwapchainImagesKHR(layer_data *device_data, SWAPCHAIN_NODE *swapchain_state, VkDevice device, + uint32_t *pSwapchainImageCount, VkImage *pSwapchainImages) { + bool skip = false; + if (swapchain_state && pSwapchainImages) { std::lock_guard<std::mutex> lock(global_lock); - auto swapchain_node = GetSwapchainNode(dev_data, swapchain); + // Compare the preliminary value of *pSwapchainImageCount with the value this time: + if (swapchain_state->vkGetSwapchainImagesKHRState == UNCALLED) { + skip |= log_msg(device_data->report_data, VK_DEBUG_REPORT_WARNING_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT, + HandleToUint64(device), __LINE__, SWAPCHAIN_PRIOR_COUNT, "DS", + "vkGetSwapchainImagesKHR() called with non-NULL pSwapchainImageCount; but no prior positive " + "value has been seen for pSwapchainImages."); + } else if (*pSwapchainImageCount > swapchain_state->get_swapchain_image_count) { + skip |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT, + HandleToUint64(device), __LINE__, SWAPCHAIN_INVALID_COUNT, "DS", + "vkGetSwapchainImagesKHR() called with non-NULL pSwapchainImageCount, and with " + "pSwapchainImages set to a value (%d) that is greater than the value (%d) that was returned when " + "pSwapchainImageCount was NULL.", + *pSwapchainImageCount, swapchain_state->get_swapchain_image_count); + } + } + return skip; +} + +static void PostCallRecordGetSwapchainImagesKHR(layer_data *device_data, SWAPCHAIN_NODE *swapchain_state, VkDevice device, + uint32_t *pSwapchainImageCount, VkImage *pSwapchainImages) { + std::lock_guard<std::mutex> lock(global_lock); - if (*pCount > swapchain_node->images.size()) - swapchain_node->images.resize(*pCount); + if (*pSwapchainImageCount > swapchain_state->images.size()) swapchain_state->images.resize(*pSwapchainImageCount); - for (uint32_t i = 0; i < *pCount; ++i) { - if (swapchain_node->images[i] != VK_NULL_HANDLE) - continue; // Already retrieved this. + if (pSwapchainImages) { + if (swapchain_state->vkGetSwapchainImagesKHRState < QUERY_DETAILS) { + swapchain_state->vkGetSwapchainImagesKHRState = QUERY_DETAILS; + } + for (uint32_t i = 0; i < *pSwapchainImageCount; ++i) { + if (swapchain_state->images[i] != VK_NULL_HANDLE) continue; // Already retrieved this. IMAGE_LAYOUT_NODE image_layout_node; image_layout_node.layout = VK_IMAGE_LAYOUT_UNDEFINED; - image_layout_node.format = swapchain_node->createInfo.imageFormat; + image_layout_node.format = swapchain_state->createInfo.imageFormat; // Add imageMap entries for each swapchain image VkImageCreateInfo image_ci = {}; image_ci.flags = 0; image_ci.imageType = VK_IMAGE_TYPE_2D; - image_ci.format = swapchain_node->createInfo.imageFormat; - image_ci.extent.width = swapchain_node->createInfo.imageExtent.width; - image_ci.extent.height = swapchain_node->createInfo.imageExtent.height; + image_ci.format = swapchain_state->createInfo.imageFormat; + image_ci.extent.width = swapchain_state->createInfo.imageExtent.width; + image_ci.extent.height = swapchain_state->createInfo.imageExtent.height; image_ci.extent.depth = 1; image_ci.mipLevels = 1; - image_ci.arrayLayers = swapchain_node->createInfo.imageArrayLayers; + image_ci.arrayLayers = swapchain_state->createInfo.imageArrayLayers; image_ci.samples = VK_SAMPLE_COUNT_1_BIT; image_ci.tiling = VK_IMAGE_TILING_OPTIMAL; - image_ci.usage = swapchain_node->createInfo.imageUsage; - image_ci.sharingMode = swapchain_node->createInfo.imageSharingMode; - dev_data->imageMap[pSwapchainImages[i]] = unique_ptr<IMAGE_STATE>(new IMAGE_STATE(pSwapchainImages[i], &image_ci)); - auto &image_state = dev_data->imageMap[pSwapchainImages[i]]; + image_ci.usage = swapchain_state->createInfo.imageUsage; + image_ci.sharingMode = swapchain_state->createInfo.imageSharingMode; + device_data->imageMap[pSwapchainImages[i]] = unique_ptr<IMAGE_STATE>(new IMAGE_STATE(pSwapchainImages[i], &image_ci)); + auto &image_state = device_data->imageMap[pSwapchainImages[i]]; image_state->valid = false; image_state->binding.mem = MEMTRACKER_SWAP_CHAIN_IMAGE_KEY; - swapchain_node->images[i] = pSwapchainImages[i]; + swapchain_state->images[i] = pSwapchainImages[i]; ImageSubresourcePair subpair = {pSwapchainImages[i], false, VkImageSubresource()}; - dev_data->imageSubresourceMap[pSwapchainImages[i]].push_back(subpair); - dev_data->imageLayoutMap[subpair] = image_layout_node; + device_data->imageSubresourceMap[pSwapchainImages[i]].push_back(subpair); + device_data->imageLayoutMap[subpair] = image_layout_node; } } + + if (*pSwapchainImageCount) { + if (swapchain_state->vkGetSwapchainImagesKHRState < QUERY_COUNT) { + swapchain_state->vkGetSwapchainImagesKHRState = QUERY_COUNT; + } + swapchain_state->get_swapchain_image_count = *pSwapchainImageCount; + } +} + +VKAPI_ATTR VkResult VKAPI_CALL GetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint32_t *pSwapchainImageCount, + VkImage *pSwapchainImages) { + VkResult result = VK_ERROR_VALIDATION_FAILED_EXT; + layer_data *device_data = GetLayerDataPtr(get_dispatch_key(device), layer_data_map); + + auto swapchain_state = GetSwapchainNode(device_data, swapchain); + bool skip = PreCallValidateGetSwapchainImagesKHR(device_data, swapchain_state, device, pSwapchainImageCount, pSwapchainImages); + + if (!skip) { + result = device_data->dispatch_table.GetSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, pSwapchainImages); + } + + if ((result == VK_SUCCESS || result == VK_INCOMPLETE)) { + PostCallRecordGetSwapchainImagesKHR(device_data, swapchain_state, device, pSwapchainImageCount, pSwapchainImages); + } return result; } @@ -9399,14 +9441,15 @@ VKAPI_ATTR void VKAPI_CALL DestroySurfaceKHR(VkInstance instance, VkSurfaceKHR s std::unique_lock<std::mutex> lock(global_lock); auto surface_state = GetSurfaceState(instance_data, surface); - if (surface_state) { - // TODO: track swapchains created from this surface. - instance_data->surface_map.erase(surface); + if ((surface_state) && (surface_state->swapchain)) { + skip |= log_msg(instance_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT, + HandleToUint64(instance), __LINE__, VALIDATION_ERROR_26c009e4, "DS", + "vkDestroySurfaceKHR() called before its associated VkSwapchainKHR was destroyed. %s", + validation_error_map[VALIDATION_ERROR_26c009e4]); } + instance_data->surface_map.erase(surface); lock.unlock(); - if (!skip) { - // Call down the call chain: instance_data->dispatch_table.DestroySurfaceKHR(instance, surface, pAllocator); } } @@ -10000,6 +10043,105 @@ VKAPI_ATTR void VKAPI_CALL CmdPushDescriptorSetWithTemplateKHR(VkCommandBuffer c dev_data->dispatch_table.CmdPushDescriptorSetWithTemplateKHR(commandBuffer, descriptorUpdateTemplate, layout, set, pData); } +static void PostCallRecordGetPhysicalDeviceDisplayPlanePropertiesKHR(instance_layer_data *instanceData, + VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, + VkDisplayPlanePropertiesKHR *pProperties) { + std::unique_lock<std::mutex> lock(global_lock); + auto physical_device_state = GetPhysicalDeviceState(instanceData, physicalDevice); + + if (*pPropertyCount) { + if (physical_device_state->vkGetPhysicalDeviceSurfaceFormatsKHRState < QUERY_COUNT) { + physical_device_state->vkGetPhysicalDeviceSurfaceFormatsKHRState = QUERY_COUNT; + } + physical_device_state->display_plane_property_count = *pPropertyCount; + } + if (pProperties) { + if (physical_device_state->vkGetPhysicalDeviceSurfaceFormatsKHRState < QUERY_DETAILS) { + physical_device_state->vkGetPhysicalDeviceSurfaceFormatsKHRState = QUERY_DETAILS; + } + } +} + +VKAPI_ATTR VkResult VKAPI_CALL GetPhysicalDeviceDisplayPlanePropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, + VkDisplayPlanePropertiesKHR *pProperties) { + VkResult result = VK_SUCCESS; + instance_layer_data *instance_data = GetLayerDataPtr(get_dispatch_key(physicalDevice), instance_layer_data_map); + + result = instance_data->dispatch_table.GetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, pPropertyCount, pProperties); + + if (result == VK_SUCCESS || result == VK_INCOMPLETE) { + PostCallRecordGetPhysicalDeviceDisplayPlanePropertiesKHR(instance_data, physicalDevice, pPropertyCount, pProperties); + } + + return result; +} + +static bool ValidateGetPhysicalDeviceDisplayPlanePropertiesKHRQuery(instance_layer_data *instance_data, + VkPhysicalDevice physicalDevice, uint32_t planeIndex, + const char *api_name) { + bool skip = false; + auto physical_device_state = GetPhysicalDeviceState(instance_data, physicalDevice); + if (physical_device_state->vkGetPhysicalDeviceSurfaceFormatsKHRState == UNCALLED) { + skip |= log_msg( + instance_data->report_data, VK_DEBUG_REPORT_WARNING_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT, + HandleToUint64(physicalDevice), __LINE__, SWAPCHAIN_GET_SUPPORTED_DISPLAYS_WITHOUT_QUERY, "DL", + "Potential problem with calling %s() without first querying vkGetPhysicalDeviceDisplayPlanePropertiesKHR.", api_name); + } else { + if (planeIndex >= physical_device_state->display_plane_property_count) { + skip |= log_msg( + instance_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT, + HandleToUint64(physicalDevice), __LINE__, VALIDATION_ERROR_29c009c2, "DL", + "%s(): planeIndex must be in the range [0, %d] that was returned by vkGetPhysicalDeviceDisplayPlanePropertiesKHR. " + "Do you have the plane index hardcoded? %s", + api_name, physical_device_state->display_plane_property_count - 1, validation_error_map[VALIDATION_ERROR_29c009c2]); + } + } + return skip; +} + +static bool PreCallValidateGetDisplayPlaneSupportedDisplaysKHR(instance_layer_data *instance_data, VkPhysicalDevice physicalDevice, + uint32_t planeIndex) { + bool skip = false; + std::lock_guard<std::mutex> lock(global_lock); + skip |= ValidateGetPhysicalDeviceDisplayPlanePropertiesKHRQuery(instance_data, physicalDevice, planeIndex, + "vkGetDisplayPlaneSupportedDisplaysKHR"); + return skip; +} + +VKAPI_ATTR VkResult VKAPI_CALL GetDisplayPlaneSupportedDisplaysKHR(VkPhysicalDevice physicalDevice, uint32_t planeIndex, + uint32_t *pDisplayCount, VkDisplayKHR *pDisplays) { + VkResult result = VK_ERROR_VALIDATION_FAILED_EXT; + instance_layer_data *instance_data = GetLayerDataPtr(get_dispatch_key(physicalDevice), instance_layer_data_map); + bool skip = PreCallValidateGetDisplayPlaneSupportedDisplaysKHR(instance_data, physicalDevice, planeIndex); + if (!skip) { + result = + instance_data->dispatch_table.GetDisplayPlaneSupportedDisplaysKHR(physicalDevice, planeIndex, pDisplayCount, pDisplays); + } + return result; +} + +static bool PreCallValidateGetDisplayPlaneCapabilitiesKHR(instance_layer_data *instance_data, VkPhysicalDevice physicalDevice, + uint32_t planeIndex) { + bool skip = false; + std::lock_guard<std::mutex> lock(global_lock); + skip |= ValidateGetPhysicalDeviceDisplayPlanePropertiesKHRQuery(instance_data, physicalDevice, planeIndex, + "vkGetDisplayPlaneCapabilitiesKHR"); + return skip; +} + +VKAPI_ATTR VkResult VKAPI_CALL GetDisplayPlaneCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkDisplayModeKHR mode, + uint32_t planeIndex, VkDisplayPlaneCapabilitiesKHR *pCapabilities) { + VkResult result = VK_ERROR_VALIDATION_FAILED_EXT; + instance_layer_data *instance_data = GetLayerDataPtr(get_dispatch_key(physicalDevice), instance_layer_data_map); + bool skip = PreCallValidateGetDisplayPlaneCapabilitiesKHR(instance_data, physicalDevice, planeIndex); + + if (!skip) { + result = instance_data->dispatch_table.GetDisplayPlaneCapabilitiesKHR(physicalDevice, mode, planeIndex, pCapabilities); + } + + return result; +} + VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice device, const char *funcName); VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetPhysicalDeviceProcAddr(VkInstance instance, const char *funcName); VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetInstanceProcAddr(VkInstance instance, const char *funcName); @@ -10179,6 +10321,9 @@ static const std::unordered_map<std::string, void*> name_to_funcptr_map = { {"vkCreateDebugReportCallbackEXT", (void*)CreateDebugReportCallbackEXT}, {"vkDestroyDebugReportCallbackEXT", (void*)DestroyDebugReportCallbackEXT}, {"vkDebugReportMessageEXT", (void*)DebugReportMessageEXT}, + {"vkGetPhysicalDeviceDisplayPlanePropertiesKHR", (void*)GetPhysicalDeviceDisplayPlanePropertiesKHR}, + {"GetDisplayPlaneSupportedDisplaysKHR", (void*)GetDisplayPlaneSupportedDisplaysKHR}, + {"GetDisplayPlaneCapabilitiesKHR", (void*)GetDisplayPlaneCapabilitiesKHR}, }; VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice device, const char *funcName) { diff --git a/layers/core_validation.h b/layers/core_validation.h index fd1c8b4d..460dba37 100644 --- a/layers/core_validation.h +++ b/layers/core_validation.h @@ -140,13 +140,6 @@ class QUERY_POOL_NODE : public BASE_NODE { VkQueryPoolCreateInfo createInfo; }; -// Stuff from Device Limits Layer -enum CALL_STATE { - UNCALLED, // Function has not been called - QUERY_COUNT, // Function called once to query a count - QUERY_DETAILS, // Function called w/ a count to query details -}; - struct PHYSICAL_DEVICE_STATE { // Track the call state and array sizes for various query functions CALL_STATE vkGetPhysicalDeviceQueueFamilyPropertiesState = UNCALLED; @@ -156,6 +149,7 @@ struct PHYSICAL_DEVICE_STATE { CALL_STATE vkGetPhysicalDeviceSurfaceCapabilitiesKHRState = UNCALLED; CALL_STATE vkGetPhysicalDeviceSurfacePresentModesKHRState = UNCALLED; CALL_STATE vkGetPhysicalDeviceSurfaceFormatsKHRState = UNCALLED; + CALL_STATE vkGetPhysicalDeviceDisplayPlanePropertiesKHRState = UNCALLED; VkPhysicalDeviceFeatures features = {}; VkPhysicalDevice phys_device = VK_NULL_HANDLE; uint32_t queue_family_count = 0; @@ -163,6 +157,7 @@ struct PHYSICAL_DEVICE_STATE { VkSurfaceCapabilitiesKHR surfaceCapabilities = {}; std::vector<VkPresentModeKHR> present_modes; std::vector<VkSurfaceFormatKHR> surface_formats; + uint32_t display_plane_property_count = 0; }; struct GpuQueue { diff --git a/layers/core_validation_error_enums.h b/layers/core_validation_error_enums.h index c9a3d1eb..12099c32 100644 --- a/layers/core_validation_error_enums.h +++ b/layers/core_validation_error_enums.h @@ -201,4 +201,36 @@ enum IMAGE_ERROR { IMAGE_INVALID_FORMAT_LIMITS_VIOLATION, IMAGE_ZERO_AREA_SUBREGION, }; + +enum SWAPCHAIN_ERROR { + SWAPCHAIN_INVALID_HANDLE, // Handle used that isn't currently valid + SWAPCHAIN_NULL_POINTER, // Pointer set to NULL, instead of being a valid pointer + SWAPCHAIN_EXT_NOT_ENABLED_BUT_USED, // Did not enable WSI extension, but called WSI function + SWAPCHAIN_DEL_OBJECT_BEFORE_CHILDREN, // Called vkDestroyDevice() before vkDestroySwapchainKHR() + SWAPCHAIN_CREATE_UNSUPPORTED_SURFACE, // Called vkCreateSwapchainKHR() with a pCreateInfo->surface that wasn't supported + SWAPCHAIN_CREATE_SWAP_WITHOUT_QUERY, // Called vkCreateSwapchainKHR() without calling a query + SWAPCHAIN_CREATE_SWAP_OUT_OF_BOUNDS_EXTENTS, // Called vkCreateSwapchainKHR() with out-of-bounds imageExtent + SWAPCHAIN_CREATE_SWAP_EXTENTS_NO_MATCH_WIN, // Called vkCreateSwapchainKHR w/imageExtent that doesn't match window's extent + SWAPCHAIN_CREATE_SWAP_BAD_PRE_TRANSFORM, // Called vkCreateSwapchainKHR() with a non-supported preTransform + SWAPCHAIN_CREATE_SWAP_BAD_COMPOSITE_ALPHA, // Called vkCreateSwapchainKHR() with a non-supported compositeAlpha + SWAPCHAIN_CREATE_SWAP_BAD_IMG_ARRAY_LAYERS, // Called vkCreateSwapchainKHR() with a non-supported imageArrayLayers + SWAPCHAIN_CREATE_SWAP_BAD_IMG_USAGE_FLAGS, // Called vkCreateSwapchainKHR() with a non-supported imageUsageFlags + SWAPCHAIN_CREATE_SWAP_BAD_IMG_COLOR_SPACE, // Called vkCreateSwapchainKHR() with a non-supported imageColorSpace + SWAPCHAIN_CREATE_SWAP_BAD_IMG_FORMAT, // Called vkCreateSwapchainKHR() with a non-supported imageFormat + SWAPCHAIN_CREATE_SWAP_BAD_IMG_FMT_CLR_SP, // Called vkCreateSwapchainKHR() with a non-supported imageColorSpace + SWAPCHAIN_CREATE_SWAP_BAD_PRESENT_MODE, // Called vkCreateSwapchainKHR() with a non-supported presentMode + SWAPCHAIN_CREATE_SWAP_BAD_SHARING_MODE, // Called vkCreateSwapchainKHR() with a non-supported imageSharingMode + SWAPCHAIN_CREATE_SWAP_BAD_SHARING_VALUES, // Called vkCreateSwapchainKHR() with bad values when imageSharingMode is + // VK_SHARING_MODE_CONCURRENT + SWAPCHAIN_BAD_BOOL, // VkBool32 that doesn't have value of VK_TRUE or VK_FALSE (e.g. is a non-zero form of true) + SWAPCHAIN_PRIOR_COUNT, // Query must be called first to get value of pCount, then called second time + SWAPCHAIN_INVALID_COUNT, // Second time a query called, the pCount value didn't match first time + SWAPCHAIN_WRONG_STYPE, // The sType for a struct has the wrong value + SWAPCHAIN_WRONG_NEXT, // The pNext for a struct is not NULL + SWAPCHAIN_ZERO_VALUE, // A value should be non-zero + SWAPCHAIN_GET_SUPPORTED_DISPLAYS_WITHOUT_QUERY, // vkGetDisplayPlaneSupportedDisplaysKHR should be called after querying + // device display plane properties + SWAPCHAIN_PLANE_INDEX_TOO_LARGE, // a planeIndex value is larger than what vkGetDisplayPlaneSupportedDisplaysKHR returns +}; + #endif // CORE_VALIDATION_ERROR_ENUMS_H_ diff --git a/layers/core_validation_types.h b/layers/core_validation_types.h index 1555850d..ae9c69ea 100644 --- a/layers/core_validation_types.h +++ b/layers/core_validation_types.h @@ -70,6 +70,12 @@ class DescriptorSet; struct GLOBAL_CB_NODE; +enum CALL_STATE { + UNCALLED, // Function has not been called + QUERY_COUNT, // Function called once to query a count + QUERY_DETAILS, // Function called w/ a count to query details +}; + class BASE_NODE { public: // Track when object is being used by an in-flight command buffer @@ -345,6 +351,8 @@ class SWAPCHAIN_NODE { std::vector<VkImage> images; bool replaced = false; bool shared_presentable = false; + CALL_STATE vkGetSwapchainImagesKHRState = UNCALLED; + uint32_t get_swapchain_image_count = 0; SWAPCHAIN_NODE(const VkSwapchainCreateInfoKHR *pCreateInfo, VkSwapchainKHR swapchain) : createInfo(pCreateInfo), swapchain(swapchain) {} }; |
