Commit 2736cee5 authored by Ell's avatar Ell

app: remove gimp_parallel_distribute(); use gegl_parallel_distribute()

The parallel_distribute() family of functions has been migrated to
GEGL.  Remove the gimp_parallel_distribute() functions from
gimp-parallel, and replace all uses of these functions with the
corresponding gegl_parallel_distrubte() functions.
parent 43e3939d
......@@ -47,7 +47,6 @@ extern "C"
#define GIMP_PARALLEL_MAX_THREADS 64
#define GIMP_PARALLEL_RUN_ASYNC_MAX_THREADS 1
#define GIMP_PARALLEL_DISTRIBUTE_MAX_THREADS GIMP_PARALLEL_MAX_THREADS
typedef struct
......@@ -68,25 +67,6 @@ typedef struct
GimpAsync *current_async;
} GimpParallelRunAsyncThread;
typedef struct
{
GimpParallelDistributeFunc func;
gint n;
gpointer user_data;
} GimpParallelDistributeTask;
typedef struct
{
GThread *thread;
GMutex mutex;
GCond cond;
gboolean quit;
GimpParallelDistributeTask *volatile task;
volatile gint i;
} GimpParallelDistributeThread;
/* local function prototypes */
......@@ -104,9 +84,6 @@ static gboolean gimp_parallel_run_async_execute_task (GimpPa
static void gimp_parallel_run_async_abort_task (GimpParallelRunAsyncTask *task);
static void gimp_parallel_run_async_cancel (GimpAsync *async);
static void gimp_parallel_distribute_set_n_threads (gint n_threads);
static gpointer gimp_parallel_distribute_thread_func (GimpParallelDistributeThread *thread);
/* local variables */
......@@ -117,14 +94,6 @@ static GMutex gimp_parallel_run_async_mutex;
static GCond gimp_parallel_run_async_cond;
static GQueue gimp_parallel_run_async_queue = G_QUEUE_INIT;
static gint gimp_parallel_distribute_n_threads = 1;
static GimpParallelDistributeThread gimp_parallel_distribute_threads[GIMP_PARALLEL_DISTRIBUTE_MAX_THREADS - 1];
static GMutex gimp_parallel_distribute_completion_mutex;
static GCond gimp_parallel_distribute_completion_cond;
static volatile gint gimp_parallel_distribute_completion_counter;
static volatile gint gimp_parallel_distribute_busy;
/* public functions */
......@@ -257,154 +226,6 @@ gimp_parallel_run_async_independent (GimpParallelRunAsyncFunc func,
return async;
}
void
gimp_parallel_distribute (gint max_n,
GimpParallelDistributeFunc func,
gpointer user_data)
{
GimpParallelDistributeTask task;
gint i;
g_return_if_fail (func != NULL);
if (max_n == 0)
return;
if (max_n < 0)
max_n = gimp_parallel_distribute_n_threads;
else
max_n = MIN (max_n, gimp_parallel_distribute_n_threads);
if (max_n == 1 ||
! g_atomic_int_compare_and_exchange (&gimp_parallel_distribute_busy,
0, 1))
{
func (0, 1, user_data);
return;
}
task.n = max_n;
task.func = func;
task.user_data = user_data;
g_atomic_int_set (&gimp_parallel_distribute_completion_counter, task.n - 1);
for (i = 0; i < task.n - 1; i++)
{
GimpParallelDistributeThread *thread =
&gimp_parallel_distribute_threads[i];
g_mutex_lock (&thread->mutex);
thread->task = &task;
thread->i = i;
g_cond_signal (&thread->cond);
g_mutex_unlock (&thread->mutex);
}
func (i, task.n, user_data);
if (g_atomic_int_get (&gimp_parallel_distribute_completion_counter))
{
g_mutex_lock (&gimp_parallel_distribute_completion_mutex);
while (g_atomic_int_get (&gimp_parallel_distribute_completion_counter))
{
g_cond_wait (&gimp_parallel_distribute_completion_cond,
&gimp_parallel_distribute_completion_mutex);
}
g_mutex_unlock (&gimp_parallel_distribute_completion_mutex);
}
g_atomic_int_set (&gimp_parallel_distribute_busy, 0);
}
void
gimp_parallel_distribute_range (gsize size,
gsize min_sub_size,
GimpParallelDistributeRangeFunc func,
gpointer user_data)
{
gsize n = size;
g_return_if_fail (func != NULL);
if (size == 0)
return;
if (min_sub_size > 1)
n /= min_sub_size;
n = CLAMP (n, 1, gimp_parallel_distribute_n_threads);
gimp_parallel_distribute (n, [=] (gint i, gint n)
{
gsize offset;
gsize sub_size;
offset = (2 * i * size + n) / (2 * n);
sub_size = (2 * (i + 1) * size + n) / (2 * n) - offset;
func (offset, sub_size, user_data);
});
}
void
gimp_parallel_distribute_area (const GeglRectangle *area,
gsize min_sub_area,
GimpParallelDistributeAreaFunc func,
gpointer user_data)
{
gsize n;
g_return_if_fail (area != NULL);
g_return_if_fail (func != NULL);
if (area->width <= 0 || area->height <= 0)
return;
n = (gsize) area->width * (gsize) area->height;
if (min_sub_area > 1)
n /= min_sub_area;
n = CLAMP (n, 1, gimp_parallel_distribute_n_threads);
gimp_parallel_distribute (n, [=] (gint i, gint n)
{
GeglRectangle sub_area;
if (area->width <= area->height)
{
sub_area.x = area->x;
sub_area.width = area->width;
sub_area.y = (2 * i * area->height + n) / (2 * n);
sub_area.height = (2 * (i + 1) * area->height + n) / (2 * n);
sub_area.height -= sub_area.y;
sub_area.y += area->y;
}
else
{
sub_area.y = area->y;
sub_area.height = area->height;
sub_area.x = (2 * i * area->width + n) / (2 * n);
sub_area.width = (2 * (i + 1) * area->width + n) / (2 * n);
sub_area.width -= sub_area.x;
sub_area.x += area->x;
}
func (&sub_area, user_data);
});
}
/* private functions */
......@@ -421,7 +242,6 @@ gimp_parallel_set_n_threads (gint n_threads,
gboolean finish_tasks)
{
gimp_parallel_run_async_set_n_threads (n_threads, finish_tasks);
gimp_parallel_distribute_set_n_threads (n_threads);
}
static void
......@@ -678,96 +498,4 @@ gimp_parallel_run_async_cancel (GimpAsync *async)
gimp_parallel_run_async_abort_task (task);
}
static void
gimp_parallel_distribute_set_n_threads (gint n_threads)
{
gint i;
while (! g_atomic_int_compare_and_exchange (&gimp_parallel_distribute_busy,
0, 1));
n_threads = CLAMP (n_threads, 1, GIMP_PARALLEL_DISTRIBUTE_MAX_THREADS);
if (n_threads > gimp_parallel_distribute_n_threads) /* need more threads */
{
for (i = gimp_parallel_distribute_n_threads - 1; i < n_threads - 1; i++)
{
GimpParallelDistributeThread *thread =
&gimp_parallel_distribute_threads[i];
thread->quit = FALSE;
thread->task = NULL;
thread->thread = g_thread_new (
"worker",
(GThreadFunc) gimp_parallel_distribute_thread_func,
thread);
}
}
else if (n_threads < gimp_parallel_distribute_n_threads) /* need less threads */
{
for (i = n_threads - 1; i < gimp_parallel_distribute_n_threads - 1; i++)
{
GimpParallelDistributeThread *thread =
&gimp_parallel_distribute_threads[i];
g_mutex_lock (&thread->mutex);
thread->quit = TRUE;
g_cond_signal (&thread->cond);
g_mutex_unlock (&thread->mutex);
}
for (i = n_threads - 1; i < gimp_parallel_distribute_n_threads - 1; i++)
{
GimpParallelDistributeThread *thread =
&gimp_parallel_distribute_threads[i];
g_thread_join (thread->thread);
}
}
gimp_parallel_distribute_n_threads = n_threads;
g_atomic_int_set (&gimp_parallel_distribute_busy, 0);
}
static gpointer
gimp_parallel_distribute_thread_func (GimpParallelDistributeThread *thread)
{
g_mutex_lock (&thread->mutex);
while (TRUE)
{
if (thread->quit)
{
break;
}
else if (thread->task)
{
thread->task->func (thread->i, thread->task->n,
thread->task->user_data);
if (g_atomic_int_dec_and_test (
&gimp_parallel_distribute_completion_counter))
{
g_mutex_lock (&gimp_parallel_distribute_completion_mutex);
g_cond_signal (&gimp_parallel_distribute_completion_cond);
g_mutex_unlock (&gimp_parallel_distribute_completion_mutex);
}
thread->task = NULL;
}
g_cond_wait (&thread->cond, &thread->mutex);
}
g_mutex_unlock (&thread->mutex);
return NULL;
}
} /* extern "C" */
......@@ -47,17 +47,6 @@ GimpAsync * gimp_parallel_run_async_full (gint
GimpAsync * gimp_parallel_run_async_independent (GimpParallelRunAsyncFunc func,
gpointer user_data);
void gimp_parallel_distribute (gint max_n,
GimpParallelDistributeFunc func,
gpointer user_data);
void gimp_parallel_distribute_range (gsize size,
gsize min_sub_size,
GimpParallelDistributeRangeFunc func,
gpointer user_data);
void gimp_parallel_distribute_area (const GeglRectangle *area,
gsize min_sub_area,
GimpParallelDistributeAreaFunc func,
gpointer user_data);
#ifdef __cplusplus
......@@ -161,61 +150,6 @@ gimp_parallel_run_async_independent (ParallelRunAsyncFunc func)
func_copy);
}
template <class ParallelDistributeFunc>
inline void
gimp_parallel_distribute (gint max_n,
ParallelDistributeFunc func)
{
gimp_parallel_distribute (max_n,
[] (gint i,
gint n,
gpointer user_data)
{
ParallelDistributeFunc func_copy (
*(const ParallelDistributeFunc *) user_data);
func_copy (i, n);
},
&func);
}
template <class ParallelDistributeRangeFunc>
inline void
gimp_parallel_distribute_range (gsize size,
gsize min_sub_size,
ParallelDistributeRangeFunc func)
{
gimp_parallel_distribute_range (size, min_sub_size,
[] (gsize offset,
gsize size,
gpointer user_data)
{
ParallelDistributeRangeFunc func_copy (
*(const ParallelDistributeRangeFunc *) user_data);
func_copy (offset, size);
},
&func);
}
template <class ParallelDistributeAreaFunc>
inline void
gimp_parallel_distribute_area (const GeglRectangle *area,
gsize min_sub_area,
ParallelDistributeAreaFunc func)
{
gimp_parallel_distribute_area (area, min_sub_area,
[] (const GeglRectangle *area,
gpointer user_data)
{
ParallelDistributeAreaFunc func_copy (
*(const ParallelDistributeAreaFunc *) user_data);
func_copy (area);
},
&func);
}
}
#endif /* __cplusplus */
......
......@@ -33,14 +33,13 @@ extern "C"
#include "gegl/gimp-gegl-loops.h"
#include "gimp-parallel.h"
#include "gimpbrush.h"
#include "gimpbrush-transform.h"
#include "gimptempbuf.h"
#define MIN_PARALLEL_SUB_SIZE 64
#define MIN_PARALLEL_SUB_AREA (MIN_PARALLEL_SUB_SIZE * MIN_PARALLEL_SUB_SIZE)
#define PIXELS_PER_THREAD \
(/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
/* local function prototypes */
......@@ -256,9 +255,9 @@ gimp_brush_real_transform_mask (GimpBrush *brush,
src_walk_vx_i = (gint) ((src_tl_to_bl_delta_x / dest_height) * int_multiple);
src_walk_vy_i = (gint) ((src_tl_to_bl_delta_y / dest_height) * int_multiple);
gimp_parallel_distribute_area (GEGL_RECTANGLE (0, 0, dest_width, dest_height),
MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *area)
gegl_parallel_distribute_area (
GEGL_RECTANGLE (0, 0, dest_width, dest_height), PIXELS_PER_THREAD,
[=] (const GeglRectangle *area)
{
guchar *dest;
gint src_space_cur_pos_x;
......@@ -556,9 +555,9 @@ gimp_brush_real_transform_pixmap (GimpBrush *brush,
src_walk_vx_i = (gint) ((src_tl_to_bl_delta_x / dest_height) * int_multiple);
src_walk_vy_i = (gint) ((src_tl_to_bl_delta_y / dest_height) * int_multiple);
gimp_parallel_distribute_area (GEGL_RECTANGLE (0, 0, dest_width, dest_height),
MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *area)
gegl_parallel_distribute_area (
GEGL_RECTANGLE (0, 0, dest_width, dest_height), PIXELS_PER_THREAD,
[=] (const GeglRectangle *area)
{
guchar *dest;
gint src_space_cur_pos_x;
......@@ -808,8 +807,9 @@ gimp_brush_transform_blur (GimpTempBuf *buf,
sums = g_new (Sums, width * height * components);
gimp_parallel_distribute_range (height, MIN_PARALLEL_SUB_SIZE,
[=] (gint y0, gint height)
gegl_parallel_distribute_range (
height, PIXELS_PER_THREAD / width,
[=] (gint y0, gint height)
{
gint x;
gint y;
......@@ -885,8 +885,9 @@ gimp_brush_transform_blur (GimpTempBuf *buf,
}
});
gimp_parallel_distribute_range (width, MIN_PARALLEL_SUB_SIZE,
[=] (gint x0, gint width)
gegl_parallel_distribute_range (
width, PIXELS_PER_THREAD / height,
[=] (gint x0, gint width)
{
gint x;
gint y;
......
......@@ -41,8 +41,8 @@
#include "gimpwaitable.h"
#define MIN_PARALLEL_SUB_SIZE 64
#define MIN_PARALLEL_SUB_AREA (MIN_PARALLEL_SUB_SIZE * MIN_PARALLEL_SUB_SIZE)
#define PIXELS_PER_THREAD \
(/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
enum
......@@ -912,8 +912,8 @@ gimp_histogram_calculate_internal (GimpAsync *async,
data.format = format;
data.values_list = NULL;
gimp_parallel_distribute_area (
&context->buffer_rect, MIN_PARALLEL_SUB_AREA,
gegl_parallel_distribute_area (
&context->buffer_rect, PIXELS_PER_THREAD, GEGL_SPLIT_STRATEGY_AUTO,
(GimpParallelDistributeAreaFunc) gimp_histogram_calculate_area,
&data);
......
......@@ -41,13 +41,12 @@ extern "C"
#include "gimp-gegl-loops-sse2.h"
#include "core/gimp-atomic.h"
#include "core/gimp-parallel.h"
#include "core/gimp-utils.h"
#include "core/gimpprogress.h"
#define MIN_PARALLEL_SUB_SIZE 64
#define MIN_PARALLEL_SUB_AREA (MIN_PARALLEL_SUB_SIZE * MIN_PARALLEL_SUB_SIZE)
#define PIXELS_PER_THREAD \
(/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
#define SHIFTED_AREA(dest, src) \
const GeglRectangle dest##_area_ = { \
......@@ -82,8 +81,9 @@ gimp_gegl_buffer_copy (GeglBuffer *src_buffer,
if (! dest_rect)
dest_rect = src_rect;
gimp_parallel_distribute_area (src_rect, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *src_area)
gegl_parallel_distribute_area (
src_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *src_area)
{
SHIFTED_AREA (dest, src);
......@@ -165,8 +165,9 @@ gimp_gegl_convolve (GeglBuffer *src_buffer,
offset = 0.0;
}
gimp_parallel_distribute_area (dest_rect, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *dest_area)
gegl_parallel_distribute_area (
dest_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *dest_area)
{
const gint components = src_components;
const gint a_component = components - 1;
......@@ -317,8 +318,9 @@ gimp_gegl_dodgeburn (GeglBuffer *src_buffer,
if (! dest_rect)
dest_rect = gegl_buffer_get_extent (dest_buffer);
gimp_parallel_distribute_area (src_rect, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *src_area)
gegl_parallel_distribute_area (
src_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *src_area)
{
GeglBufferIterator *iter;
......@@ -552,8 +554,9 @@ gimp_gegl_smudge_with_paint (GeglBuffer *accum_buffer,
brush_a *= brush_color_ptr[3];
}
gimp_parallel_distribute_area (accum_rect, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *accum_area)
gegl_parallel_distribute_area (
accum_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *accum_area)
{
GeglBufferIterator *iter;
......@@ -620,8 +623,9 @@ gimp_gegl_apply_mask (GeglBuffer *mask_buffer,
if (! dest_rect)
dest_rect = gegl_buffer_get_extent (dest_buffer);
gimp_parallel_distribute_area (mask_rect, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *mask_area)
gegl_parallel_distribute_area (
mask_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *mask_area)
{
GeglBufferIterator *iter;
......@@ -665,8 +669,9 @@ gimp_gegl_combine_mask (GeglBuffer *mask_buffer,
if (! dest_rect)
dest_rect = gegl_buffer_get_extent (dest_buffer);
gimp_parallel_distribute_area (mask_rect, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *mask_area)
gegl_parallel_distribute_area (
mask_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *mask_area)
{
GeglBufferIterator *iter;
......@@ -711,8 +716,9 @@ gimp_gegl_combine_mask_weird (GeglBuffer *mask_buffer,
if (! dest_rect)
dest_rect = gegl_buffer_get_extent (dest_buffer);
gimp_parallel_distribute_area (mask_rect, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *mask_area)
gegl_parallel_distribute_area (
mask_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *mask_area)
{
GeglBufferIterator *iter;
......@@ -781,8 +787,9 @@ gimp_gegl_replace (GeglBuffer *top_buffer,
if (! dest_rect)
dest_rect = gegl_buffer_get_extent (dest_buffer);
gimp_parallel_distribute_area (top_rect, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *top_area)
gegl_parallel_distribute_area (
top_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *top_area)
{
GeglBufferIterator *iter;
......@@ -897,8 +904,9 @@ gimp_gegl_index_to_mask (GeglBuffer *indexed_buffer,
if (! mask_rect)
mask_rect = gegl_buffer_get_extent (mask_buffer);
gimp_parallel_distribute_area (indexed_rect, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *indexed_area)
gegl_parallel_distribute_area (
indexed_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *indexed_area)
{
GeglBufferIterator *iter;
......@@ -993,8 +1001,9 @@ gimp_gegl_convert_color_profile (GeglBuffer *src_buffer,
GIMP_TIMER_START ();
gimp_parallel_distribute_area (src_rect, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *src_area)
gegl_parallel_distribute_area (
src_rect, PIXELS_PER_THREAD,
[=] (const GeglRectangle *src_area)
{
SHIFTED_AREA (dest, src);
......@@ -1055,8 +1064,9 @@ gimp_gegl_average_color (GeglBuffer *buffer,
else
roi = *rect;
gimp_parallel_distribute_area (&roi, MIN_PARALLEL_SUB_AREA,
[&] (const GeglRectangle *area)
gegl_parallel_distribute_area (
&roi, PIXELS_PER_THREAD,
[&] (const GeglRectangle *area)
{
Sum *sum;
GeglBufferIterator *iter;
......
......@@ -29,7 +29,6 @@ extern "C"
#include "paint-types.h"
#include "core/gimp-parallel.h"
#include "core/gimptempbuf.h"
#include "gimpbrushcore.h"
......@@ -37,8 +36,8 @@ extern "C"
#include "gimpbrushcore-kernels.h"
#define MIN_PARALLEL_SUB_SIZE 64
#define MIN_PARALLEL_SUB_AREA (MIN_PARALLEL_SUB_SIZE * MIN_PARALLEL_SUB_SIZE)
#define PIXELS_PER_THREAD \
(/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
static inline void
......@@ -133,8 +132,9 @@ gimp_brush_core_subsample_mask (GimpBrushCore *core,
core->subsample_brushes[index2][index1] = dest;
gimp_parallel_distribute_range (mask_height, MIN_PARALLEL_SUB_SIZE,
[=] (gint y, gint height)
gegl_parallel_distribute_range (
mask_height, PIXELS_PER_THREAD / mask_width,
[=] (gint y, gint height)
{
const guchar *m;
guchar *d;
......@@ -322,10 +322,11 @@ gimp_brush_core_pressurize_mask (GimpBrushCore *core,
/* Now convert the brush */
gimp_parallel_distribute_range (gimp_temp_buf_get_width (subsample_mask) *
gimp_temp_buf_get_height (subsample_mask),
MIN_PARALLEL_SUB_AREA,
[=] (gint offset, gint size)
gegl_parallel_distribute_range (
gimp_temp_buf_get_width (subsample_mask) *
gimp_temp_buf_get_height (subsample_mask),
PIXELS_PER_THREAD,
[=] (gint offset, gint size)
{
const guchar *source;
guchar *dest;
......@@ -396,12 +397,10 @@ gimp_brush_core_solidify_mask (GimpBrushCore *core,
core->solid_brushes[dest_offset_y][dest_offset_x] = dest;
gimp_parallel_distribute_area (GEGL_RECTANGLE (0,
0,
brush_mask_width,
brush_mask_height),
MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *area)
gegl_parallel_distribute_area (
GEGL_RECTANGLE (0, 0, brush_mask_width, brush_mask_height),
PIXELS_PER_THREAD,
[=] (const GeglRectangle *area)
{
const guchar *m;
gfloat *d;
......
......@@ -29,7 +29,6 @@ extern "C"
#include "operations/layer-modes/gimp-layer-modes.h"
#include "core/gimp-parallel.h"
#include "core/gimptempbuf.h"
#include "operations/layer-modes/gimpoperationlayermode.h"
......@@ -39,8 +38,8 @@ extern "C"
} /* extern "C" */
#define MIN_PARALLEL_SUB_SIZE 64
#define MIN_PARALLEL_SUB_AREA (MIN_PARALLEL_SUB_SIZE * MIN_PARALLEL_SUB_SIZE)
#define PIXELS_PER_THREAD \
(/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
/* In order to avoid iterating over the same region of the same buffers
......@@ -1133,8 +1132,9 @@ gimp_paint_core_loops_process (const GimpPaintCoreLoopsParams *params,
Algorithm algorithm (params);
gimp_parallel_distribute_area (&roi, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *area)
gegl_parallel_distribute_area (
&roi, PIXELS_PER_THREAD,
[=] (const GeglRectangle *area)
{
State state;
gint y;
......@@ -1350,8 +1350,9 @@ mask_components_onto (GeglBuffer *src_buffer,
gimp_babl_precision (GIMP_COMPONENT_TYPE_FLOAT, trc),
TRUE, space);
gimp_parallel_distribute_area (roi, MIN_PARALLEL_SUB_AREA,
[=] (const GeglRectangle *area)
gegl_parallel_distribute_area (
roi, PIXELS_PER_THREAD,
[=] (const GeglRectangle *area)
{
GeglBufferIterator *iter;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment