Option to have painting multi-threaded.

Each thread process the next free bucket the brush is over until they are all done.
This commit is contained in:
Campbell Barton 2008-11-12 05:56:37 +00:00
parent 58b861170c
commit c63c99d49e

@ -49,6 +49,7 @@
#include "BLI_linklist.h"
#include "BLI_memarena.h"
#include "PIL_time.h"
#include "BLI_threads.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
@ -138,6 +139,7 @@ typedef struct ImagePaintState {
//#define PROJ_DEBUG_PAINT 1
#define PROJ_DEBUG_NOSCANLINE 1
//#define PROJ_DEBUG_NOSEAMBLEED 1
//#define PROJ_DEBUG_PRINT_THREADS 1
/* projectFaceSeamFlags options */
//#define PROJ_FACE_IGNORE 1<<0 /* When the face is hidden, backfacing or occluded */
@ -178,6 +180,7 @@ typedef struct ProjectPaintState {
/* projection painting only */
MemArena *projectArena; /* use for alocating many pixel structs and link-lists */
MemArena *projectArena_mt[BLENDER_MAX_THREADS]; /* Same as above but use for multithreading */
LinkNode **projectBuckets; /* screen sized 2D array, each pixel has a linked list of ProjectPixel's */
LinkNode **projectFaces; /* projectBuckets alligned array linkList of faces overlapping each bucket */
char *projectBucketFlags; /* store if the bucks have been initialized */
@ -193,7 +196,6 @@ typedef struct ProjectPaintState {
ImBuf **projectImBufs; /* array of imbufs we are painting onto while, use so we can get the rect and rect_float quickly */
int projectImageTotal; /* size of projectImages array */
int imaContextIndex; /* current image, use for context switching */
float (*projectVertScreenCos)[4]; /* verts projected into floating point screen space */
@ -216,6 +218,12 @@ typedef struct ProjectPaintState {
float viewMax2D[2];
float viewWidth; /* Calculated from viewMin2D & viewMax2D */
float viewHeight;
/* threads */
int thread_tot;
int min_bucket[2];
int max_bucket[2];
int context_bucket_x, context_bucket_y; /* must lock threads while accessing these */
} ProjectPaintState;
#ifndef PROJ_DEBUG_NOSCANLINE
@ -1201,6 +1209,8 @@ static void uv_image_outset(float (*orig_uv)[2], float (*outset_uv)[2], float sc
/*
* Be tricky with flags, first 4 bits are PROJ_FACE_SEAM1 to 4, last 4 bits are PROJ_FACE_NOSEAM1 to 4
* 1<<i - where i is (0-3)
*
* If we're multithreadng, make sure threads are locked when this is called
*/
static void project_face_seams_init(ProjectPaintState *ps, int face_index, int is_quad)
{
@ -1280,7 +1290,7 @@ static screen_px_from_persp(
/* Only run this function once for new ProjectPixelClone's */
#define pixel_size 4
static void project_paint_uvpixel_init(ProjectPaintState *ps, ImBuf *ibuf, int x, int y, int bucket_index, int face_index, float pixelScreenCo[4])
static void project_paint_uvpixel_init(ProjectPaintState *ps, int thread_index, ImBuf *ibuf, int x, int y, int bucket_index, int face_index, int image_index, float pixelScreenCo[4])
{
ProjectPixel *projPixel;
short size;
@ -1308,7 +1318,7 @@ static void project_paint_uvpixel_init(ProjectPaintState *ps, ImBuf *ibuf, int x
size = sizeof(ProjectPixel);
}
projPixel = (ProjectPixel *)BLI_memarena_alloc(ps->projectArena, size);
projPixel = (ProjectPixel *)BLI_memarena_alloc(ps->projectArena_mt[thread_index], size);
if (ibuf->rect_float) {
projPixel->pixel = (void *) ((( float * ) ibuf->rect_float) + (( x + y * ibuf->x ) * pixel_size));
@ -1345,12 +1355,12 @@ static void project_paint_uvpixel_init(ProjectPaintState *ps, ImBuf *ibuf, int x
if (ibuf->rect_float) ((float *)projPixel->pixel)[1] = 0;
else ((char *)projPixel->pixel)[1] = 0;
#endif
projPixel->image_index = ps->imaContextIndex;
projPixel->image_index = image_index;
BLI_linklist_prepend_arena(
&ps->projectBuckets[ bucket_index ],
projPixel,
ps->projectArena
ps->projectArena_mt[thread_index]
);
}
}
@ -1493,8 +1503,50 @@ static void scale_tri(float *origCos[4], float insetCos[4][3], float inset)
VecAddf(insetCos[2], insetCos[2], cent);
}
/**/
static void project_paint_face_init(ProjectPaintState *ps, int bucket_index, int face_index, float bucket_bounds[4], ImBuf *ibuf)
static void rect_to_uvspace(
ProjectPaintState *ps, float bucket_bounds[4],
float *v1coSS, float *v2coSS, float *v3coSS,
float *uv1co, float *uv2co, float *uv3co,
float bucket_bounds_uv[4][2]
)
{
float uv[2];
float w[3];
/* get the UV space bounding box */
uv[0] = bucket_bounds[PROJ_BUCKET_RIGHT];
uv[1] = bucket_bounds[PROJ_BUCKET_BOTTOM];
if (ps->projectIsOrtho) BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
else BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
bucket_bounds_uv[0][0] = uv1co[0]*w[0] + uv2co[0]*w[1] + uv3co[0]*w[2];
bucket_bounds_uv[0][1] = uv1co[1]*w[0] + uv2co[1]*w[1] + uv3co[1]*w[2];
//uv[0] = bucket_bounds[PROJ_BUCKET_RIGHT]; // set above
uv[1] = bucket_bounds[PROJ_BUCKET_TOP];
if (ps->projectIsOrtho) BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
else BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
bucket_bounds_uv[1][0] = uv1co[0]*w[0] + uv2co[0]*w[1] + uv3co[0]*w[2];
bucket_bounds_uv[1][1] = uv1co[1]*w[0] + uv2co[1]*w[1] + uv3co[1]*w[2];
uv[0] = bucket_bounds[PROJ_BUCKET_LEFT];
//uv[1] = bucket_bounds[PROJ_BUCKET_TOP]; // set above
if (ps->projectIsOrtho) BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
else BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
bucket_bounds_uv[2][0] = uv1co[0]*w[0] + uv2co[0]*w[1] + uv3co[0]*w[2];
bucket_bounds_uv[2][1] = uv1co[1]*w[0] + uv2co[1]*w[1] + uv3co[1]*w[2];
//uv[0] = bucket_bounds[PROJ_BUCKET_LEFT]; // set above
uv[1] = bucket_bounds[PROJ_BUCKET_BOTTOM];
if (ps->projectIsOrtho) BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
else BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
bucket_bounds_uv[3][0] = uv1co[0]*w[0] + uv2co[0]*w[1] + uv3co[0]*w[2];
bucket_bounds_uv[3][1] = uv1co[1]*w[0] + uv2co[1]*w[1] + uv3co[1]*w[2];
}
/* initialize pixels from this face where it intersects with the bucket_index, initialize pixels for removing seams */
static void project_paint_face_init(ProjectPaintState *ps, int thread_index, int bucket_index, int face_index, int image_index, float bucket_bounds[4], ImBuf *ibuf)
{
/* Projection vars, to get the 3D locations into screen space */
@ -1508,7 +1560,7 @@ static void project_paint_face_init(ProjectPaintState *ps, int bucket_index, int
int min_px[2], max_px[2]; /* UV Bounds converted to int's for pixel */
int min_px_tf[2], max_px_tf[2]; /* UV Bounds converted to int's for pixel */
int min_px_bucket[2][2], max_px_bucket[2][2]; /* Bucket Bounds converted to int's for pixel */
int min_px_bucket[2], max_px_bucket[2]; /* Bucket Bounds converted to int's for pixel */
float *v1coSS, *v2coSS, *v3coSS, *v4coSS; /* vert co screen-space, these will be assigned to mf->v1,2,3 or mf->v1,3,4 */
float *v1co, *v2co, *v3co; /* vert co */
float *vCo[4]; /* vertex screenspace coords */
@ -1551,43 +1603,15 @@ static void project_paint_face_init(ProjectPaintState *ps, int bucket_index, int
v2coSS = ps->projectVertScreenCos[ (*(&mf->v1 + i2)) ];
v3coSS = ps->projectVertScreenCos[ (*(&mf->v1 + i3)) ];
/* get the UV space bounding box */
uv[0] = bucket_bounds[PROJ_BUCKET_RIGHT];
uv[1] = bucket_bounds[PROJ_BUCKET_BOTTOM];
if (ps->projectIsOrtho) BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
else BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
bucket_bounds_uv[0][0] = uv1co[0]*w[0] + uv2co[0]*w[1] + uv3co[0]*w[2];
bucket_bounds_uv[0][1] = uv1co[1]*w[0] + uv2co[1]*w[1] + uv3co[1]*w[2];
//uv[0] = bucket_bounds[PROJ_BUCKET_RIGHT]; // set above
uv[1] = bucket_bounds[PROJ_BUCKET_TOP];
if (ps->projectIsOrtho) BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
else BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
bucket_bounds_uv[1][0] = uv1co[0]*w[0] + uv2co[0]*w[1] + uv3co[0]*w[2];
bucket_bounds_uv[1][1] = uv1co[1]*w[0] + uv2co[1]*w[1] + uv3co[1]*w[2];
uv[0] = bucket_bounds[PROJ_BUCKET_LEFT];
//uv[1] = bucket_bounds[PROJ_BUCKET_TOP]; // set above
if (ps->projectIsOrtho) BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
else BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
bucket_bounds_uv[2][0] = uv1co[0]*w[0] + uv2co[0]*w[1] + uv3co[0]*w[2];
bucket_bounds_uv[2][1] = uv1co[1]*w[0] + uv2co[1]*w[1] + uv3co[1]*w[2];
//uv[0] = bucket_bounds[PROJ_BUCKET_LEFT]; // set above
uv[1] = bucket_bounds[PROJ_BUCKET_BOTTOM];
if (ps->projectIsOrtho) BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
else BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
bucket_bounds_uv[3][0] = uv1co[0]*w[0] + uv2co[0]*w[1] + uv3co[0]*w[2];
bucket_bounds_uv[3][1] = uv1co[1]*w[0] + uv2co[1]*w[1] + uv3co[1]*w[2];
rect_to_uvspace(ps, bucket_bounds, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, bucket_bounds_uv);
//printf("Bounds: %f | %f | %f | %f\n", bucket_bounds[0], bucket_bounds[1], bucket_bounds[2], bucket_bounds[3]);
if ( uv_image_rect(uv1co, uv2co, uv3co, NULL, min_px_tf, max_px_tf, ibuf->x, ibuf->y, 0) &&
uv_image_rect(bucket_bounds_uv[0], bucket_bounds_uv[1], bucket_bounds_uv[2], bucket_bounds_uv[3], min_px_bucket[i], max_px_bucket[i], ibuf->x, ibuf->y, 1) )
uv_image_rect(bucket_bounds_uv[0], bucket_bounds_uv[1], bucket_bounds_uv[2], bucket_bounds_uv[3], min_px_bucket, max_px_bucket, ibuf->x, ibuf->y, 1) )
{
uvpixel_rect_intersect(min_px, max_px, min_px_bucket[i], max_px_bucket[i], min_px_tf, max_px_tf);
uvpixel_rect_intersect(min_px, max_px, min_px_bucket, max_px_bucket, min_px_tf, max_px_tf);
/* clip face and */
@ -1607,7 +1631,7 @@ static void project_paint_face_init(ProjectPaintState *ps, int bucket_index, int
screen_px_from_persp(ps, uv, vCo[i1],vCo[i2],vCo[i3], uv1co,uv2co,uv3co, pixelScreenCo);
}
project_paint_uvpixel_init(ps, ibuf, x,y, bucket_index, face_index, pixelScreenCo);
project_paint_uvpixel_init(ps, thread_index, ibuf, x,y, bucket_index, face_index, image_index, pixelScreenCo);
}
}
}
@ -1615,21 +1639,30 @@ static void project_paint_face_init(ProjectPaintState *ps, int bucket_index, int
} while(i--);
#ifndef PROJ_DEBUG_NOSEAMBLEED
if (ps->projectSeamBleed > 0.0) {
int face_seam_flag;
int flag = ps->projectFaceSeamFlags[face_index];
if (ps->thread_tot > 1)
BLI_lock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */
face_seam_flag = ps->projectFaceSeamFlags[face_index];
/* are any of our edges un-initialized? */
if ((flag & (PROJ_FACE_SEAM1|PROJ_FACE_NOSEAM1))==0 ||
(flag & (PROJ_FACE_SEAM2|PROJ_FACE_NOSEAM2))==0 ||
(flag & (PROJ_FACE_SEAM3|PROJ_FACE_NOSEAM3))==0 ||
(flag & (PROJ_FACE_SEAM4|PROJ_FACE_NOSEAM4))==0
if ((face_seam_flag & (PROJ_FACE_SEAM1|PROJ_FACE_NOSEAM1))==0 ||
(face_seam_flag & (PROJ_FACE_SEAM2|PROJ_FACE_NOSEAM2))==0 ||
(face_seam_flag & (PROJ_FACE_SEAM3|PROJ_FACE_NOSEAM3))==0 ||
(face_seam_flag & (PROJ_FACE_SEAM4|PROJ_FACE_NOSEAM4))==0
) {
project_face_seams_init(ps, face_index, mf->v4);
flag = ps->projectFaceSeamFlags[face_index];
face_seam_flag = ps->projectFaceSeamFlags[face_index];
//printf("seams - %d %d %d %d\n", flag&PROJ_FACE_SEAM1, flag&PROJ_FACE_SEAM2, flag&PROJ_FACE_SEAM3, flag&PROJ_FACE_SEAM4);
}
if (flag & (PROJ_FACE_SEAM1|PROJ_FACE_SEAM2|PROJ_FACE_SEAM3|PROJ_FACE_SEAM4)) {
if ((face_seam_flag & (PROJ_FACE_SEAM1|PROJ_FACE_SEAM2|PROJ_FACE_SEAM3|PROJ_FACE_SEAM4))==0) {
if (ps->thread_tot > 1)
BLI_unlock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */
} else {
/* we have a seam - deal with it! */
/* Now create new UV's for the seam face */
@ -1652,6 +1685,10 @@ static void project_paint_face_init(ProjectPaintState *ps, int bucket_index, int
if (outset_uv[0][0]==MAXFLOAT) /* first time initialize */
uv_image_outset(tf->uv, outset_uv, ps->projectSeamBleed, ibuf->x, ibuf->y, mf->v4);
/* ps->projectFaceSeamUVs cant be modified when threading, now this is done we can unlock */
if (ps->thread_tot > 1)
BLI_unlock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */
vCoSS[0] = ps->projectVertScreenCos[ mf->v1 ];
vCoSS[1] = ps->projectVertScreenCos[ mf->v2 ];
vCoSS[2] = ps->projectVertScreenCos[ mf->v3 ];
@ -1670,7 +1707,7 @@ static void project_paint_face_init(ProjectPaintState *ps, int bucket_index, int
if (mf->v4) fidx2 = (fidx1==3) ? 0 : fidx1+1; /* next fidx in the face (0,1,2,3) -> (1,2,3,0) */
else fidx2 = (fidx1==2) ? 0 : fidx1+1; /* next fidx in the face (0,1,2) -> (1,2,0) */
if ( (ps->projectFaceSeamFlags[face_index] & (1<<fidx1) ) && /* 1<<fidx1 -> PROJ_FACE_SEAM# */
if ( (face_seam_flag & (1<<fidx1) ) && /* 1<<fidx1 -> PROJ_FACE_SEAM# */
line_clip_rect2f(bucket_bounds, vCoSS[fidx1], vCoSS[fidx2], bucket_clip_edges[0], bucket_clip_edges[1])
) {
@ -1736,7 +1773,7 @@ static void project_paint_face_init(ProjectPaintState *ps, int bucket_index, int
pixelScreenCo[2] = pixelScreenCo[2]/pixelScreenCo[3]; /* Use the depth for bucket point occlusion */
}
project_paint_uvpixel_init(ps, ibuf, x, y, bucket_index, face_index, pixelScreenCo);
project_paint_uvpixel_init(ps, thread_index, ibuf, x, y, bucket_index, face_index, image_index, pixelScreenCo);
}
}
}
@ -1778,10 +1815,10 @@ static void project_bucket_bounds(ProjectPaintState *ps, int bucket_x, int bucke
}
/* have bucket_bounds as an arg so we dont need to give bucket_x/y the rect function need */
static void project_paint_bucket_init(ProjectPaintState *ps, int bucket_index, float bucket_bounds[4])
static void project_paint_bucket_init(ProjectPaintState *ps, int thread_index, int bucket_index, float bucket_bounds[4])
{
LinkNode *node;
int face_index;
int face_index, image_index;
ImBuf *ibuf;
MTFace *tf;
@ -1797,23 +1834,23 @@ static void project_paint_bucket_init(ProjectPaintState *ps, int bucket_index, f
if (tpage_last != tf->tpage) {
tpage_last = tf->tpage;
ps->imaContextIndex = -1; /* sanity check */
image_index = -1; /* sanity check */
for (tpage_index=0; tpage_index < ps->projectImageTotal; tpage_index++) {
if (ps->projectImages[tpage_index] == tpage_last) {
ps->imaContextIndex = tpage_index;
image_index = tpage_index;
break;
}
}
if (ps->imaContextIndex==-1) {
if (image_index==-1) {
printf("Error, should never happen!\n");
return;
}
ibuf = BKE_image_get_ibuf(tpage_last, NULL); /* TODO - this may be slow */
}
project_paint_face_init(ps, bucket_index, face_index, bucket_bounds, ibuf);
project_paint_face_init(ps, thread_index, bucket_index, face_index, image_index, bucket_bounds, ibuf);
node = node->next;
} while (node);
@ -1896,7 +1933,9 @@ static void project_paint_delayed_face_init(ProjectPaintState *ps, MFace *mf, MT
float min[2], max[2];
int bucket_min[2], bucket_max[2]; /* for ps->projectBuckets indexing */
int i, a, bucket_x, bucket_y, bucket_index;
int has_x_isect = -1, has_isect = -1; /* for early loop exit */
INIT_MINMAX2(min,max);
i = mf->v4 ? 3:2;
@ -1920,6 +1959,7 @@ static void project_paint_delayed_face_init(ProjectPaintState *ps, MFace *mf, MT
project_paint_rect(ps, min, max, bucket_min, bucket_max);
for (bucket_y = bucket_min[1]; bucket_y < bucket_max[1]; bucket_y++) {
has_x_isect = 0;
for (bucket_x = bucket_min[0]; bucket_x < bucket_max[0]; bucket_x++) {
bucket_index = bucket_x + (bucket_y * ps->bucketsX);
@ -1930,8 +1970,18 @@ static void project_paint_delayed_face_init(ProjectPaintState *ps, MFace *mf, MT
(void *)face_index, /* cast to a pointer to shut up the compiler */
ps->projectArena
);
has_x_isect = has_isect = 1;
} else if (has_x_isect) {
/* assuming the face is not a bow-tie - we know we cant intersect again on the X */
break;
}
}
/* no intersection for this entire row, after some intersection above means we can quit now */
if (has_x_isect==0 && has_isect) {
break;
}
}
#ifndef PROJ_DEBUG_NOSEAMBLEED
@ -1978,6 +2028,7 @@ static void project_paint_begin( ProjectPaintState *ps, short mval[2])
MTFace *tf;
int a, i; /* generic looping vars */
int image_index;
/* memory sized to add to arena size */
int tot_bucketMem=0;
@ -2000,10 +2051,8 @@ static void project_paint_begin( ProjectPaintState *ps, short mval[2])
ps->dm_totvert = ps->dm->getNumVerts( ps->dm );
ps->dm_totface = ps->dm->getNumFaces( ps->dm );
ps->bucketsX = PROJ_BUCKET_DIV;
ps->bucketsY = PROJ_BUCKET_DIV;
ps->imaContextIndex = -1;
ps->bucketsX = G.rt ? G.rt : PROJ_BUCKET_DIV;
ps->bucketsY = G.rt ? G.rt : PROJ_BUCKET_DIV;
ps->viewDir[0] = 0.0;
ps->viewDir[1] = 0.0;
@ -2028,8 +2077,8 @@ static void project_paint_begin( ProjectPaintState *ps, short mval[2])
tot_faceListMem +
tot_faceSeamFlagMem +
tot_faceSeamUVMem +
tot_bucketVertFacesMem + (1<<16));
tot_bucketVertFacesMem + (1<<18));
//BLI_memarena_use_calloc(ps->projectArena); ?
ps->projectBuckets = (LinkNode **)BLI_memarena_alloc( ps->projectArena, tot_bucketMem);
ps->projectFaces= (LinkNode **)BLI_memarena_alloc( ps->projectArena, tot_faceListMem);
@ -2054,6 +2103,18 @@ static void project_paint_begin( ProjectPaintState *ps, short mval[2])
memset(ps->projectFaceSeamUVs, 0, tot_faceSeamUVMem);
}
#endif
/* Thread stuff */
if (G.scene->r.mode & R_FIXED_THREADS) {
ps->thread_tot = G.scene->r.threads;
} else {
ps->thread_tot = BLI_system_thread_count();
}
for (a=0; a<ps->thread_tot; a++) {
ps->projectArena_mt[a] = BLI_memarena_new(1<<16);
}
Mat4Invert(ps->ob->imat, ps->ob->obmat);
Mat3CpyMat4(mat, G.vd->viewinv);
@ -2169,11 +2230,11 @@ static void project_paint_begin( ProjectPaintState *ps, short mval[2])
ibuf= BKE_image_get_ibuf((Image *)tf->tpage, NULL);
if (ibuf) {
ps->imaContextIndex = BLI_linklist_index(image_LinkList, tf->tpage);
image_index = BLI_linklist_index(image_LinkList, tf->tpage);
if (ps->imaContextIndex==-1) { /* MemArena dosnt have an append func */
if (image_index==-1) { /* MemArena dosnt have an append func */
BLI_linklist_append(&image_LinkList, tf->tpage);
ps->imaContextIndex = ps->projectImageTotal;
image_index = ps->projectImageTotal;
ps->projectImageTotal++;
}
}
@ -2205,7 +2266,13 @@ static void project_paint_begin( ProjectPaintState *ps, short mval[2])
static void project_paint_end( ProjectPaintState *ps )
{
int a;
BLI_memarena_free(ps->projectArena);
for (a=0; a<ps->thread_tot; a++) {
BLI_memarena_free(ps->projectArena_mt[a]);
}
ps->dm->release(ps->dm);
}
@ -2711,7 +2778,86 @@ static int project_bucket_circle_isect(ProjectPaintState *ps, int bucket_x, int
return 0;
}
static int imapaint_paint_sub_stroke_project(ProjectPaintState *ps, BrushPainter *painter, short prevmval[2], short mval[2], double time, int update, float pressure)
/* Loop over all images on this mesh and update any we have touched */
static int imapaint_refresh_tagged(ProjectPaintState *ps)
{
int a;
int redraw = 0;
for (a=0; a < ps->projectImageTotal; a++) {
Image *ima = ps->projectImages[a];
if (ima->id.flag & LIB_DOIT) {
imapaint_image_update(ima, BKE_image_get_ibuf(ima, NULL), 1 /*texpaint*/ );
redraw = 1;
ima->id.flag &= ~LIB_DOIT; /* clear for reuse */
}
}
return redraw;
}
static int bucket_iter_init(ProjectPaintState *ps, float mval_f[2])
{
float min_brush[2], max_brush[2];
min_brush[0] = mval_f[0] - (ps->brush->size/2);
min_brush[1] = mval_f[1] - (ps->brush->size/2);
max_brush[0] = mval_f[0] + (ps->brush->size/2);
max_brush[1] = mval_f[1] + (ps->brush->size/2);
/* offset to make this a valid bucket index */
project_paint_rect(ps, min_brush, max_brush, ps->min_bucket, ps->max_bucket);
/* mouse outside the model areas? */
if (ps->min_bucket[0]==ps->max_bucket[0] || ps->min_bucket[1]==ps->max_bucket[1]) {
return 0;
}
ps->context_bucket_x = ps->min_bucket[0];
ps->context_bucket_y = ps->min_bucket[1];
#ifdef PROJ_DEBUG_PRINT_THREADS
printf("Initializing Values %d %d!", ps->min_bucket[0], ps->min_bucket[1]);
#endif
return 1;
}
static int bucket_iter_next(ProjectPaintState *ps, int *bucket_index, float bucket_bounds[4], float mval_f[2])
{
if (ps->thread_tot > 1)
BLI_lock_thread(LOCK_CUSTOM1);
//printf("%d %d \n", ps->context_bucket_x, ps->context_bucket_y);
for ( ; ps->context_bucket_y < ps->max_bucket[1]; ps->context_bucket_y++) {
for ( ; ps->context_bucket_x < ps->max_bucket[0]; ps->context_bucket_x++) {
/* use bucket_bounds for project_bucket_circle_isect and project_paint_bucket_init*/
project_bucket_bounds(ps, ps->context_bucket_x, ps->context_bucket_y, bucket_bounds);
if (project_bucket_circle_isect(ps, ps->context_bucket_x, ps->context_bucket_y, mval_f, ps->brush->size * ps->brush->size, bucket_bounds)) {
*bucket_index = ps->context_bucket_x + (ps->context_bucket_y * ps->bucketsX);
#ifdef PROJ_DEBUG_PRINT_THREADS
printf(" --- %d %d \n", ps->context_bucket_x, ps->context_bucket_y);
#endif
ps->context_bucket_x++;
if (ps->thread_tot > 1)
BLI_unlock_thread(LOCK_CUSTOM1);
return 1;
}
}
ps->context_bucket_x = ps->min_bucket[0];
}
if (ps->thread_tot > 1)
BLI_unlock_thread(LOCK_CUSTOM1);
return 0;
}
static int imapaint_paint_sub_stroke_project(ProjectPaintState *ps, BrushPainter *painter, short prevmval[2], short mval[2], double time, float pressure, int thread_index)
{
/* TODO - texpaint option : is there any use in projection painting from the image window??? - could be interesting */
/* TODO - floating point images */
@ -2727,19 +2873,18 @@ static int imapaint_paint_sub_stroke_project(ProjectPaintState *ps, BrushPainter
char rgba_ub[4];
float rgba_fp[4];
float brush_size_sqared;
float min[2], max[2]; /* brush bounds in screenspace */
//float min[2], max[2]; /* brush bounds in screenspace */
int bucket_min[2], bucket_max[2]; /* brush bounds in bucket grid space */
int bucket_index;
int a;
int is_floatbuf = 0;
short blend= ps->blend;
/* pixel values */
char *cp;
float *fp;
float bucket_bounds[4];
int bucket_x, bucket_y;
/* for smear only */
float mval_ofs[2];
@ -2747,22 +2892,8 @@ static int imapaint_paint_sub_stroke_project(ProjectPaintState *ps, BrushPainter
LinkNode *smearPixels = NULL;
LinkNode *smearPixels_float = NULL;
MemArena *smearArena = NULL; /* mem arena for this brush projection only */
mval_f[0] = mval[0]; mval_f[1] = mval[1];
min[0] = mval_f[0] - (ps->brush->size/2);
min[1] = mval_f[1] - (ps->brush->size/2);
max[0] = mval_f[0] + (ps->brush->size/2);
max[1] = mval_f[1] + (ps->brush->size/2);
/* offset to make this a valid bucket index */
project_paint_rect(ps, min, max, bucket_min, bucket_max);
/* mouse outside the model areas? */
if (bucket_min[0]==bucket_max[0] || bucket_min[1]==bucket_max[1]) {
return 0;
}
mval_f[0] = mval[0]; mval_f[1] = mval[1];
if (ps->tool==PAINT_TOOL_SMEAR) {
mval_ofs[0] = (float)(mval[0] - prevmval[0]);
@ -2781,130 +2912,128 @@ static int imapaint_paint_sub_stroke_project(ProjectPaintState *ps, BrushPainter
/* no clamping needed, dont use screen bounds, use vert bounds */
for (bucket_y = bucket_min[1]; bucket_y < bucket_max[1]; bucket_y++) {
for (bucket_x = bucket_min[0]; bucket_x < bucket_max[0]; bucket_x++) {
/* use bucket_bounds for project_bucket_circle_isect and project_paint_bucket_init*/
project_bucket_bounds(ps, bucket_x, bucket_y, bucket_bounds);
if (project_bucket_circle_isect(ps, bucket_x, bucket_y, mval_f, brush_size_sqared, bucket_bounds)) {
bucket_index = bucket_x + (bucket_y * ps->bucketsX);
/* Check this bucket and its faces are initialized */
if (ps->projectBucketFlags[bucket_index] == PROJ_BUCKET_NULL) {
/* No pixels initialized */
project_paint_bucket_init(ps, bucket_index, bucket_bounds);
}
/* TODO - we may want to init clone data in a seperate to project_paint_bucket_init
* so we dont go overboard and init too many clone pixels */
if ((node = ps->projectBuckets[bucket_index])) {
do {
projPixel = (ProjectPixel *)node->link;
/*dist = Vec2Lenf(projPixel->projCo2D, mval_f);*/ /* correct but uses a sqrt */
dist_nosqrt = Vec2Lenf_nosqrt(projPixel->projCo2D, mval_f);
/*if (dist < s->brush->size) {*/ /* correct but uses a sqrt */
if (dist_nosqrt < brush_size_sqared) {
if (last_index != projPixel->image_index) {
last_index = projPixel->image_index;
ps->projectImages[last_index]->id.flag |= LIB_DOIT;
is_floatbuf = ps->projectImBufs[last_index]->rect_float ? 1 : 0;
}
dist = (float)sqrt(dist_nosqrt);
switch(ps->tool) {
case PAINT_TOOL_CLONE:
if (is_floatbuf) {
if (((ProjectPixelCloneFloat*)projPixel)->clonepx[3]) {
alpha = brush_sample_falloff(ps->brush, dist);
fp = ((ProjectPixelCloneFloat *)projPixel)->clonepx;
if (alpha >= 1.0) {
VECCOPY((float *)projPixel->pixel, ((ProjectPixelCloneFloat *)projPixel)->clonepx );
} else {
((float *)projPixel->pixel)[0] = (fp[0] * alpha) + ((((float *)projPixel->pixel)[0])*(1.0-alpha));
((float *)projPixel->pixel)[1] = (fp[1] * alpha) + ((((float *)projPixel->pixel)[1])*(1.0-alpha));
((float *)projPixel->pixel)[2] = (fp[2] * alpha) + ((((float *)projPixel->pixel)[2])*(1.0-alpha));
}
}
} else {
if (((ProjectPixelClone*)projPixel)->clonepx[3]) {
alpha = brush_sample_falloff(ps->brush, dist);
cp = (char *)((ProjectPixelClone*)projPixel)->clonepx;
if (alpha >= 1.0) {
((char *)projPixel->pixel)[0] = cp[0];
((char *)projPixel->pixel)[1] = cp[1];
((char *)projPixel->pixel)[2] = cp[2];
} else {
((char *)projPixel->pixel)[0] = FTOCHAR( (((cp[0]/255.0) * alpha) + (((((char *)projPixel->pixel)[0])/255.0)*(1.0-alpha))) );
((char *)projPixel->pixel)[1] = FTOCHAR( (((cp[1]/255.0) * alpha) + (((((char *)projPixel->pixel)[1])/255.0)*(1.0-alpha))) );
((char *)projPixel->pixel)[2] = FTOCHAR( (((cp[2]/255.0) * alpha) + (((((char *)projPixel->pixel)[2])/255.0)*(1.0-alpha))) );
}
}
}
break;
case PAINT_TOOL_SMEAR:
Vec2Subf(co, projPixel->projCo2D, mval_ofs);
if (screenco_pickcol(ps, co, NULL, rgba_ub, 0)) { /* Note, no interpolation here, only needed for clone, nearest should be is OK */
brush_sample_tex(ps->brush, projPixel->projCo2D, rgba);
alpha = rgba[3]*brush_sample_falloff(ps->brush, dist);
/* drat! - this could almost be very simple if we ignore
* the fact that applying the color directly gives feedback,
* instead, collect the colors and apply after :/ */
#if 0 /* looks OK but not correct - also would need float buffer */
*((unsigned int *)projPixel->pixel) = IMB_blend_color( *((unsigned int *)projPixel->pixel), *((unsigned int *)rgba_ub), (int)(alpha*255), blend);
//for (bucket_y = bucket_min[1]; bucket_y < bucket_max[1]; bucket_y++) {
#ifdef PROJ_DEBUG_PRINT_THREADS
printf("THREAD %d %d %d\n", ps->thread_tot, thread_index, (ps->max_bucket[0] - ps->min_bucket[0]) * (ps->max_bucket[1] - ps->min_bucket[1]) );
#endif
/* add to memarena instead */
if (is_floatbuf) {
/* TODO FLOAT */ /* Smear wont do float properly yet */
char rgba_smear[4];
IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba_smear, (float *)projPixel->pixel);
*((unsigned int *) &((ProjectPixelClone *)projPixel)->clonepx) = IMB_blend_color( *((unsigned int *)rgba_smear), *((unsigned int *)rgba_ub), (int)(alpha*255), blend);
BLI_linklist_prepend_arena( &smearPixels_float, (void *)projPixel, smearArena );
} else {
*((unsigned int *) &((ProjectPixelClone *)projPixel)->clonepx) = IMB_blend_color( *((unsigned int *)projPixel->pixel), *((unsigned int *)rgba_ub), (int)(alpha*255), blend);
BLI_linklist_prepend_arena( &smearPixels, (void *)projPixel, smearArena );
}
while (bucket_iter_next(ps, &bucket_index, bucket_bounds, mval_f)) {
#ifdef PROJ_DEBUG_PRINT_THREADS
printf("\t%d %d\n", thread_index, bucket_index);
#endif
/* Check this bucket and its faces are initialized */
if (ps->projectBucketFlags[bucket_index] == PROJ_BUCKET_NULL) {
/* No pixels initialized */
project_paint_bucket_init(ps, thread_index, bucket_index, bucket_bounds);
}
/* TODO - we may want to init clone data in a seperate to project_paint_bucket_init
* so we dont go overboard and init too many clone pixels */
if ((node = ps->projectBuckets[bucket_index])) {
do {
projPixel = (ProjectPixel *)node->link;
/*dist = Vec2Lenf(projPixel->projCo2D, mval_f);*/ /* correct but uses a sqrt */
dist_nosqrt = Vec2Lenf_nosqrt(projPixel->projCo2D, mval_f);
/*if (dist < s->brush->size) {*/ /* correct but uses a sqrt */
if (dist_nosqrt < brush_size_sqared) {
if (last_index != projPixel->image_index) {
last_index = projPixel->image_index;
ps->projectImages[last_index]->id.flag |= LIB_DOIT; /* halgrind complains this is not threadsafe but probably ok? - we are only setting this flag anyway */
is_floatbuf = ps->projectImBufs[last_index]->rect_float ? 1 : 0;
}
dist = (float)sqrt(dist_nosqrt);
switch(ps->tool) {
case PAINT_TOOL_CLONE:
if (is_floatbuf) {
if (((ProjectPixelCloneFloat*)projPixel)->clonepx[3]) {
alpha = brush_sample_falloff(ps->brush, dist);
fp = ((ProjectPixelCloneFloat *)projPixel)->clonepx;
if (alpha >= 1.0) {
VECCOPY((float *)projPixel->pixel, ((ProjectPixelCloneFloat *)projPixel)->clonepx );
} else {
((float *)projPixel->pixel)[0] = (fp[0] * alpha) + ((((float *)projPixel->pixel)[0])*(1.0-alpha));
((float *)projPixel->pixel)[1] = (fp[1] * alpha) + ((((float *)projPixel->pixel)[1])*(1.0-alpha));
((float *)projPixel->pixel)[2] = (fp[2] * alpha) + ((((float *)projPixel->pixel)[2])*(1.0-alpha));
}
}
} else {
if (((ProjectPixelClone*)projPixel)->clonepx[3]) {
alpha = brush_sample_falloff(ps->brush, dist);
cp = (char *)((ProjectPixelClone*)projPixel)->clonepx;
if (alpha >= 1.0) {
((char *)projPixel->pixel)[0] = cp[0];
((char *)projPixel->pixel)[1] = cp[1];
((char *)projPixel->pixel)[2] = cp[2];
} else {
((char *)projPixel->pixel)[0] = FTOCHAR( (((cp[0]/255.0) * alpha) + (((((char *)projPixel->pixel)[0])/255.0)*(1.0-alpha))) );
((char *)projPixel->pixel)[1] = FTOCHAR( (((cp[1]/255.0) * alpha) + (((((char *)projPixel->pixel)[1])/255.0)*(1.0-alpha))) );
((char *)projPixel->pixel)[2] = FTOCHAR( (((cp[2]/255.0) * alpha) + (((((char *)projPixel->pixel)[2])/255.0)*(1.0-alpha))) );
}
break;
default:
brush_sample_tex(ps->brush, projPixel->projCo2D, rgba);
alpha = rgba[3]*brush_sample_falloff(ps->brush, dist);
if (alpha > 0.0) {
if (is_floatbuf) {
rgba_fp[0] = rgba[0] * ps->brush->rgb[0];
rgba_fp[1] = rgba[1] * ps->brush->rgb[1];
rgba_fp[2] = rgba[2] * ps->brush->rgb[2];
rgba_fp[3] = rgba[3];
IMB_blend_color_float( (float *)projPixel->pixel, (float *)projPixel->pixel, rgba_fp, alpha, blend);
} else {
rgba_ub[0] = FTOCHAR(rgba[0] * ps->brush->rgb[0]);
rgba_ub[1] = FTOCHAR(rgba[1] * ps->brush->rgb[1]);
rgba_ub[2] = FTOCHAR(rgba[2] * ps->brush->rgb[2]);
rgba_ub[3] = FTOCHAR(rgba[3]);
*((unsigned int *)projPixel->pixel) = IMB_blend_color( *((unsigned int *)projPixel->pixel), *((unsigned int *)rgba_ub), (int)(alpha*255), blend);
}
}
break;
}
/* done painting */
}
break;
case PAINT_TOOL_SMEAR:
Vec2Subf(co, projPixel->projCo2D, mval_ofs);
if (screenco_pickcol(ps, co, NULL, rgba_ub, 0)) { /* Note, no interpolation here, only needed for clone, nearest should be is OK */
brush_sample_tex(ps->brush, projPixel->projCo2D, rgba);
alpha = rgba[3]*brush_sample_falloff(ps->brush, dist);
/* drat! - this could almost be very simple if we ignore
* the fact that applying the color directly gives feedback,
* instead, collect the colors and apply after :/ */
#if 0 /* looks OK but not correct - also would need float buffer */
*((unsigned int *)projPixel->pixel) = IMB_blend_color( *((unsigned int *)projPixel->pixel), *((unsigned int *)rgba_ub), (int)(alpha*255), blend);
#endif
/* add to memarena instead */
if (is_floatbuf) {
/* TODO FLOAT */ /* Smear wont do float properly yet */
char rgba_smear[4];
IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba_smear, (float *)projPixel->pixel);
*((unsigned int *) &((ProjectPixelClone *)projPixel)->clonepx) = IMB_blend_color( *((unsigned int *)rgba_smear), *((unsigned int *)rgba_ub), (int)(alpha*255), blend);
BLI_linklist_prepend_arena( &smearPixels_float, (void *)projPixel, smearArena );
} else {
*((unsigned int *) &((ProjectPixelClone *)projPixel)->clonepx) = IMB_blend_color( *((unsigned int *)projPixel->pixel), *((unsigned int *)rgba_ub), (int)(alpha*255), blend);
BLI_linklist_prepend_arena( &smearPixels, (void *)projPixel, smearArena );
}
}
break;
default:
brush_sample_tex(ps->brush, projPixel->projCo2D, rgba);
alpha = rgba[3]*brush_sample_falloff(ps->brush, dist);
if (alpha > 0.0) {
if (is_floatbuf) {
rgba_fp[0] = rgba[0] * ps->brush->rgb[0];
rgba_fp[1] = rgba[1] * ps->brush->rgb[1];
rgba_fp[2] = rgba[2] * ps->brush->rgb[2];
rgba_fp[3] = rgba[3];
IMB_blend_color_float( (float *)projPixel->pixel, (float *)projPixel->pixel, rgba_fp, alpha, blend);
} else {
rgba_ub[0] = FTOCHAR(rgba[0] * ps->brush->rgb[0]);
rgba_ub[1] = FTOCHAR(rgba[1] * ps->brush->rgb[1]);
rgba_ub[2] = FTOCHAR(rgba[2] * ps->brush->rgb[2]);
rgba_ub[3] = FTOCHAR(rgba[3]);
*((unsigned int *)projPixel->pixel) = IMB_blend_color( *((unsigned int *)projPixel->pixel), *((unsigned int *)rgba_ub), (int)(alpha*255), blend);
}
}
break;
node = node->next;
} while (node);
}
/* done painting */
}
}
node = node->next;
} while (node);
}
}
@ -2928,20 +3057,109 @@ static int imapaint_paint_sub_stroke_project(ProjectPaintState *ps, BrushPainter
BLI_memarena_free(smearArena);
}
/* Loop over all images on this mesh and update any we have touched */
for (a=0; a < ps->projectImageTotal; a++) {
Image *ima = ps->projectImages[a];
if (ima->id.flag & LIB_DOIT) {
imapaint_image_update(ima, BKE_image_get_ibuf(ima, NULL), 1 /*texpaint*/ );
redraw = 1;
ima->id.flag &= ~LIB_DOIT; /* clear for reuse */
}
if (ps->thread_tot < 2) { /* only run this if we have no threads */
redraw = imapaint_refresh_tagged(ps);
}
return redraw;
}
typedef struct ProjectHandle {
/* args */
ProjectPaintState *ps;
BrushPainter *painter;
short prevmval[2];
short mval[2];
double time;
float pressure;
/* thread settings */
int thread_tot;
int thread_index;
int ready;
} ProjectHandle;
static void *do_projectpaint_thread(void *ph_v)
{
ProjectHandle *ph= ph_v;
imapaint_paint_sub_stroke_project(
ph->ps,
ph->painter,
ph->prevmval,
ph->mval,
ph->time,
ph->pressure,
ph->thread_index
);
ph->ready= 1;
return NULL;
}
static int imapaint_paint_sub_stroke_project_mt(ProjectPaintState *ps, BrushPainter *painter, short prevmval[2], short mval[2], double time, float pressure)
{
ProjectHandle handles[BLENDER_MAX_THREADS];
ListBase threads;
int a;
float mval_f[2];
mval_f[0] = mval[0]; mval_f[1] = mval[1];
if (bucket_iter_init(ps, mval_f)==0)
return 0;
#ifdef PROJ_DEBUG_PRINT_THREADS
printf("Begin Threads %d\n", ps->thread_tot);
#endif
BLI_init_threads(&threads, do_projectpaint_thread, ps->thread_tot);
/* get the threads running */
for(a=0; a < ps->thread_tot; a++) {
#ifdef PROJ_DEBUG_PRINT_THREADS
printf("INIT THREAD %d\n", a);
#endif
/* set defaults in handles */
//memset(&handles[a], 0, sizeof(BakeShade));
handles[a].ps = ps;
handles[a].painter = painter;
VECCOPY2D(handles[a].prevmval, prevmval);
VECCOPY2D(handles[a].mval, mval);
handles[a].time = time;
handles[a].pressure = pressure;
/* thread spesific */
handles[a].thread_index = a;
handles[a].ready = 0;
BLI_insert_thread(&threads, &handles[a]);
}
/* wait for everything to be done */
a= 0;
while(a != ps->thread_tot) {
PIL_sleep_ms(1);
for(a=0; a < ps->thread_tot; a++)
if(handles[a].ready==0)
break;
}
BLI_end_threads(&threads);
return imapaint_refresh_tagged(ps);
}
static void imapaint_paint_stroke(ImagePaintState *s, BrushPainter *painter, short texpaint, short *prevmval, short *mval, double time, float pressure)
{
Image *newimage = NULL;
@ -3027,8 +3245,14 @@ static void imapaint_paint_stroke_project(ProjectPaintState *ps, BrushPainter *p
int redraw_flag = 0;
/* TODO - support more brush operations, airbrush etc */
{
redraw_flag |= imapaint_paint_sub_stroke_project(ps, painter, prevmval, mval, time, 1, pressure);
if (ps->thread_tot > 1) {
redraw_flag |= imapaint_paint_sub_stroke_project_mt(ps, painter, prevmval, mval, time, pressure);
} else {
float mval_f[2];
mval_f[0] = mval[0]; mval_f[1] = mval[1];
if (bucket_iter_init(ps, mval_f)) {
redraw_flag |= imapaint_paint_sub_stroke_project(ps, painter, prevmval, mval, time, pressure, 0); /* no threads */
}
}
if (redraw && redraw_flag) {
@ -3044,7 +3268,6 @@ static int imapaint_paint_gp_to_stroke(float **points_gp) {
bGPDstroke *gps;
tGPspoint *pt;
int stroke_gp = 0;
int index_gp = 0;
int tot_gp = 0;
float *vec_gp;
@ -3140,10 +3363,12 @@ void imagepaint_paint(short mousebutton, short texpaint)
return;
}
/* TODO - add UI */
/* TODO - grease pencil stroke is verry basic now and only useful for benchmarking, should make this nicer */
// stroke_gp = 1;
/*
if (G.rt==123) {
stroke_gp = 1;
}
}*/
/* initialize state */
memset(&s, 0, sizeof(s));