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
a9cf391b
Commit
a9cf391b
authored
Mar 20, 2018
by
Henri Verbeet
Committed by
Alexandre Julliard
Mar 20, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
wined3d: Merge wined3d_surface_upload_data() into texture2d_upload_data().
Signed-off-by:
Henri Verbeet
<
hverbeet@codeweavers.com
>
Signed-off-by:
Alexandre Julliard
<
julliard@winehq.org
>
parent
17c7f8e3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
154 additions
and
170 deletions
+154
-170
surface.c
dlls/wined3d/surface.c
+8
-162
texture.c
dlls/wined3d/texture.c
+146
-3
wined3d_private.h
dlls/wined3d/wined3d_private.h
+0
-5
No files found.
dlls/wined3d/surface.c
View file @
a9cf391b
...
...
@@ -772,162 +772,11 @@ static void texture2d_download_data(struct wined3d_texture *texture, unsigned in
heap_free
(
temporary_mem
);
}
/* This call just uploads data, the caller is responsible for binding the
* correct texture. */
/* Context activation is done by the caller. */
void
wined3d_surface_upload_data
(
struct
wined3d_texture
*
texture
,
unsigned
int
sub_resource_idx
,
const
struct
wined3d_gl_info
*
gl_info
,
const
struct
wined3d_format
*
format
,
const
struct
wined3d_box
*
src_box
,
unsigned
int
src_pitch
,
unsigned
int
dst_x
,
unsigned
int
dst_y
,
BOOL
srgb
,
const
struct
wined3d_const_bo_address
*
data
)
{
unsigned
int
update_w
=
src_box
->
right
-
src_box
->
left
;
unsigned
int
update_h
=
src_box
->
bottom
-
src_box
->
top
;
unsigned
int
level
,
layer
;
GLenum
target
;
TRACE
(
"texure %p, sub_resource_idx %u, gl_info %p, format %s, src_box %s, "
"src_pitch %u, dst_x %u, dst_y %u, srgb %#x, data {%#x:%p}.
\n
"
,
texture
,
sub_resource_idx
,
gl_info
,
debug_d3dformat
(
format
->
id
),
debug_box
(
src_box
),
src_pitch
,
dst_x
,
dst_y
,
srgb
,
data
->
buffer_object
,
data
->
addr
);
if
(
texture
->
sub_resources
[
sub_resource_idx
].
map_count
)
{
WARN
(
"Uploading a texture that is currently mapped, setting WINED3D_TEXTURE_PIN_SYSMEM.
\n
"
);
texture
->
flags
|=
WINED3D_TEXTURE_PIN_SYSMEM
;
}
if
(
format
->
flags
[
WINED3D_GL_RES_TYPE_TEX_2D
]
&
WINED3DFMT_FLAG_HEIGHT_SCALE
)
{
update_h
*=
format
->
height_scale
.
numerator
;
update_h
/=
format
->
height_scale
.
denominator
;
}
if
(
data
->
buffer_object
)
{
GL_EXTCALL
(
glBindBuffer
(
GL_PIXEL_UNPACK_BUFFER
,
data
->
buffer_object
));
checkGLcall
(
"glBindBuffer"
);
}
target
=
wined3d_texture_get_sub_resource_target
(
texture
,
sub_resource_idx
);
level
=
sub_resource_idx
%
texture
->
level_count
;
layer
=
sub_resource_idx
/
texture
->
level_count
;
if
(
format
->
flags
[
WINED3D_GL_RES_TYPE_TEX_2D
]
&
WINED3DFMT_FLAG_COMPRESSED
)
{
unsigned
int
dst_row_pitch
,
dst_slice_pitch
;
const
BYTE
*
addr
=
data
->
addr
;
GLenum
internal
;
addr
+=
(
src_box
->
top
/
format
->
block_height
)
*
src_pitch
;
addr
+=
(
src_box
->
left
/
format
->
block_width
)
*
format
->
block_byte_count
;
if
(
srgb
)
internal
=
format
->
glGammaInternal
;
else
if
(
texture
->
resource
.
usage
&
WINED3DUSAGE_RENDERTARGET
&&
wined3d_resource_is_offscreen
(
&
texture
->
resource
))
internal
=
format
->
rtInternal
;
else
internal
=
format
->
glInternal
;
wined3d_format_calculate_pitch
(
format
,
1
,
update_w
,
update_h
,
&
dst_row_pitch
,
&
dst_slice_pitch
);
TRACE
(
"Uploading compressed data, target %#x, level %u, layer %u, "
"x %u, y %u, w %u, h %u, format %#x, image_size %#x, addr %p.
\n
"
,
target
,
level
,
layer
,
dst_x
,
dst_y
,
update_w
,
update_h
,
internal
,
dst_slice_pitch
,
addr
);
if
(
dst_row_pitch
==
src_pitch
)
{
if
(
target
==
GL_TEXTURE_2D_ARRAY
)
{
GL_EXTCALL
(
glCompressedTexSubImage3D
(
target
,
level
,
dst_x
,
dst_y
,
layer
,
update_w
,
update_h
,
1
,
internal
,
dst_slice_pitch
,
addr
));
}
else
{
GL_EXTCALL
(
glCompressedTexSubImage2D
(
target
,
level
,
dst_x
,
dst_y
,
update_w
,
update_h
,
internal
,
dst_slice_pitch
,
addr
));
}
}
else
{
UINT
row_count
=
(
update_h
+
format
->
block_height
-
1
)
/
format
->
block_height
;
UINT
row
,
y
;
/* glCompressedTexSubImage2D() ignores pixel store state, so we
* can't use the unpack row length like for glTexSubImage2D. */
for
(
row
=
0
,
y
=
dst_y
;
row
<
row_count
;
++
row
)
{
if
(
target
==
GL_TEXTURE_2D_ARRAY
)
{
GL_EXTCALL
(
glCompressedTexSubImage3D
(
target
,
level
,
dst_x
,
y
,
layer
,
update_w
,
format
->
block_height
,
1
,
internal
,
dst_row_pitch
,
addr
));
}
else
{
GL_EXTCALL
(
glCompressedTexSubImage2D
(
target
,
level
,
dst_x
,
y
,
update_w
,
format
->
block_height
,
internal
,
dst_row_pitch
,
addr
));
}
y
+=
format
->
block_height
;
addr
+=
src_pitch
;
}
}
checkGLcall
(
"Upload compressed texture data"
);
}
else
{
const
BYTE
*
addr
=
data
->
addr
;
addr
+=
src_box
->
top
*
src_pitch
;
addr
+=
src_box
->
left
*
format
->
byte_count
;
TRACE
(
"Uploading data, target %#x, level %u, layer %u, x %d, y %d, w %u, h %u, "
"format %#x, type %#x, addr %p.
\n
"
,
target
,
level
,
layer
,
dst_x
,
dst_y
,
update_w
,
update_h
,
format
->
glFormat
,
format
->
glType
,
addr
);
gl_info
->
gl_ops
.
gl
.
p_glPixelStorei
(
GL_UNPACK_ROW_LENGTH
,
src_pitch
/
format
->
byte_count
);
if
(
target
==
GL_TEXTURE_2D_ARRAY
)
{
GL_EXTCALL
(
glTexSubImage3D
(
target
,
level
,
dst_x
,
dst_y
,
layer
,
update_w
,
update_h
,
1
,
format
->
glFormat
,
format
->
glType
,
addr
));
}
else
{
gl_info
->
gl_ops
.
gl
.
p_glTexSubImage2D
(
target
,
level
,
dst_x
,
dst_y
,
update_w
,
update_h
,
format
->
glFormat
,
format
->
glType
,
addr
);
}
gl_info
->
gl_ops
.
gl
.
p_glPixelStorei
(
GL_UNPACK_ROW_LENGTH
,
0
);
checkGLcall
(
"Upload texture data"
);
}
if
(
data
->
buffer_object
)
{
GL_EXTCALL
(
glBindBuffer
(
GL_PIXEL_UNPACK_BUFFER
,
0
));
checkGLcall
(
"glBindBuffer"
);
}
if
(
wined3d_settings
.
strict_draw_ordering
)
gl_info
->
gl_ops
.
gl
.
p_glFlush
();
if
(
gl_info
->
quirks
&
WINED3D_QUIRK_FBO_TEX_UPDATE
)
{
struct
wined3d_device
*
device
=
texture
->
resource
.
device
;
unsigned
int
i
;
for
(
i
=
0
;
i
<
device
->
context_count
;
++
i
)
{
context_texture_update
(
device
->
contexts
[
i
],
texture
);
}
}
}
static
HRESULT
texture2d_upload_from_surface
(
struct
wined3d_texture
*
dst_texture
,
unsigned
int
dst_sub_resource_idx
,
unsigned
int
dst_x
,
unsigned
int
dst_y
,
struct
wined3d_texture
*
src_texture
,
unsigned
int
src_sub_resource_idx
,
const
struct
wined3d_box
*
src_box
)
{
unsigned
int
src_row_pitch
,
src_slice_pitch
;
const
struct
wined3d_gl_info
*
gl_info
;
unsigned
int
src_level
,
dst_level
;
struct
wined3d_context
*
context
;
struct
wined3d_bo_address
data
;
...
...
@@ -939,7 +788,6 @@ static HRESULT texture2d_upload_from_surface(struct wined3d_texture *dst_texture
src_texture
,
src_sub_resource_idx
,
debug_box
(
src_box
));
context
=
context_acquire
(
dst_texture
->
resource
.
device
,
NULL
,
0
);
gl_info
=
context
->
gl_info
;
/* Only load the sub-resource for partial updates. For newly allocated
* textures the texture wouldn't be the current location, and we'd upload
...
...
@@ -959,8 +807,8 @@ static HRESULT texture2d_upload_from_surface(struct wined3d_texture *dst_texture
src_texture
->
sub_resources
[
src_sub_resource_idx
].
locations
);
wined3d_texture_get_pitch
(
src_texture
,
src_level
,
&
src_row_pitch
,
&
src_slice_pitch
);
wined3d_
surface_upload_data
(
dst_texture
,
dst_sub_resource_idx
,
gl_info
,
src_texture
->
resource
.
format
,
src_box
,
src_row_pitch
,
dst_x
,
dst_y
,
FALSE
,
wined3d_const_bo_address
(
&
data
)
);
wined3d_
texture_upload_data
(
dst_texture
,
dst_sub_resource_idx
,
context
,
src_texture
->
resource
.
format
,
src_box
,
wined3d_const_bo_address
(
&
data
),
src_row_pitch
,
src_slice_pitch
,
dst_x
,
dst_y
,
0
,
FALSE
);
context_release
(
context
);
...
...
@@ -1239,7 +1087,6 @@ static struct wined3d_texture *surface_convert_format(struct wined3d_texture *sr
const
struct
wined3d_format
*
src_format
=
src_texture
->
resource
.
format
;
struct
wined3d_device
*
device
=
src_texture
->
resource
.
device
;
const
struct
d3dfmt_converter_desc
*
conv
=
NULL
;
const
struct
wined3d_gl_info
*
gl_info
=
NULL
;
unsigned
int
src_row_pitch
,
src_slice_pitch
;
struct
wined3d_context
*
context
=
NULL
;
struct
wined3d_texture
*
dst_texture
;
...
...
@@ -1277,10 +1124,7 @@ static struct wined3d_texture *surface_convert_format(struct wined3d_texture *sr
}
if
(
device
->
d3d_initialized
)
{
context
=
context_acquire
(
device
,
NULL
,
0
);
gl_info
=
context
->
gl_info
;
}
map_binding
=
src_texture
->
resource
.
map_binding
;
if
(
!
wined3d_texture_load_location
(
src_texture
,
sub_resource_idx
,
context
,
map_binding
))
...
...
@@ -1320,8 +1164,8 @@ static struct wined3d_texture *surface_convert_format(struct wined3d_texture *sr
wined3d_texture_prepare_texture
(
dst_texture
,
context
,
FALSE
);
wined3d_texture_bind_and_dirtify
(
dst_texture
,
context
,
FALSE
);
wined3d_
surface_upload_data
(
dst_texture
,
0
,
gl_info
,
src_format
,
&
src_box
,
src_row_pitch
,
0
,
0
,
FALSE
,
wined3d_const_bo_address
(
&
src_data
)
);
wined3d_
texture_upload_data
(
dst_texture
,
0
,
context
,
src_format
,
&
src_box
,
wined3d_const_bo_address
(
&
src_data
),
src_row_pitch
,
src_slice_pitch
,
0
,
0
,
0
,
FALSE
);
wined3d_texture_validate_location
(
dst_texture
,
0
,
WINED3D_LOCATION_TEXTURE_RGB
);
wined3d_texture_invalidate_location
(
dst_texture
,
0
,
~
WINED3D_LOCATION_TEXTURE_RGB
);
...
...
@@ -2269,6 +2113,7 @@ BOOL texture2d_load_texture(struct wined3d_texture *texture, unsigned int sub_re
format
.
upload
(
src_mem
,
dst_mem
,
src_row_pitch
,
src_slice_pitch
,
dst_row_pitch
,
dst_slice_pitch
,
width
,
height
,
1
);
src_row_pitch
=
dst_row_pitch
;
src_slice_pitch
=
dst_slice_pitch
;
context_unmap_bo_address
(
context
,
&
data
,
GL_PIXEL_UNPACK_BUFFER
);
data
.
buffer_object
=
0
;
...
...
@@ -2295,14 +2140,15 @@ BOOL texture2d_load_texture(struct wined3d_texture *texture, unsigned int sub_re
conversion
->
convert
(
src_mem
,
src_row_pitch
,
dst_mem
,
dst_row_pitch
,
width
,
height
,
palette
,
&
texture
->
async
.
gl_color_key
);
src_row_pitch
=
dst_row_pitch
;
src_slice_pitch
=
dst_slice_pitch
;
context_unmap_bo_address
(
context
,
&
data
,
GL_PIXEL_UNPACK_BUFFER
);
data
.
buffer_object
=
0
;
data
.
addr
=
dst_mem
;
}
wined3d_
surface_upload_data
(
texture
,
sub_resource_idx
,
gl_info
,
&
format
,
&
src_box
,
src_row_pitch
,
0
,
0
,
srgb
,
wined3d_const_bo_address
(
&
data
)
);
wined3d_
texture_upload_data
(
texture
,
sub_resource_idx
,
context
,
&
format
,
&
src_box
,
wined3d_const_bo_address
(
&
data
),
src_row_pitch
,
src_slice_pitch
,
0
,
0
,
0
,
srgb
);
heap_free
(
dst_mem
);
...
...
dlls/wined3d/texture.c
View file @
a9cf391b
...
...
@@ -1753,13 +1753,156 @@ void wined3d_texture_upload_data(struct wined3d_texture *texture, unsigned int s
format
,
src_box
,
data
,
row_pitch
,
slice_pitch
,
dst_x
,
dst_y
,
dst_z
,
srgb
);
}
/* This call just uploads data, the caller is responsible for binding the
* correct texture. */
/* Context activation is done by the caller. */
static
void
texture2d_upload_data
(
struct
wined3d_texture
*
texture
,
unsigned
int
sub_resource_idx
,
const
struct
wined3d_context
*
context
,
const
struct
wined3d_format
*
format
,
const
struct
wined3d_box
*
src_box
,
const
struct
wined3d_const_bo_address
*
data
,
unsigned
int
row_pitch
,
unsigned
int
slice_pitch
,
const
struct
wined3d_const_bo_address
*
data
,
unsigned
int
src_row_pitch
,
unsigned
int
src_
slice_pitch
,
unsigned
int
dst_x
,
unsigned
int
dst_y
,
unsigned
int
dst_z
,
BOOL
srgb
)
{
wined3d_surface_upload_data
(
texture
,
sub_resource_idx
,
context
->
gl_info
,
format
,
src_box
,
row_pitch
,
dst_x
,
dst_y
,
srgb
,
data
);
const
struct
wined3d_gl_info
*
gl_info
=
context
->
gl_info
;
unsigned
int
update_w
=
src_box
->
right
-
src_box
->
left
;
unsigned
int
update_h
=
src_box
->
bottom
-
src_box
->
top
;
unsigned
int
level
,
layer
;
GLenum
target
;
TRACE
(
"texture %p, sub_resource_idx %u, context %p, format %s, src_box %s, data {%#x:%p}, "
"src_row_pitch %#x, src_slice_pitch %#x, dst_x %u, dst_y %u, dst_z %u, srgb %#x.
\n
"
,
texture
,
sub_resource_idx
,
context
,
debug_d3dformat
(
format
->
id
),
debug_box
(
src_box
),
data
->
buffer_object
,
data
->
addr
,
src_row_pitch
,
src_slice_pitch
,
dst_x
,
dst_y
,
dst_z
,
srgb
);
if
(
texture
->
sub_resources
[
sub_resource_idx
].
map_count
)
{
WARN
(
"Uploading a texture that is currently mapped, setting WINED3D_TEXTURE_PIN_SYSMEM.
\n
"
);
texture
->
flags
|=
WINED3D_TEXTURE_PIN_SYSMEM
;
}
if
(
format
->
flags
[
WINED3D_GL_RES_TYPE_TEX_2D
]
&
WINED3DFMT_FLAG_HEIGHT_SCALE
)
{
update_h
*=
format
->
height_scale
.
numerator
;
update_h
/=
format
->
height_scale
.
denominator
;
}
if
(
data
->
buffer_object
)
{
GL_EXTCALL
(
glBindBuffer
(
GL_PIXEL_UNPACK_BUFFER
,
data
->
buffer_object
));
checkGLcall
(
"glBindBuffer"
);
}
target
=
wined3d_texture_get_sub_resource_target
(
texture
,
sub_resource_idx
);
level
=
sub_resource_idx
%
texture
->
level_count
;
layer
=
sub_resource_idx
/
texture
->
level_count
;
if
(
format
->
flags
[
WINED3D_GL_RES_TYPE_TEX_2D
]
&
WINED3DFMT_FLAG_COMPRESSED
)
{
unsigned
int
dst_row_pitch
,
dst_slice_pitch
;
const
BYTE
*
addr
=
data
->
addr
;
GLenum
internal
;
addr
+=
(
src_box
->
top
/
format
->
block_height
)
*
src_row_pitch
;
addr
+=
(
src_box
->
left
/
format
->
block_width
)
*
format
->
block_byte_count
;
if
(
srgb
)
internal
=
format
->
glGammaInternal
;
else
if
(
texture
->
resource
.
usage
&
WINED3DUSAGE_RENDERTARGET
&&
wined3d_resource_is_offscreen
(
&
texture
->
resource
))
internal
=
format
->
rtInternal
;
else
internal
=
format
->
glInternal
;
wined3d_format_calculate_pitch
(
format
,
1
,
update_w
,
update_h
,
&
dst_row_pitch
,
&
dst_slice_pitch
);
TRACE
(
"Uploading compressed data, target %#x, level %u, layer %u, x %d, y %d, w %u, h %u, "
"format %#x, image_size %#x, addr %p.
\n
"
,
target
,
level
,
layer
,
dst_x
,
dst_y
,
update_w
,
update_h
,
internal
,
dst_slice_pitch
,
addr
);
if
(
dst_row_pitch
==
src_row_pitch
)
{
if
(
target
==
GL_TEXTURE_2D_ARRAY
)
{
GL_EXTCALL
(
glCompressedTexSubImage3D
(
target
,
level
,
dst_x
,
dst_y
,
layer
,
update_w
,
update_h
,
1
,
internal
,
dst_slice_pitch
,
addr
));
}
else
{
GL_EXTCALL
(
glCompressedTexSubImage2D
(
target
,
level
,
dst_x
,
dst_y
,
update_w
,
update_h
,
internal
,
dst_slice_pitch
,
addr
));
}
}
else
{
UINT
row_count
=
(
update_h
+
format
->
block_height
-
1
)
/
format
->
block_height
;
UINT
row
,
y
;
/* glCompressedTexSubImage2D() ignores pixel store state, so we
* can't use the unpack row length like for glTexSubImage2D. */
for
(
row
=
0
,
y
=
dst_y
;
row
<
row_count
;
++
row
)
{
if
(
target
==
GL_TEXTURE_2D_ARRAY
)
{
GL_EXTCALL
(
glCompressedTexSubImage3D
(
target
,
level
,
dst_x
,
y
,
layer
,
update_w
,
format
->
block_height
,
1
,
internal
,
dst_row_pitch
,
addr
));
}
else
{
GL_EXTCALL
(
glCompressedTexSubImage2D
(
target
,
level
,
dst_x
,
y
,
update_w
,
format
->
block_height
,
internal
,
dst_row_pitch
,
addr
));
}
y
+=
format
->
block_height
;
addr
+=
src_row_pitch
;
}
}
checkGLcall
(
"Upload compressed texture data"
);
}
else
{
const
BYTE
*
addr
=
data
->
addr
;
addr
+=
src_box
->
top
*
src_row_pitch
;
addr
+=
src_box
->
left
*
format
->
byte_count
;
TRACE
(
"Uploading data, target %#x, level %u, layer %u, x %d, y %d, w %u, h %u, "
"format %#x, type %#x, addr %p.
\n
"
,
target
,
level
,
layer
,
dst_x
,
dst_y
,
update_w
,
update_h
,
format
->
glFormat
,
format
->
glType
,
addr
);
gl_info
->
gl_ops
.
gl
.
p_glPixelStorei
(
GL_UNPACK_ROW_LENGTH
,
src_row_pitch
/
format
->
byte_count
);
if
(
target
==
GL_TEXTURE_2D_ARRAY
)
{
GL_EXTCALL
(
glTexSubImage3D
(
target
,
level
,
dst_x
,
dst_y
,
layer
,
update_w
,
update_h
,
1
,
format
->
glFormat
,
format
->
glType
,
addr
));
}
else
{
gl_info
->
gl_ops
.
gl
.
p_glTexSubImage2D
(
target
,
level
,
dst_x
,
dst_y
,
update_w
,
update_h
,
format
->
glFormat
,
format
->
glType
,
addr
);
}
gl_info
->
gl_ops
.
gl
.
p_glPixelStorei
(
GL_UNPACK_ROW_LENGTH
,
0
);
checkGLcall
(
"Upload texture data"
);
}
if
(
data
->
buffer_object
)
{
GL_EXTCALL
(
glBindBuffer
(
GL_PIXEL_UNPACK_BUFFER
,
0
));
checkGLcall
(
"glBindBuffer"
);
}
if
(
wined3d_settings
.
strict_draw_ordering
)
gl_info
->
gl_ops
.
gl
.
p_glFlush
();
if
(
gl_info
->
quirks
&
WINED3D_QUIRK_FBO_TEX_UPDATE
)
{
struct
wined3d_device
*
device
=
texture
->
resource
.
device
;
unsigned
int
i
;
for
(
i
=
0
;
i
<
device
->
context_count
;
++
i
)
{
context_texture_update
(
device
->
contexts
[
i
],
texture
);
}
}
}
/* Context activation is done by the caller. Context may be NULL in ddraw-only mode. */
...
...
dlls/wined3d/wined3d_private.h
View file @
a9cf391b
...
...
@@ -3335,11 +3335,6 @@ struct fbo_entry
}
key
;
};
void
wined3d_surface_upload_data
(
struct
wined3d_texture
*
texture
,
unsigned
int
sub_resource_idx
,
const
struct
wined3d_gl_info
*
gl_info
,
const
struct
wined3d_format
*
format
,
const
struct
wined3d_box
*
src_box
,
unsigned
int
src_pitch
,
unsigned
int
dst_x
,
unsigned
int
dst_y
,
BOOL
srgb
,
const
struct
wined3d_const_bo_address
*
data
)
DECLSPEC_HIDDEN
;
void
draw_textured_quad
(
struct
wined3d_texture
*
texture
,
unsigned
int
sub_resource_idx
,
struct
wined3d_context
*
context
,
const
RECT
*
src_rect
,
const
RECT
*
dst_rect
,
enum
wined3d_texture_filter_type
filter
)
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