Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
08c1b0a0
Commit
08c1b0a0
authored
Apr 13, 2021
by
Jacek Caban
Committed by
Alexandre Julliard
Apr 13, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
winevulkan: Move ICD functions to loader.c.
Signed-off-by:
Jacek Caban
<
jacek@codeweavers.com
>
Signed-off-by:
Alexandre Julliard
<
julliard@winehq.org
>
parent
4278a30e
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
161 additions
and
157 deletions
+161
-157
loader.c
dlls/winevulkan/loader.c
+156
-0
make_vulkan
dlls/winevulkan/make_vulkan
+1
-1
vulkan.c
dlls/winevulkan/vulkan.c
+0
-156
vulkan_thunks.h
dlls/winevulkan/vulkan_thunks.h
+4
-0
No files found.
dlls/winevulkan/loader.c
View file @
08c1b0a0
...
...
@@ -28,8 +28,164 @@
WINE_DEFAULT_DEBUG_CHANNEL
(
vulkan
);
/* For now default to 4 as it felt like a reasonable version feature wise to support.
* Version 5 adds more extensive version checks. Something to tackle later.
*/
#define WINE_VULKAN_ICD_VERSION 4
static
HINSTANCE
hinstance
;
static
void
*
wine_vk_get_global_proc_addr
(
const
char
*
name
);
VkResult
WINAPI
wine_vkEnumerateInstanceLayerProperties
(
uint32_t
*
count
,
VkLayerProperties
*
properties
)
{
TRACE
(
"%p, %p
\n
"
,
count
,
properties
);
*
count
=
0
;
return
VK_SUCCESS
;
}
static
const
struct
vulkan_func
vk_global_dispatch_table
[]
=
{
/* These functions must call wine_vk_init_once() before accessing vk_funcs. */
{
"vkCreateInstance"
,
&
wine_vkCreateInstance
},
{
"vkEnumerateInstanceExtensionProperties"
,
&
wine_vkEnumerateInstanceExtensionProperties
},
{
"vkEnumerateInstanceLayerProperties"
,
&
wine_vkEnumerateInstanceLayerProperties
},
{
"vkEnumerateInstanceVersion"
,
&
wine_vkEnumerateInstanceVersion
},
{
"vkGetInstanceProcAddr"
,
&
wine_vkGetInstanceProcAddr
},
};
static
void
*
wine_vk_get_global_proc_addr
(
const
char
*
name
)
{
unsigned
int
i
;
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
vk_global_dispatch_table
);
i
++
)
{
if
(
strcmp
(
name
,
vk_global_dispatch_table
[
i
].
name
)
==
0
)
{
TRACE
(
"Found name=%s in global table
\n
"
,
debugstr_a
(
name
));
return
vk_global_dispatch_table
[
i
].
func
;
}
}
return
NULL
;
}
PFN_vkVoidFunction
WINAPI
wine_vkGetInstanceProcAddr
(
VkInstance
instance
,
const
char
*
name
)
{
void
*
func
;
TRACE
(
"%p, %s
\n
"
,
instance
,
debugstr_a
(
name
));
if
(
!
name
)
return
NULL
;
/* vkGetInstanceProcAddr can load most Vulkan functions when an instance is passed in, however
* for a NULL instance it can only load global functions.
*/
func
=
wine_vk_get_global_proc_addr
(
name
);
if
(
func
)
{
return
func
;
}
if
(
!
instance
)
{
WARN
(
"Global function %s not found.
\n
"
,
debugstr_a
(
name
));
return
NULL
;
}
func
=
wine_vk_get_instance_proc_addr
(
name
);
if
(
func
)
return
func
;
func
=
wine_vk_get_phys_dev_proc_addr
(
name
);
if
(
func
)
return
func
;
/* vkGetInstanceProcAddr also loads any children of instance, so device functions as well. */
func
=
wine_vk_get_device_proc_addr
(
name
);
if
(
func
)
return
func
;
WARN
(
"Unsupported device or instance function: %s.
\n
"
,
debugstr_a
(
name
));
return
NULL
;
}
PFN_vkVoidFunction
WINAPI
wine_vkGetDeviceProcAddr
(
VkDevice
device
,
const
char
*
name
)
{
void
*
func
;
TRACE
(
"%p, %s
\n
"
,
device
,
debugstr_a
(
name
));
/* The spec leaves return value undefined for a NULL device, let's just return NULL. */
if
(
!
device
||
!
name
)
return
NULL
;
/* Per the spec, we are only supposed to return device functions as in functions
* for which the first parameter is vkDevice or a child of vkDevice like a
* vkCommandBuffer or vkQueue.
* Loader takes care of filtering of extensions which are enabled or not.
*/
func
=
wine_vk_get_device_proc_addr
(
name
);
if
(
func
)
return
func
;
/* vkGetDeviceProcAddr was intended for loading device and subdevice functions.
* idTech 6 titles such as Doom and Wolfenstein II, however use it also for
* loading of instance functions. This is undefined behavior as the specification
* disallows using any of the returned function pointers outside of device /
* subdevice objects. The games don't actually use the function pointers and if they
* did, they would crash as VkInstance / VkPhysicalDevice parameters need unwrapping.
* Khronos clarified behavior in the Vulkan spec and expects drivers to get updated,
* however it would require both driver and game fixes.
* https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/issues/2323
* https://github.com/KhronosGroup/Vulkan-Docs/issues/655
*/
if
(
device
->
quirks
&
WINEVULKAN_QUIRK_GET_DEVICE_PROC_ADDR
&&
((
func
=
wine_vk_get_instance_proc_addr
(
name
))
||
(
func
=
wine_vk_get_phys_dev_proc_addr
(
name
))))
{
WARN
(
"Returning instance function %s.
\n
"
,
debugstr_a
(
name
));
return
func
;
}
WARN
(
"Unsupported device function: %s.
\n
"
,
debugstr_a
(
name
));
return
NULL
;
}
void
*
WINAPI
wine_vk_icdGetPhysicalDeviceProcAddr
(
VkInstance
instance
,
const
char
*
name
)
{
TRACE
(
"%p, %s
\n
"
,
instance
,
debugstr_a
(
name
));
return
wine_vk_get_phys_dev_proc_addr
(
name
);
}
void
*
WINAPI
wine_vk_icdGetInstanceProcAddr
(
VkInstance
instance
,
const
char
*
name
)
{
TRACE
(
"%p, %s
\n
"
,
instance
,
debugstr_a
(
name
));
/* Initial version of the Vulkan ICD spec required vkGetInstanceProcAddr to be
* exported. vk_icdGetInstanceProcAddr was added later to separate ICD calls from
* Vulkan API. One of them in our case should forward to the other, so just forward
* to the older vkGetInstanceProcAddr.
*/
return
wine_vkGetInstanceProcAddr
(
instance
,
name
);
}
VkResult
WINAPI
wine_vk_icdNegotiateLoaderICDInterfaceVersion
(
uint32_t
*
supported_version
)
{
uint32_t
req_version
;
TRACE
(
"%p
\n
"
,
supported_version
);
/* The spec is not clear how to handle this. Mesa drivers don't check, but it
* is probably best to not explode. VK_INCOMPLETE seems to be the closest value.
*/
if
(
!
supported_version
)
return
VK_INCOMPLETE
;
req_version
=
*
supported_version
;
*
supported_version
=
min
(
req_version
,
WINE_VULKAN_ICD_VERSION
);
TRACE
(
"Loader requested ICD version %u, returning %u
\n
"
,
req_version
,
*
supported_version
);
return
VK_SUCCESS
;
}
BOOL
WINAPI
DllMain
(
HINSTANCE
hinst
,
DWORD
reason
,
void
*
reserved
)
{
TRACE
(
"%p, %u, %p
\n
"
,
hinst
,
reason
,
reserved
);
...
...
dlls/winevulkan/make_vulkan
View file @
08c1b0a0
...
...
@@ -2422,7 +2422,7 @@ class VkGenerator(object):
# Generate prototypes for device and instance functions requiring a custom implementation.
f
.
write
(
"/* Functions for which we have custom implementations outside of the thunks. */
\n
"
)
for
vk_func
in
self
.
registry
.
funcs
.
values
():
if
not
vk_func
.
is_required
()
or
vk_func
.
is_global_func
()
:
if
not
vk_func
.
is_required
():
continue
if
vk_func
.
needs_thunk
()
and
not
vk_func
.
needs_private_thunk
():
continue
...
...
dlls/winevulkan/vulkan.c
View file @
08c1b0a0
...
...
@@ -37,11 +37,6 @@ WINE_DEFAULT_DEBUG_CHANNEL(vulkan);
DEFINE_DEVPROPKEY
(
DEVPROPKEY_GPU_LUID
,
0x60b193cb
,
0x5276
,
0x4d0f
,
0x96
,
0xfc
,
0xf1
,
0x73
,
0xab
,
0xad
,
0x3e
,
0xc6
,
2
);
DEFINE_DEVPROPKEY
(
WINE_DEVPROPKEY_GPU_VULKAN_UUID
,
0x233a9ef3
,
0xafc4
,
0x4abd
,
0xb5
,
0x64
,
0xc3
,
0x2f
,
0x21
,
0xf1
,
0x53
,
0x5c
,
2
);
/* For now default to 4 as it felt like a reasonable version feature wise to support.
* Version 5 adds more extensive version checks. Something to tackle later.
*/
#define WINE_VULKAN_ICD_VERSION 4
#define wine_vk_find_struct(s, t) wine_vk_find_struct_((void *)s, VK_STRUCTURE_TYPE_##t)
static
void
*
wine_vk_find_struct_
(
void
*
s
,
VkStructureType
t
)
{
...
...
@@ -71,8 +66,6 @@ static uint32_t wine_vk_count_struct_(void *s, VkStructureType t)
return
result
;
}
static
void
*
wine_vk_get_global_proc_addr
(
const
char
*
name
);
static
const
struct
vulkan_funcs
*
vk_funcs
;
static
VkResult
(
*
p_vkEnumerateInstanceVersion
)(
uint32_t
*
version
);
...
...
@@ -1068,14 +1061,6 @@ VkResult WINAPI wine_vkEnumerateDeviceLayerProperties(VkPhysicalDevice phys_dev,
return
VK_SUCCESS
;
}
VkResult
WINAPI
wine_vkEnumerateInstanceLayerProperties
(
uint32_t
*
count
,
VkLayerProperties
*
properties
)
{
TRACE
(
"%p, %p
\n
"
,
count
,
properties
);
*
count
=
0
;
return
VK_SUCCESS
;
}
VkResult
WINAPI
wine_vkEnumerateInstanceVersion
(
uint32_t
*
version
)
{
VkResult
res
;
...
...
@@ -1133,47 +1118,6 @@ void WINAPI wine_vkFreeCommandBuffers(VkDevice device, VkCommandPool pool_handle
wine_vk_free_command_buffers
(
device
,
pool
,
count
,
buffers
);
}
PFN_vkVoidFunction
WINAPI
wine_vkGetDeviceProcAddr
(
VkDevice
device
,
const
char
*
name
)
{
void
*
func
;
TRACE
(
"%p, %s
\n
"
,
device
,
debugstr_a
(
name
));
/* The spec leaves return value undefined for a NULL device, let's just return NULL. */
if
(
!
device
||
!
name
)
return
NULL
;
/* Per the spec, we are only supposed to return device functions as in functions
* for which the first parameter is vkDevice or a child of vkDevice like a
* vkCommandBuffer or vkQueue.
* Loader takes care of filtering of extensions which are enabled or not.
*/
func
=
wine_vk_get_device_proc_addr
(
name
);
if
(
func
)
return
func
;
/* vkGetDeviceProcAddr was intended for loading device and subdevice functions.
* idTech 6 titles such as Doom and Wolfenstein II, however use it also for
* loading of instance functions. This is undefined behavior as the specification
* disallows using any of the returned function pointers outside of device /
* subdevice objects. The games don't actually use the function pointers and if they
* did, they would crash as VkInstance / VkPhysicalDevice parameters need unwrapping.
* Khronos clarified behavior in the Vulkan spec and expects drivers to get updated,
* however it would require both driver and game fixes.
* https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/issues/2323
* https://github.com/KhronosGroup/Vulkan-Docs/issues/655
*/
if
(
device
->
quirks
&
WINEVULKAN_QUIRK_GET_DEVICE_PROC_ADDR
&&
((
func
=
wine_vk_get_instance_proc_addr
(
name
))
||
(
func
=
wine_vk_get_phys_dev_proc_addr
(
name
))))
{
WARN
(
"Returning instance function %s.
\n
"
,
debugstr_a
(
name
));
return
func
;
}
WARN
(
"Unsupported device function: %s.
\n
"
,
debugstr_a
(
name
));
return
NULL
;
}
void
WINAPI
wine_vkGetDeviceQueue
(
VkDevice
device
,
uint32_t
family_index
,
uint32_t
queue_index
,
VkQueue
*
queue
)
{
...
...
@@ -1201,81 +1145,6 @@ void WINAPI wine_vkGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2 *in
*
queue
=
matching_queue
;
}
PFN_vkVoidFunction
WINAPI
wine_vkGetInstanceProcAddr
(
VkInstance
instance
,
const
char
*
name
)
{
void
*
func
;
TRACE
(
"%p, %s
\n
"
,
instance
,
debugstr_a
(
name
));
if
(
!
name
)
return
NULL
;
/* vkGetInstanceProcAddr can load most Vulkan functions when an instance is passed in, however
* for a NULL instance it can only load global functions.
*/
func
=
wine_vk_get_global_proc_addr
(
name
);
if
(
func
)
{
return
func
;
}
if
(
!
instance
)
{
WARN
(
"Global function %s not found.
\n
"
,
debugstr_a
(
name
));
return
NULL
;
}
func
=
wine_vk_get_instance_proc_addr
(
name
);
if
(
func
)
return
func
;
func
=
wine_vk_get_phys_dev_proc_addr
(
name
);
if
(
func
)
return
func
;
/* vkGetInstanceProcAddr also loads any children of instance, so device functions as well. */
func
=
wine_vk_get_device_proc_addr
(
name
);
if
(
func
)
return
func
;
WARN
(
"Unsupported device or instance function: %s.
\n
"
,
debugstr_a
(
name
));
return
NULL
;
}
void
*
WINAPI
wine_vk_icdGetPhysicalDeviceProcAddr
(
VkInstance
instance
,
const
char
*
name
)
{
TRACE
(
"%p, %s
\n
"
,
instance
,
debugstr_a
(
name
));
return
wine_vk_get_phys_dev_proc_addr
(
name
);
}
void
*
WINAPI
wine_vk_icdGetInstanceProcAddr
(
VkInstance
instance
,
const
char
*
name
)
{
TRACE
(
"%p, %s
\n
"
,
instance
,
debugstr_a
(
name
));
/* Initial version of the Vulkan ICD spec required vkGetInstanceProcAddr to be
* exported. vk_icdGetInstanceProcAddr was added later to separate ICD calls from
* Vulkan API. One of them in our case should forward to the other, so just forward
* to the older vkGetInstanceProcAddr.
*/
return
wine_vkGetInstanceProcAddr
(
instance
,
name
);
}
VkResult
WINAPI
wine_vk_icdNegotiateLoaderICDInterfaceVersion
(
uint32_t
*
supported_version
)
{
uint32_t
req_version
;
TRACE
(
"%p
\n
"
,
supported_version
);
/* The spec is not clear how to handle this. Mesa drivers don't check, but it
* is probably best to not explode. VK_INCOMPLETE seems to be the closest value.
*/
if
(
!
supported_version
)
return
VK_INCOMPLETE
;
req_version
=
*
supported_version
;
*
supported_version
=
min
(
req_version
,
WINE_VULKAN_ICD_VERSION
);
TRACE
(
"Loader requested ICD version %u, returning %u
\n
"
,
req_version
,
*
supported_version
);
return
VK_SUCCESS
;
}
VkResult
WINAPI
wine_vkQueueSubmit
(
VkQueue
queue
,
uint32_t
count
,
const
VkSubmitInfo
*
submits
,
VkFence
fence
)
{
...
...
@@ -2104,31 +1973,6 @@ VkResult WINAPI wine_vkDebugMarkerSetObjectNameEXT(VkDevice device, const VkDebu
return
thunk_vkDebugMarkerSetObjectNameEXT
(
device
,
&
wine_name_info
);
}
static
const
struct
vulkan_func
vk_global_dispatch_table
[]
=
{
/* These functions must call wine_vk_init_once() before accessing vk_funcs. */
{
"vkCreateInstance"
,
&
wine_vkCreateInstance
},
{
"vkEnumerateInstanceExtensionProperties"
,
&
wine_vkEnumerateInstanceExtensionProperties
},
{
"vkEnumerateInstanceLayerProperties"
,
&
wine_vkEnumerateInstanceLayerProperties
},
{
"vkEnumerateInstanceVersion"
,
&
wine_vkEnumerateInstanceVersion
},
{
"vkGetInstanceProcAddr"
,
&
wine_vkGetInstanceProcAddr
},
};
static
void
*
wine_vk_get_global_proc_addr
(
const
char
*
name
)
{
unsigned
int
i
;
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
vk_global_dispatch_table
);
i
++
)
{
if
(
strcmp
(
name
,
vk_global_dispatch_table
[
i
].
name
)
==
0
)
{
TRACE
(
"Found name=%s in global table
\n
"
,
debugstr_a
(
name
));
return
vk_global_dispatch_table
[
i
].
func
;
}
}
return
NULL
;
}
/*
* Wrapper around driver vkGetInstanceProcAddr implementation.
* Allows winelib applications to access Vulkan functions with Wine
...
...
dlls/winevulkan/vulkan_thunks.h
View file @
08c1b0a0
...
...
@@ -21,6 +21,7 @@ VkResult WINAPI wine_vkCreateCommandPool(VkDevice device, const VkCommandPoolCre
VkResult
WINAPI
wine_vkCreateDebugReportCallbackEXT
(
VkInstance
instance
,
const
VkDebugReportCallbackCreateInfoEXT
*
pCreateInfo
,
const
VkAllocationCallbacks
*
pAllocator
,
VkDebugReportCallbackEXT
*
pCallback
)
DECLSPEC_HIDDEN
;
VkResult
WINAPI
wine_vkCreateDebugUtilsMessengerEXT
(
VkInstance
instance
,
const
VkDebugUtilsMessengerCreateInfoEXT
*
pCreateInfo
,
const
VkAllocationCallbacks
*
pAllocator
,
VkDebugUtilsMessengerEXT
*
pMessenger
)
DECLSPEC_HIDDEN
;
VkResult
WINAPI
wine_vkCreateDevice
(
VkPhysicalDevice
physicalDevice
,
const
VkDeviceCreateInfo
*
pCreateInfo
,
const
VkAllocationCallbacks
*
pAllocator
,
VkDevice
*
pDevice
);
VkResult
WINAPI
wine_vkCreateInstance
(
const
VkInstanceCreateInfo
*
pCreateInfo
,
const
VkAllocationCallbacks
*
pAllocator
,
VkInstance
*
pInstance
);
VkResult
WINAPI
wine_vkCreateSwapchainKHR
(
VkDevice
device
,
const
VkSwapchainCreateInfoKHR
*
pCreateInfo
,
const
VkAllocationCallbacks
*
pAllocator
,
VkSwapchainKHR
*
pSwapchain
);
VkResult
WINAPI
wine_vkCreateWin32SurfaceKHR
(
VkInstance
instance
,
const
VkWin32SurfaceCreateInfoKHR
*
pCreateInfo
,
const
VkAllocationCallbacks
*
pAllocator
,
VkSurfaceKHR
*
pSurface
);
VkResult
WINAPI
wine_vkDebugMarkerSetObjectNameEXT
(
VkDevice
device
,
const
VkDebugMarkerObjectNameInfoEXT
*
pNameInfo
)
DECLSPEC_HIDDEN
;
...
...
@@ -34,6 +35,8 @@ void WINAPI wine_vkDestroyInstance(VkInstance instance, const VkAllocationCallba
void
WINAPI
wine_vkDestroySurfaceKHR
(
VkInstance
instance
,
VkSurfaceKHR
surface
,
const
VkAllocationCallbacks
*
pAllocator
);
VkResult
WINAPI
wine_vkEnumerateDeviceExtensionProperties
(
VkPhysicalDevice
physicalDevice
,
const
char
*
pLayerName
,
uint32_t
*
pPropertyCount
,
VkExtensionProperties
*
pProperties
);
VkResult
WINAPI
wine_vkEnumerateDeviceLayerProperties
(
VkPhysicalDevice
physicalDevice
,
uint32_t
*
pPropertyCount
,
VkLayerProperties
*
pProperties
);
VkResult
WINAPI
wine_vkEnumerateInstanceExtensionProperties
(
const
char
*
pLayerName
,
uint32_t
*
pPropertyCount
,
VkExtensionProperties
*
pProperties
);
VkResult
WINAPI
wine_vkEnumerateInstanceVersion
(
uint32_t
*
pApiVersion
);
VkResult
WINAPI
wine_vkEnumeratePhysicalDeviceGroups
(
VkInstance
instance
,
uint32_t
*
pPhysicalDeviceGroupCount
,
VkPhysicalDeviceGroupProperties
*
pPhysicalDeviceGroupProperties
);
VkResult
WINAPI
wine_vkEnumeratePhysicalDeviceGroupsKHR
(
VkInstance
instance
,
uint32_t
*
pPhysicalDeviceGroupCount
,
VkPhysicalDeviceGroupProperties
*
pPhysicalDeviceGroupProperties
)
DECLSPEC_HIDDEN
;
VkResult
WINAPI
wine_vkEnumeratePhysicalDevices
(
VkInstance
instance
,
uint32_t
*
pPhysicalDeviceCount
,
VkPhysicalDevice
*
pPhysicalDevices
);
...
...
@@ -42,6 +45,7 @@ VkResult WINAPI wine_vkGetCalibratedTimestampsEXT(VkDevice device, uint32_t time
PFN_vkVoidFunction
WINAPI
wine_vkGetDeviceProcAddr
(
VkDevice
device
,
const
char
*
pName
);
void
WINAPI
wine_vkGetDeviceQueue
(
VkDevice
device
,
uint32_t
queueFamilyIndex
,
uint32_t
queueIndex
,
VkQueue
*
pQueue
);
void
WINAPI
wine_vkGetDeviceQueue2
(
VkDevice
device
,
const
VkDeviceQueueInfo2
*
pQueueInfo
,
VkQueue
*
pQueue
);
PFN_vkVoidFunction
WINAPI
wine_vkGetInstanceProcAddr
(
VkInstance
instance
,
const
char
*
pName
);
VkResult
WINAPI
wine_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT
(
VkPhysicalDevice
physicalDevice
,
uint32_t
*
pTimeDomainCount
,
VkTimeDomainEXT
*
pTimeDomains
)
DECLSPEC_HIDDEN
;
void
WINAPI
wine_vkGetPhysicalDeviceExternalBufferProperties
(
VkPhysicalDevice
physicalDevice
,
const
VkPhysicalDeviceExternalBufferInfo
*
pExternalBufferInfo
,
VkExternalBufferProperties
*
pExternalBufferProperties
);
void
WINAPI
wine_vkGetPhysicalDeviceExternalBufferPropertiesKHR
(
VkPhysicalDevice
physicalDevice
,
const
VkPhysicalDeviceExternalBufferInfo
*
pExternalBufferInfo
,
VkExternalBufferProperties
*
pExternalBufferProperties
)
DECLSPEC_HIDDEN
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment