sna: Experiment with cpu mappings for migration
Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
This commit is contained in:
parent
12c013d25e
commit
64cb923588
|
|
@ -802,9 +802,7 @@ void sna_glyphs__shared(CARD8 op,
|
|||
void sna_glyph_unrealize(ScreenPtr screen, GlyphPtr glyph);
|
||||
void sna_glyphs_close(struct sna *sna);
|
||||
|
||||
void sna_read_boxes(struct sna *sna,
|
||||
struct kgem_bo *src_bo, int16_t src_dx, int16_t src_dy,
|
||||
PixmapPtr dst, int16_t dst_dx, int16_t dst_dy,
|
||||
void sna_read_boxes(struct sna *sna, PixmapPtr dst, struct kgem_bo *src_bo,
|
||||
const BoxRec *box, int n);
|
||||
bool sna_write_boxes(struct sna *sna, PixmapPtr dst,
|
||||
struct kgem_bo *dst_bo, int16_t dst_dx, int16_t dst_dy,
|
||||
|
|
|
|||
|
|
@ -138,10 +138,7 @@ static void __sna_fallback_flush(DrawablePtr d)
|
|||
0);
|
||||
|
||||
DBG(("%s: comparing with direct read...\n", __FUNCTION__));
|
||||
sna_read_boxes(sna,
|
||||
priv->gpu_bo, 0, 0,
|
||||
tmp, 0, 0,
|
||||
&box, 1);
|
||||
sna_read_boxes(sna, tmp, priv->gpu_bo, &box, 1);
|
||||
|
||||
src = pixmap->devPrivate.ptr;
|
||||
dst = tmp->devPrivate.ptr;
|
||||
|
|
@ -2054,9 +2051,7 @@ skip_inplace_map:
|
|||
}
|
||||
if (!ok) {
|
||||
assert(has_coherent_ptr(sna_pixmap(pixmap)));
|
||||
sna_read_boxes(sna,
|
||||
priv->gpu_bo, 0, 0,
|
||||
pixmap, 0, 0,
|
||||
sna_read_boxes(sna, pixmap, priv->gpu_bo,
|
||||
box, n);
|
||||
}
|
||||
}
|
||||
|
|
@ -2491,9 +2486,7 @@ sna_drawable_move_region_to_cpu(DrawablePtr drawable,
|
|||
}
|
||||
if (!ok) {
|
||||
assert(has_coherent_ptr(sna_pixmap(pixmap)));
|
||||
sna_read_boxes(sna,
|
||||
priv->gpu_bo, 0, 0,
|
||||
pixmap, 0, 0,
|
||||
sna_read_boxes(sna, pixmap, priv->gpu_bo,
|
||||
box, n);
|
||||
}
|
||||
}
|
||||
|
|
@ -2515,9 +2508,7 @@ sna_drawable_move_region_to_cpu(DrawablePtr drawable,
|
|||
region->extents.y2 - region->extents.y1 == 1) {
|
||||
/* Often associated with synchronisation, KISS */
|
||||
DBG(("%s: single pixel read\n", __FUNCTION__));
|
||||
sna_read_boxes(sna,
|
||||
priv->gpu_bo, 0, 0,
|
||||
pixmap, 0, 0,
|
||||
sna_read_boxes(sna, pixmap, priv->gpu_bo,
|
||||
®ion->extents, 1);
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -2542,9 +2533,7 @@ sna_drawable_move_region_to_cpu(DrawablePtr drawable,
|
|||
if ((flags & MOVE_WRITE) == 0 &&
|
||||
region->extents.x2 - region->extents.x1 == 1 &&
|
||||
region->extents.y2 - region->extents.y1 == 1) {
|
||||
sna_read_boxes(sna,
|
||||
priv->gpu_bo, 0, 0,
|
||||
pixmap, 0, 0,
|
||||
sna_read_boxes(sna, pixmap, priv->gpu_bo,
|
||||
®ion->extents, 1);
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -2608,9 +2597,7 @@ sna_drawable_move_region_to_cpu(DrawablePtr drawable,
|
|||
|
||||
if (!ok) {
|
||||
assert(has_coherent_ptr(sna_pixmap(pixmap)));
|
||||
sna_read_boxes(sna,
|
||||
priv->gpu_bo, 0, 0,
|
||||
pixmap, 0, 0,
|
||||
sna_read_boxes(sna, pixmap, priv->gpu_bo,
|
||||
box, n);
|
||||
}
|
||||
}
|
||||
|
|
@ -2635,9 +2622,7 @@ sna_drawable_move_region_to_cpu(DrawablePtr drawable,
|
|||
}
|
||||
if (!ok) {
|
||||
assert(has_coherent_ptr(sna_pixmap(pixmap)));
|
||||
sna_read_boxes(sna,
|
||||
priv->gpu_bo, 0, 0,
|
||||
pixmap, 0, 0,
|
||||
sna_read_boxes(sna, pixmap, priv->gpu_bo,
|
||||
box, n);
|
||||
}
|
||||
|
||||
|
|
@ -2663,9 +2648,7 @@ sna_drawable_move_region_to_cpu(DrawablePtr drawable,
|
|||
}
|
||||
if (!ok) {
|
||||
assert(has_coherent_ptr(sna_pixmap(pixmap)));
|
||||
sna_read_boxes(sna,
|
||||
priv->gpu_bo, 0, 0,
|
||||
pixmap, 0, 0,
|
||||
sna_read_boxes(sna, pixmap, priv->gpu_bo,
|
||||
box, n);
|
||||
}
|
||||
|
||||
|
|
|
|||
196
src/sna/sna_io.c
196
src/sna/sna_io.c
|
|
@ -53,55 +53,91 @@ static inline bool must_tile(struct sna *sna, int width, int height)
|
|||
upload_too_large(sna, width, height));
|
||||
}
|
||||
|
||||
static bool bo_inplace_tiled(struct kgem *kgem, struct kgem_bo *bo, bool write)
|
||||
static bool download_inplace__cpu(struct kgem *kgem,
|
||||
PixmapPtr p, struct kgem_bo *bo,
|
||||
const BoxRec *box, int nbox)
|
||||
{
|
||||
if (bo->tiling != I915_TILING_X)
|
||||
BoxRec extents;
|
||||
|
||||
switch (bo->tiling) {
|
||||
case I915_TILING_X:
|
||||
if (!kgem->memcpy_from_tiled_x)
|
||||
return false;
|
||||
case I915_TILING_NONE:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!kgem_bo_can_map__cpu(kgem, bo, false))
|
||||
return false;
|
||||
|
||||
return kgem_bo_can_map__cpu(kgem, bo, write);
|
||||
}
|
||||
if (kgem->has_llc)
|
||||
return true;
|
||||
|
||||
static bool download_inplace__tiled(struct kgem *kgem, struct kgem_bo *bo)
|
||||
{
|
||||
if (!kgem->memcpy_from_tiled_x)
|
||||
return false;
|
||||
extents = *box;
|
||||
while (--nbox) {
|
||||
++box;
|
||||
if (box->x1 < extents.x1)
|
||||
extents.x1 = box->x1;
|
||||
if (box->x2 > extents.x2)
|
||||
extents.x2 = box->x2;
|
||||
extents.y2 = box->y2;
|
||||
}
|
||||
|
||||
return bo_inplace_tiled(kgem, bo, false);
|
||||
if (extents.x2 - extents.x1 == p->drawable.width &&
|
||||
extents.y2 - extents.y1 == p->drawable.height)
|
||||
return true;
|
||||
|
||||
return __kgem_bo_size(bo) <= PAGE_SIZE;
|
||||
}
|
||||
|
||||
static bool
|
||||
read_boxes_inplace__tiled(struct kgem *kgem,
|
||||
struct kgem_bo *bo, int16_t src_dx, int16_t src_dy,
|
||||
PixmapPtr pixmap, int16_t dst_dx, int16_t dst_dy,
|
||||
const BoxRec *box, int n)
|
||||
read_boxes_inplace__cpu(struct kgem *kgem,
|
||||
PixmapPtr pixmap, struct kgem_bo *bo,
|
||||
const BoxRec *box, int n)
|
||||
{
|
||||
int bpp = pixmap->drawable.bitsPerPixel;
|
||||
void *src, *dst = pixmap->devPrivate.ptr;
|
||||
int src_pitch = bo->pitch;
|
||||
int dst_pitch = pixmap->devKind;
|
||||
|
||||
assert(bo->tiling == I915_TILING_X);
|
||||
if (!download_inplace__cpu(kgem, dst, bo, box, n))
|
||||
return false;
|
||||
|
||||
assert(kgem_bo_can_map__cpu(kgem, bo, false));
|
||||
assert(bo->tiling != I915_TILING_Y);
|
||||
|
||||
src = __kgem_bo_map__cpu(kgem, bo);
|
||||
if (src == NULL)
|
||||
return false;
|
||||
|
||||
kgem_bo_sync__cpu_full(kgem, bo, 0);
|
||||
do {
|
||||
memcpy_from_tiled_x(kgem, src, dst, bpp, src_pitch, dst_pitch,
|
||||
box->x1 + src_dx, box->y1 + src_dy,
|
||||
box->x1 + dst_dx, box->y1 + dst_dy,
|
||||
box->x2 - box->x1, box->y2 - box->y1);
|
||||
box++;
|
||||
} while (--n);
|
||||
if (bo->tiling == I915_TILING_X) {
|
||||
assert(kgem->memcpy_from_tiled_x);
|
||||
do {
|
||||
memcpy_from_tiled_x(kgem, src, dst, bpp, src_pitch, dst_pitch,
|
||||
box->x1, box->y1,
|
||||
box->x1, box->y1,
|
||||
box->x2 - box->x1, box->y2 - box->y1);
|
||||
box++;
|
||||
} while (--n);
|
||||
} else {
|
||||
do {
|
||||
memcpy_blt(src, dst, bpp, src_pitch, dst_pitch,
|
||||
box->x1, box->y1,
|
||||
box->x1, box->y1,
|
||||
box->x2 - box->x1, box->y2 - box->y1);
|
||||
box++;
|
||||
} while (--n);
|
||||
}
|
||||
__kgem_bo_unmap__cpu(kgem, bo, src);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void read_boxes_inplace(struct kgem *kgem,
|
||||
struct kgem_bo *bo, int16_t src_dx, int16_t src_dy,
|
||||
PixmapPtr pixmap, int16_t dst_dx, int16_t dst_dy,
|
||||
PixmapPtr pixmap, struct kgem_bo *bo,
|
||||
const BoxRec *box, int n)
|
||||
{
|
||||
int bpp = pixmap->drawable.bitsPerPixel;
|
||||
|
|
@ -109,10 +145,7 @@ static void read_boxes_inplace(struct kgem *kgem,
|
|||
int src_pitch = bo->pitch;
|
||||
int dst_pitch = pixmap->devKind;
|
||||
|
||||
if (download_inplace__tiled(kgem, bo) &&
|
||||
read_boxes_inplace__tiled(kgem, bo, src_dx, src_dy,
|
||||
pixmap, dst_dx, dst_dy,
|
||||
box, n))
|
||||
if (read_boxes_inplace__cpu(kgem, pixmap, bo, box, n))
|
||||
return;
|
||||
|
||||
DBG(("%s x %d, tiling=%d\n", __FUNCTION__, n, bo->tiling));
|
||||
|
|
@ -134,31 +167,36 @@ static void read_boxes_inplace(struct kgem *kgem,
|
|||
assert(box->x2 > box->x1);
|
||||
assert(box->y2 > box->y1);
|
||||
|
||||
assert(box->x1 + src_dx >= 0);
|
||||
assert(box->y1 + src_dy >= 0);
|
||||
assert(box->x2 + src_dx <= pixmap->drawable.width);
|
||||
assert(box->y2 + src_dy <= pixmap->drawable.height);
|
||||
assert(box->x1 >= 0);
|
||||
assert(box->y1 >= 0);
|
||||
assert(box->x2 <= pixmap->drawable.width);
|
||||
assert(box->y2 <= pixmap->drawable.height);
|
||||
|
||||
assert(box->x1 + dst_dx >= 0);
|
||||
assert(box->y1 + dst_dy >= 0);
|
||||
assert(box->x2 + dst_dx <= pixmap->drawable.width);
|
||||
assert(box->y2 + dst_dy <= pixmap->drawable.height);
|
||||
assert(box->x1 >= 0);
|
||||
assert(box->y1 >= 0);
|
||||
assert(box->x2 <= pixmap->drawable.width);
|
||||
assert(box->y2 <= pixmap->drawable.height);
|
||||
|
||||
memcpy_blt(src, dst, bpp,
|
||||
src_pitch, dst_pitch,
|
||||
box->x1 + src_dx, box->y1 + src_dy,
|
||||
box->x1 + dst_dx, box->y1 + dst_dy,
|
||||
box->x1, box->y1,
|
||||
box->x1, box->y1,
|
||||
box->x2 - box->x1, box->y2 - box->y1);
|
||||
box++;
|
||||
} while (--n);
|
||||
}
|
||||
|
||||
static bool download_inplace(struct kgem *kgem, struct kgem_bo *bo)
|
||||
static bool download_inplace(struct kgem *kgem,
|
||||
PixmapPtr p, struct kgem_bo *bo,
|
||||
const BoxRec *box, int nbox)
|
||||
{
|
||||
bool cpu;
|
||||
|
||||
if (unlikely(kgem->wedged))
|
||||
return true;
|
||||
|
||||
if (!kgem_bo_can_map(kgem, bo) && !download_inplace__tiled(kgem, bo))
|
||||
cpu = download_inplace__cpu(kgem, p, bo, box, nbox);
|
||||
if (!cpu && !kgem_bo_can_map(kgem, bo))
|
||||
return false;
|
||||
|
||||
if (FORCE_INPLACE)
|
||||
|
|
@ -167,14 +205,10 @@ static bool download_inplace(struct kgem *kgem, struct kgem_bo *bo)
|
|||
if (kgem->can_blt_cpu && kgem->max_cpu_size)
|
||||
return false;
|
||||
|
||||
return !__kgem_bo_is_busy(kgem, bo) ||
|
||||
bo->tiling == I915_TILING_NONE ||
|
||||
download_inplace__tiled(kgem, bo);
|
||||
return !__kgem_bo_is_busy(kgem, bo) || cpu;
|
||||
}
|
||||
|
||||
void sna_read_boxes(struct sna *sna,
|
||||
struct kgem_bo *src_bo, int16_t src_dx, int16_t src_dy,
|
||||
PixmapPtr dst, int16_t dst_dx, int16_t dst_dy,
|
||||
void sna_read_boxes(struct sna *sna, PixmapPtr dst, struct kgem_bo *src_bo,
|
||||
const BoxRec *box, int nbox)
|
||||
{
|
||||
struct kgem *kgem = &sna->kgem;
|
||||
|
|
@ -188,20 +222,19 @@ void sna_read_boxes(struct sna *sna,
|
|||
int n, cmd, br13;
|
||||
bool can_blt;
|
||||
|
||||
DBG(("%s x %d, src=(handle=%d, offset=(%d,%d)), dst=(size=(%d, %d), offset=(%d,%d))\n",
|
||||
__FUNCTION__, nbox, src_bo->handle, src_dx, src_dy,
|
||||
dst->drawable.width, dst->drawable.height, dst_dx, dst_dy));
|
||||
DBG(("%s x %d, src=(handle=%d), dst=(size=(%d, %d)\n",
|
||||
__FUNCTION__, nbox, src_bo->handle, src_dy,
|
||||
dst->drawable.width, dst->drawable.height));
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (n = 0; n < nbox; n++) {
|
||||
if (box[n].x1 + src_dx < 0 || box[n].y1 + src_dy < 0 ||
|
||||
(box[n].x2 + src_dx) * dst->drawable.bitsPerPixel/8 > src_bo->pitch ||
|
||||
(box[n].y2 + src_dy) * src_bo->pitch > kgem_bo_size(src_bo))
|
||||
if (box[n].x1 < 0 || box[n].y1 < 0 ||
|
||||
box[n].x2 * dst->drawable.bitsPerPixel/8 > src_bo->pitch ||
|
||||
box[n].y2 * src_bo->pitch > kgem_bo_size(src_bo))
|
||||
{
|
||||
FatalError("source out-of-bounds box[%d]=(%d, %d), (%d, %d) + (%d, %d), pitch=%d, size=%d\n", n,
|
||||
FatalError("source out-of-bounds box[%d]=(%d, %d), (%d, %d), pitch=%d, size=%d\n", n,
|
||||
box[n].x1, box[n].y1,
|
||||
box[n].x2, box[n].y2,
|
||||
src_dx, src_dy,
|
||||
src_bo->pitch, kgem_bo_size(src_bo));
|
||||
}
|
||||
}
|
||||
|
|
@ -213,12 +246,9 @@ void sna_read_boxes(struct sna *sna,
|
|||
* this path.
|
||||
*/
|
||||
|
||||
if (download_inplace(kgem, src_bo)) {
|
||||
if (download_inplace(kgem, dst, src_bo, box ,nbox)) {
|
||||
fallback:
|
||||
read_boxes_inplace(kgem,
|
||||
src_bo, src_dx, src_dy,
|
||||
dst, dst_dx, dst_dy,
|
||||
box, nbox);
|
||||
read_boxes_inplace(kgem, dst, src_bo, box, nbox);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -297,11 +327,10 @@ fallback:
|
|||
if (!box_intersect(c, &tile))
|
||||
continue;
|
||||
|
||||
DBG(("%s: box(%d, %d), (%d, %d), src=(%d, %d), dst=(%d, %d)\n",
|
||||
DBG(("%s: box(%d, %d), (%d, %d),, dst=(%d, %d)\n",
|
||||
__FUNCTION__,
|
||||
c->x1, c->y1,
|
||||
c->x2, c->y2,
|
||||
src_dx, src_dy,
|
||||
c->x1 - tile.x1,
|
||||
c->y1 - tile.y1));
|
||||
c++;
|
||||
|
|
@ -322,7 +351,7 @@ fallback:
|
|||
}
|
||||
|
||||
if (!sna->render.copy_boxes(sna, GXcopy,
|
||||
dst, src_bo, src_dx, src_dy,
|
||||
dst, src_bo, 0, 0,
|
||||
&tmp, dst_bo, -tile.x1, -tile.y1,
|
||||
clipped, c-clipped, COPY_LAST)) {
|
||||
kgem_bo_destroy(&sna->kgem, dst_bo);
|
||||
|
|
@ -339,8 +368,7 @@ fallback:
|
|||
dst_bo->pitch, dst->devKind,
|
||||
c->x1 - tile.x1,
|
||||
c->y1 - tile.y1,
|
||||
c->x1 + dst_dx,
|
||||
c->y1 + dst_dy,
|
||||
c->x1, c->y1,
|
||||
c->x2 - c->x1,
|
||||
c->y2 - c->y1);
|
||||
}
|
||||
|
|
@ -362,7 +390,7 @@ fallback:
|
|||
goto fallback;
|
||||
|
||||
if (!sna->render.copy_boxes(sna, GXcopy,
|
||||
dst, src_bo, src_dx, src_dy,
|
||||
dst, src_bo, 0, 0,
|
||||
&tmp, dst_bo, -extents.x1, -extents.y1,
|
||||
box, nbox, COPY_LAST)) {
|
||||
kgem_bo_destroy(&sna->kgem, dst_bo);
|
||||
|
|
@ -377,8 +405,7 @@ fallback:
|
|||
dst_bo->pitch, dst->devKind,
|
||||
box[n].x1 - extents.x1,
|
||||
box[n].y1 - extents.y1,
|
||||
box[n].x1 + dst_dx,
|
||||
box[n].y1 + dst_dy,
|
||||
box[n].x1, box[n].y1,
|
||||
box[n].x2 - box[n].x1,
|
||||
box[n].y2 - box[n].y1);
|
||||
}
|
||||
|
|
@ -401,10 +428,7 @@ fallback:
|
|||
|
||||
dst_bo = kgem_create_buffer(kgem, offset, KGEM_BUFFER_LAST, &ptr);
|
||||
if (!dst_bo) {
|
||||
read_boxes_inplace(kgem,
|
||||
src_bo, src_dx, src_dy,
|
||||
dst, dst_dx, dst_dy,
|
||||
box, nbox);
|
||||
read_boxes_inplace(kgem, dst, src_bo, box, nbox);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -455,15 +479,13 @@ fallback:
|
|||
uint32_t *b = kgem->batch + kgem->nbatch;
|
||||
|
||||
DBG((" blt offset %x: (%d, %d) x (%d, %d), pitch=%d\n",
|
||||
offset,
|
||||
tmp_box[n].x1 + src_dx,
|
||||
tmp_box[n].y1 + src_dy,
|
||||
offset, tmp_box[n].x1, tmp_box[n].y1,
|
||||
width, height, pitch));
|
||||
|
||||
assert(tmp_box[n].x1 + src_dx >= 0);
|
||||
assert((tmp_box[n].x2 + src_dx) * dst->drawable.bitsPerPixel/8 <= src_bo->pitch);
|
||||
assert(tmp_box[n].y1 + src_dy >= 0);
|
||||
assert((tmp_box[n].y2 + src_dy) * src_bo->pitch <= kgem_bo_size(src_bo));
|
||||
assert(tmp_box[n].x1 >= 0);
|
||||
assert(tmp_box[n].x2 * dst->drawable.bitsPerPixel/8 <= src_bo->pitch);
|
||||
assert(tmp_box[n].y1 >= 0);
|
||||
assert(tmp_box[n].y2 * src_bo->pitch <= kgem_bo_size(src_bo));
|
||||
|
||||
b[0] = cmd;
|
||||
b[1] = br13 | pitch;
|
||||
|
|
@ -474,7 +496,7 @@ fallback:
|
|||
I915_GEM_DOMAIN_RENDER |
|
||||
KGEM_RELOC_FENCED,
|
||||
offset);
|
||||
b[5] = (tmp_box[n].y1 + src_dy) << 16 | (tmp_box[n].x1 + src_dx);
|
||||
b[5] = tmp_box[n].y1 << 16 | tmp_box[n].x1;
|
||||
b[6] = src_pitch;
|
||||
b[7] = kgem_add_reloc(kgem, kgem->nbatch + 7, src_bo,
|
||||
I915_GEM_DOMAIN_RENDER << 16 |
|
||||
|
|
@ -505,20 +527,19 @@ fallback:
|
|||
DBG((" copy offset %lx [%08x...%08x...%08x]: (%d, %d) x (%d, %d), src pitch=%d, dst pitch=%d, bpp=%d\n",
|
||||
(long)((char *)src - (char *)ptr),
|
||||
*(uint32_t*)src, *(uint32_t*)(src+pitch*height/2 + pitch/2 - 4), *(uint32_t*)(src+pitch*height - 4),
|
||||
box->x1 + dst_dx,
|
||||
box->y1 + dst_dy,
|
||||
box->x1, box->y1,
|
||||
width, height,
|
||||
pitch, dst->devKind, cpp*8));
|
||||
|
||||
assert(box->x1 + dst_dx >= 0);
|
||||
assert(box->x2 + dst_dx <= dst->drawable.width);
|
||||
assert(box->y1 + dst_dy >= 0);
|
||||
assert(box->y2 + dst_dy <= dst->drawable.height);
|
||||
assert(box->x1 >= 0);
|
||||
assert(box->x2 <= dst->drawable.width);
|
||||
assert(box->y1 >= 0);
|
||||
assert(box->y2 <= dst->drawable.height);
|
||||
|
||||
memcpy_blt(src, dst->devPrivate.ptr, cpp*8,
|
||||
pitch, dst->devKind,
|
||||
0, 0,
|
||||
box->x1 + dst_dx, box->y1 + dst_dy,
|
||||
box->x1, box->y1,
|
||||
width, height);
|
||||
box++;
|
||||
|
||||
|
|
@ -534,7 +555,10 @@ static bool upload_inplace__tiled(struct kgem *kgem, struct kgem_bo *bo)
|
|||
if (!kgem->memcpy_to_tiled_x)
|
||||
return false;
|
||||
|
||||
return bo_inplace_tiled(kgem, bo, true);
|
||||
if (bo->tiling != I915_TILING_X)
|
||||
return false;
|
||||
|
||||
return kgem_bo_can_map__cpu(kgem, bo, true);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
|
|
|||
Loading…
Reference in New Issue