Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
vkd3d
Manage
Activity
Members
Labels
Plan
Wiki
Bugzilla
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container Registry
Model registry
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
wine
vkd3d
Commits
007f894b
Commit
007f894b
authored
2 years ago
by
Conor McCarthy
Committed by
Alexandre Julliard
2 years ago
Browse files
Options
Downloads
Patches
Plain Diff
vkd3d-shader/sm1: Store parsed instructions in an array.
parent
6b82ba94
No related branches found
No related tags found
1 merge request
!60
vkd3d-shader: Store shader IR instructions in an array.
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
libs/vkd3d-shader/d3dbc.c
+76
-36
76 additions, 36 deletions
libs/vkd3d-shader/d3dbc.c
libs/vkd3d-shader/vkd3d_shader_main.c
+102
-0
102 additions, 0 deletions
libs/vkd3d-shader/vkd3d_shader_main.c
libs/vkd3d-shader/vkd3d_shader_private.h
+63
-0
63 additions, 0 deletions
libs/vkd3d-shader/vkd3d_shader_private.h
with
241 additions
and
36 deletions
libs/vkd3d-shader/d3dbc.c
+
76
−
36
View file @
007f894b
...
...
@@ -210,13 +210,6 @@ struct vkd3d_shader_sm1_parser
const
uint32_t
*
start
,
*
end
;
bool
abort
;
struct
vkd3d_shader_src_param
src_rel_addr
[
4
];
struct
vkd3d_shader_src_param
pred_rel_addr
;
struct
vkd3d_shader_src_param
dst_rel_addr
;
struct
vkd3d_shader_src_param
src_param
[
4
];
struct
vkd3d_shader_src_param
pred_param
;
struct
vkd3d_shader_dst_param
dst_param
;
struct
vkd3d_shader_parser
p
;
};
...
...
@@ -556,33 +549,50 @@ static void shader_sm1_destroy(struct vkd3d_shader_parser *parser)
{
struct
vkd3d_shader_sm1_parser
*
sm1
=
vkd3d_shader_sm1_parser
(
parser
);
shader_instruction_array_destroy
(
&
parser
->
instructions
);
free_shader_desc
(
&
sm1
->
p
.
shader_desc
);
vkd3d_free
(
sm1
);
}
static
void
shader_sm1_read_src_param
(
struct
vkd3d_shader_sm1_parser
*
sm1
,
const
uint32_t
**
ptr
,
struct
vkd3d_shader_src_param
*
src_param
,
struct
vkd3d_shader_src_param
*
src_rel_addr
)
struct
vkd3d_shader_src_param
*
src_param
)
{
struct
vkd3d_shader_src_param
*
src_rel_addr
=
NULL
;
uint32_t
token
,
addr_token
;
shader_sm1_read_param
(
sm1
,
ptr
,
&
token
,
&
addr_token
);
if
(
has_relative_address
(
token
))
{
if
(
!
(
src_rel_addr
=
shader_parser_get_src_params
(
&
sm1
->
p
,
1
)))
{
vkd3d_shader_parser_error
(
&
sm1
->
p
,
VKD3D_SHADER_ERROR_D3DBC_OUT_OF_MEMORY
,
"Out of memory."
);
sm1
->
abort
=
true
;
return
;
}
shader_sm1_parse_src_param
(
addr_token
,
NULL
,
src_rel_addr
);
else
src_rel_addr
=
NULL
;
}
shader_sm1_parse_src_param
(
token
,
src_rel_addr
,
src_param
);
}
static
void
shader_sm1_read_dst_param
(
struct
vkd3d_shader_sm1_parser
*
sm1
,
const
uint32_t
**
ptr
,
struct
vkd3d_shader_dst_param
*
dst_param
,
struct
vkd3d_shader_src_param
*
dst_rel_addr
)
struct
vkd3d_shader_dst_param
*
dst_param
)
{
struct
vkd3d_shader_src_param
*
dst_rel_addr
=
NULL
;
uint32_t
token
,
addr_token
;
shader_sm1_read_param
(
sm1
,
ptr
,
&
token
,
&
addr_token
);
if
(
has_relative_address
(
token
))
{
if
(
!
(
dst_rel_addr
=
shader_parser_get_src_params
(
&
sm1
->
p
,
1
)))
{
vkd3d_shader_parser_error
(
&
sm1
->
p
,
VKD3D_SHADER_ERROR_D3DBC_OUT_OF_MEMORY
,
"Out of memory."
);
sm1
->
abort
=
true
;
return
;
}
shader_sm1_parse_src_param
(
addr_token
,
NULL
,
dst_rel_addr
);
else
dst_rel_addr
=
NULL
;
}
shader_sm1_parse_dst_param
(
token
,
dst_rel_addr
,
dst_param
);
}
...
...
@@ -731,10 +741,13 @@ static void shader_sm1_validate_instruction(struct vkd3d_shader_sm1_parser *sm1,
static
void
shader_sm1_read_instruction
(
struct
vkd3d_shader_parser
*
parser
,
struct
vkd3d_shader_instruction
*
ins
)
{
struct
vkd3d_shader_sm1_parser
*
sm1
=
vkd3d_shader_sm1_parser
(
parser
);
struct
vkd3d_shader_src_param
*
src_params
,
*
predicate
;
const
struct
vkd3d_sm1_opcode_info
*
opcode_info
;
struct
vkd3d_shader_dst_param
*
dst_param
;
const
uint32_t
**
ptr
=
&
parser
->
ptr
;
uint32_t
opcode_token
;
const
uint32_t
*
p
;
bool
predicated
;
unsigned
int
i
;
shader_sm1_read_comment
(
sm1
);
...
...
@@ -761,11 +774,18 @@ static void shader_sm1_read_instruction(struct vkd3d_shader_parser *parser, stru
ins
->
coissue
=
opcode_token
&
VKD3D_SM1_COISSUE
;
ins
->
raw
=
false
;
ins
->
structured
=
false
;
ins
->
predicate
=
opcode_token
&
VKD3D_SM1_INSTRUCTION_PREDICATED
?
&
sm1
->
pred_param
:
NULL
;
predicated
=
!!
(
opcode_token
&
VKD3D_SM1_INSTRUCTION_PREDICATED
);
ins
->
predicate
=
predicate
=
predicated
?
shader_parser_get_src_params
(
parser
,
1
)
:
NULL
;
ins
->
dst_count
=
opcode_info
->
dst_count
;
ins
->
dst
=
&
sm1
->
dst_param
;
ins
->
dst
=
dst_param
=
shader_parser_get_dst_params
(
parser
,
ins
->
dst_count
)
;
ins
->
src_count
=
opcode_info
->
src_count
;
ins
->
src
=
sm1
->
src_param
;
ins
->
src
=
src_params
=
shader_parser_get_src_params
(
parser
,
ins
->
src_count
);
if
((
!
predicate
&&
predicated
)
||
(
!
src_params
&&
ins
->
src_count
)
||
(
!
dst_param
&&
ins
->
dst_count
))
{
vkd3d_shader_parser_error
(
parser
,
VKD3D_SHADER_ERROR_D3DBC_OUT_OF_MEMORY
,
"Out of memory."
);
goto
fail
;
}
ins
->
resource_type
=
VKD3D_SHADER_RESOURCE_NONE
;
ins
->
resource_stride
=
0
;
ins
->
resource_data_type
[
0
]
=
VKD3D_DATA_FLOAT
;
...
...
@@ -790,32 +810,32 @@ static void shader_sm1_read_instruction(struct vkd3d_shader_parser *parser, stru
}
else
if
(
ins
->
handler_idx
==
VKD3DSIH_DEF
)
{
shader_sm1_read_dst_param
(
sm1
,
&
p
,
&
sm1
->
dst_param
,
&
sm1
->
dst_rel_addr
);
shader_sm1_read_immconst
(
sm1
,
&
p
,
&
sm1
->
src_param
[
0
],
VKD3D_IMMCONST_VEC4
,
VKD3D_DATA_FLOAT
);
shader_sm1_read_dst_param
(
sm1
,
&
p
,
dst_param
);
shader_sm1_read_immconst
(
sm1
,
&
p
,
&
src_param
s
[
0
],
VKD3D_IMMCONST_VEC4
,
VKD3D_DATA_FLOAT
);
}
else
if
(
ins
->
handler_idx
==
VKD3DSIH_DEFB
)
{
shader_sm1_read_dst_param
(
sm1
,
&
p
,
&
sm1
->
dst_param
,
&
sm1
->
dst_rel_addr
);
shader_sm1_read_immconst
(
sm1
,
&
p
,
&
sm1
->
src_param
[
0
],
VKD3D_IMMCONST_SCALAR
,
VKD3D_DATA_UINT
);
shader_sm1_read_dst_param
(
sm1
,
&
p
,
dst_param
);
shader_sm1_read_immconst
(
sm1
,
&
p
,
&
src_param
s
[
0
],
VKD3D_IMMCONST_SCALAR
,
VKD3D_DATA_UINT
);
}
else
if
(
ins
->
handler_idx
==
VKD3DSIH_DEFI
)
{
shader_sm1_read_dst_param
(
sm1
,
&
p
,
&
sm1
->
dst_param
,
&
sm1
->
dst_rel_addr
);
shader_sm1_read_immconst
(
sm1
,
&
p
,
&
sm1
->
src_param
[
0
],
VKD3D_IMMCONST_VEC4
,
VKD3D_DATA_INT
);
shader_sm1_read_dst_param
(
sm1
,
&
p
,
dst_param
);
shader_sm1_read_immconst
(
sm1
,
&
p
,
&
src_param
s
[
0
],
VKD3D_IMMCONST_VEC4
,
VKD3D_DATA_INT
);
}
else
{
/* Destination token */
if
(
ins
->
dst_count
)
shader_sm1_read_dst_param
(
sm1
,
&
p
,
&
sm1
->
dst_param
,
&
sm1
->
dst_rel_addr
);
shader_sm1_read_dst_param
(
sm1
,
&
p
,
dst_param
);
/* Predication token */
if
(
ins
->
predicate
)
shader_sm1_read_src_param
(
sm1
,
&
p
,
&
sm1
->
pred_param
,
&
sm1
->
pred_rel_addr
);
shader_sm1_read_src_param
(
sm1
,
&
p
,
predicate
);
/* Other source tokens */
for
(
i
=
0
;
i
<
ins
->
src_count
;
++
i
)
shader_sm1_read_src_param
(
sm1
,
&
p
,
&
sm1
->
src_param
[
i
],
&
sm1
->
src_rel_addr
[
i
]);
shader_sm1_read_src_param
(
sm1
,
&
p
,
&
src_param
s
[
i
]);
}
if
(
sm1
->
abort
)
...
...
@@ -851,20 +871,12 @@ static bool shader_sm1_is_end(struct vkd3d_shader_parser *parser)
return
false
;
}
static
void
shader_sm1_reset
(
struct
vkd3d_shader_parser
*
parser
)
{
struct
vkd3d_shader_sm1_parser
*
sm1
=
vkd3d_shader_sm1_parser
(
parser
);
parser
->
ptr
=
sm1
->
start
;
parser
->
failed
=
false
;
}
const
struct
vkd3d_shader_parser_ops
shader_sm1_parser_ops
=
{
.
parser_reset
=
shader_
sm1
_reset
,
.
parser_reset
=
shader_
parser
_reset
,
.
parser_destroy
=
shader_sm1_destroy
,
.
parser_read_instruction
=
shader_
sm1
_read_instruction
,
.
parser_is_end
=
shader_
sm1
_is_end
,
.
parser_read_instruction
=
shader_
parser
_read_instruction
,
.
parser_is_end
=
shader_
parser
_is_end
,
};
static
enum
vkd3d_result
shader_sm1_init
(
struct
vkd3d_shader_sm1_parser
*
sm1
,
...
...
@@ -922,6 +934,10 @@ static enum vkd3d_result shader_sm1_init(struct vkd3d_shader_sm1_parser *sm1,
sm1
->
start
=
&
code
[
1
];
sm1
->
end
=
&
code
[
token_count
];
/* Estimate instruction count to avoid reallocation in most shaders. */
if
(
!
shader_instruction_array_init
(
&
sm1
->
p
.
instructions
,
code_size
!=
~
(
size_t
)
0
?
token_count
/
4u
+
4
:
16
))
return
VKD3D_ERROR_OUT_OF_MEMORY
;
vkd3d_shader_parser_init
(
&
sm1
->
p
,
message_context
,
compile_info
->
source_name
,
&
version
,
&
shader_sm1_parser_ops
);
shader_desc
=
&
sm1
->
p
.
shader_desc
;
shader_desc
->
byte_code
=
code
;
...
...
@@ -934,6 +950,8 @@ static enum vkd3d_result shader_sm1_init(struct vkd3d_shader_sm1_parser *sm1,
int
vkd3d_shader_sm1_parser_create
(
const
struct
vkd3d_shader_compile_info
*
compile_info
,
struct
vkd3d_shader_message_context
*
message_context
,
struct
vkd3d_shader_parser
**
parser
)
{
struct
vkd3d_shader_instruction_array
*
instructions
;
struct
vkd3d_shader_instruction
*
ins
;
struct
vkd3d_shader_sm1_parser
*
sm1
;
int
ret
;
...
...
@@ -950,6 +968,28 @@ int vkd3d_shader_sm1_parser_create(const struct vkd3d_shader_compile_info *compi
return
ret
;
}
instructions
=
&
sm1
->
p
.
instructions
;
while
(
!
shader_sm1_is_end
(
&
sm1
->
p
))
{
if
(
!
shader_instruction_array_reserve
(
instructions
,
instructions
->
count
+
1
))
{
ERR
(
"Failed to allocate instructions.
\n
"
);
vkd3d_shader_parser_error
(
&
sm1
->
p
,
VKD3D_SHADER_ERROR_D3DBC_OUT_OF_MEMORY
,
"Out of memory."
);
shader_sm1_destroy
(
&
sm1
->
p
);
return
VKD3D_ERROR_OUT_OF_MEMORY
;
}
ins
=
&
instructions
->
elements
[
instructions
->
count
];
shader_sm1_read_instruction
(
&
sm1
->
p
,
ins
);
if
(
ins
->
handler_idx
==
VKD3DSIH_INVALID
)
{
WARN
(
"Encountered unrecognized or invalid instruction.
\n
"
);
shader_sm1_destroy
(
&
sm1
->
p
);
return
VKD3D_ERROR_INVALID_SHADER
;
}
++
instructions
->
count
;
}
*
parser
=
&
sm1
->
p
;
return
VKD3D_OK
;
...
...
This diff is collapsed.
Click to expand it.
libs/vkd3d-shader/vkd3d_shader_main.c
+
102
−
0
View file @
007f894b
...
...
@@ -461,6 +461,22 @@ void VKD3D_PRINTF_FUNC(3, 4) vkd3d_shader_parser_warning(struct vkd3d_shader_par
va_end
(
args
);
}
void
shader_parser_reset
(
struct
vkd3d_shader_parser
*
parser
)
{
parser
->
instruction_idx
=
0
;
parser
->
failed
=
false
;
}
void
shader_parser_read_instruction
(
struct
vkd3d_shader_parser
*
parser
,
struct
vkd3d_shader_instruction
*
ins
)
{
*
ins
=
parser
->
instructions
.
elements
[
parser
->
instruction_idx
++
];
}
bool
shader_parser_is_end
(
struct
vkd3d_shader_parser
*
parser
)
{
return
parser
->
instruction_idx
>=
parser
->
instructions
.
count
;
}
static
int
vkd3d_shader_validate_compile_info
(
const
struct
vkd3d_shader_compile_info
*
compile_info
,
bool
validate_target_type
)
{
...
...
@@ -1583,3 +1599,89 @@ void vkd3d_shader_set_log_callback(PFN_vkd3d_log callback)
{
vkd3d_dbg_set_log_callback
(
callback
);
}
static
struct
vkd3d_shader_param_node
*
shader_param_allocator_node_create
(
struct
vkd3d_shader_param_allocator
*
allocator
)
{
struct
vkd3d_shader_param_node
*
node
;
if
(
!
(
node
=
vkd3d_malloc
(
offsetof
(
struct
vkd3d_shader_param_node
,
param
[
allocator
->
count
*
allocator
->
stride
]))))
return
NULL
;
node
->
next
=
NULL
;
return
node
;
}
static
void
shader_param_allocator_init
(
struct
vkd3d_shader_param_allocator
*
allocator
,
unsigned
int
count
,
unsigned
int
stride
)
{
allocator
->
count
=
max
(
count
,
4
);
allocator
->
stride
=
stride
;
allocator
->
head
=
NULL
;
allocator
->
current
=
NULL
;
allocator
->
index
=
allocator
->
count
;
}
static
void
shader_param_allocator_destroy
(
struct
vkd3d_shader_param_allocator
*
allocator
)
{
struct
vkd3d_shader_param_node
*
current
=
allocator
->
head
;
while
(
current
)
{
struct
vkd3d_shader_param_node
*
next
=
current
->
next
;
vkd3d_free
(
current
);
current
=
next
;
}
}
void
*
shader_param_allocator_get
(
struct
vkd3d_shader_param_allocator
*
allocator
,
unsigned
int
count
)
{
void
*
params
;
if
(
!
count
)
return
NULL
;
if
(
count
>
allocator
->
count
-
allocator
->
index
)
{
struct
vkd3d_shader_param_node
*
next
=
shader_param_allocator_node_create
(
allocator
);
if
(
!
next
)
return
NULL
;
if
(
allocator
->
current
)
allocator
->
current
->
next
=
next
;
allocator
->
current
=
next
;
allocator
->
index
=
0
;
}
params
=
&
allocator
->
current
->
param
[
allocator
->
index
*
allocator
->
stride
];
allocator
->
index
+=
count
;
return
params
;
}
bool
shader_instruction_array_init
(
struct
vkd3d_shader_instruction_array
*
instructions
,
unsigned
int
reserve
)
{
memset
(
instructions
,
0
,
sizeof
(
*
instructions
));
/* Size the parameter initial allocations so they are large enough for most shaders. The
* code path for chained allocations will be tested if a few shaders need to use it. */
shader_param_allocator_init
(
&
instructions
->
dst_params
,
reserve
-
reserve
/
8u
,
sizeof
(
*
instructions
->
elements
->
dst
));
shader_param_allocator_init
(
&
instructions
->
src_params
,
reserve
*
2u
,
sizeof
(
*
instructions
->
elements
->
src
));
return
shader_instruction_array_reserve
(
instructions
,
reserve
);
}
bool
shader_instruction_array_reserve
(
struct
vkd3d_shader_instruction_array
*
instructions
,
unsigned
int
reserve
)
{
if
(
!
vkd3d_array_reserve
((
void
**
)
&
instructions
->
elements
,
&
instructions
->
capacity
,
reserve
,
sizeof
(
*
instructions
->
elements
)))
{
ERR
(
"Failed to allocate instructions.
\n
"
);
return
false
;
}
return
true
;
}
void
shader_instruction_array_destroy
(
struct
vkd3d_shader_instruction_array
*
instructions
)
{
vkd3d_free
(
instructions
->
elements
);
shader_param_allocator_destroy
(
&
instructions
->
dst_params
);
shader_param_allocator_destroy
(
&
instructions
->
src_params
);
}
This diff is collapsed.
Click to expand it.
libs/vkd3d-shader/vkd3d_shader_private.h
+
63
−
0
View file @
007f894b
...
...
@@ -133,6 +133,7 @@ enum vkd3d_shader_error
VKD3D_SHADER_ERROR_D3DBC_INVALID_VERSION_TOKEN
=
7001
,
VKD3D_SHADER_ERROR_D3DBC_INVALID_OPCODE
=
7002
,
VKD3D_SHADER_ERROR_D3DBC_INVALID_RESOURCE_TYPE
=
7003
,
VKD3D_SHADER_ERROR_D3DBC_OUT_OF_MEMORY
=
7004
,
VKD3D_SHADER_WARNING_D3DBC_IGNORED_INSTRUCTION_FLAGS
=
7300
,
};
...
...
@@ -929,6 +930,51 @@ struct vkd3d_shader_location
unsigned
int
line
,
column
;
};
struct
vkd3d_shader_param_node
{
struct
vkd3d_shader_param_node
*
next
;
uint8_t
param
[];
};
struct
vkd3d_shader_param_allocator
{
struct
vkd3d_shader_param_node
*
head
;
struct
vkd3d_shader_param_node
*
current
;
unsigned
int
count
;
unsigned
int
stride
;
unsigned
int
index
;
};
void
*
shader_param_allocator_get
(
struct
vkd3d_shader_param_allocator
*
allocator
,
unsigned
int
count
);
static
inline
struct
vkd3d_shader_src_param
*
shader_src_param_allocator_get
(
struct
vkd3d_shader_param_allocator
*
allocator
,
unsigned
int
count
)
{
assert
(
allocator
->
stride
==
sizeof
(
struct
vkd3d_shader_src_param
));
return
shader_param_allocator_get
(
allocator
,
count
);
}
static
inline
struct
vkd3d_shader_dst_param
*
shader_dst_param_allocator_get
(
struct
vkd3d_shader_param_allocator
*
allocator
,
unsigned
int
count
)
{
assert
(
allocator
->
stride
==
sizeof
(
struct
vkd3d_shader_dst_param
));
return
shader_param_allocator_get
(
allocator
,
count
);
}
struct
vkd3d_shader_instruction_array
{
struct
vkd3d_shader_instruction
*
elements
;
size_t
capacity
;
size_t
count
;
struct
vkd3d_shader_param_allocator
src_params
;
struct
vkd3d_shader_param_allocator
dst_params
;
};
bool
shader_instruction_array_init
(
struct
vkd3d_shader_instruction_array
*
instructions
,
unsigned
int
reserve
);
bool
shader_instruction_array_reserve
(
struct
vkd3d_shader_instruction_array
*
instructions
,
unsigned
int
reserve
);
void
shader_instruction_array_destroy
(
struct
vkd3d_shader_instruction_array
*
instructions
);
struct
vkd3d_shader_parser
{
struct
vkd3d_shader_message_context
*
message_context
;
...
...
@@ -939,6 +985,8 @@ struct vkd3d_shader_parser
struct
vkd3d_shader_version
shader_version
;
const
uint32_t
*
ptr
;
const
struct
vkd3d_shader_parser_ops
*
ops
;
struct
vkd3d_shader_instruction_array
instructions
;
size_t
instruction_idx
;
};
struct
vkd3d_shader_parser_ops
...
...
@@ -956,6 +1004,21 @@ void vkd3d_shader_parser_init(struct vkd3d_shader_parser *parser,
const
struct
vkd3d_shader_version
*
version
,
const
struct
vkd3d_shader_parser_ops
*
ops
);
void
vkd3d_shader_parser_warning
(
struct
vkd3d_shader_parser
*
parser
,
enum
vkd3d_shader_error
error
,
const
char
*
format
,
...)
VKD3D_PRINTF_FUNC
(
3
,
4
);
void
shader_parser_reset
(
struct
vkd3d_shader_parser
*
parser
);
void
shader_parser_read_instruction
(
struct
vkd3d_shader_parser
*
parser
,
struct
vkd3d_shader_instruction
*
ins
);
bool
shader_parser_is_end
(
struct
vkd3d_shader_parser
*
parser
);
static
inline
struct
vkd3d_shader_dst_param
*
shader_parser_get_dst_params
(
struct
vkd3d_shader_parser
*
parser
,
unsigned
int
count
)
{
return
shader_dst_param_allocator_get
(
&
parser
->
instructions
.
dst_params
,
count
);
}
static
inline
struct
vkd3d_shader_src_param
*
shader_parser_get_src_params
(
struct
vkd3d_shader_parser
*
parser
,
unsigned
int
count
)
{
return
shader_src_param_allocator_get
(
&
parser
->
instructions
.
src_params
,
count
);
}
static
inline
void
vkd3d_shader_parser_destroy
(
struct
vkd3d_shader_parser
*
parser
)
{
...
...
This diff is collapsed.
Click to expand it.
Elizabeth Figura
@zfigura
mentioned in merge request
!83 (merged)
·
2 years ago
mentioned in merge request
!83 (merged)
mentioned in merge request !83
Toggle commit list
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment