Hello to all.
I have been porting my single threaded Ray-Tracer (graphics rendering algorithm) to a MT version (2 threads).
First I decided to do the most obvious thing. To split the screen in two, the upper half to one thread and the lower to the other. I started to render a couple of example scenes to see if everything was ok. And in the first cases it was. Then I send the test version that has a plane to render. And this was what happened:
https://postimg.cc/image/erm55m6ff/
Every time I execute the rendering of the scene, the artifacts change place. If I send it to print the value of some variables in some screen's pixels, I can see there is a repetition. For instance this was printed on the console:
BOTO x: 403 y 405 thread_id: 1
BOTO x: 403 y 405 thread_id: 1
BOTO x: 403 y 405 thread_id: 1
BOTO x: 403 y 405 thread_id: 1 |
Obviously, this should be printed only once. However, running the test again, it was printed only once, as it should. Nonetheless, it happened in other pixels, that did not exhibited this behavior before.
I changed the code as to allow only one of the threads to render its half to see if the problem persisted. And this is what was generated:
https://postimg.cc/image/43ie75ma3/
https://postimg.cc/image/m6bgyg2q3/
As can be seen, no problem at all.
Then I decided to change the algorithm. Went to a per pixel multithreading version, with thread 0 doing pixels 0,2,4,6... and the other the pixels 1,3,5,7...
And this is what I obtained:
https://postimg.cc/image/wk7pdvdhn/
Very similar artifacts as before. Once more, I send one thread each to do its work. The result were:
https://postimg.cc/image/o9h0uhx6f/
https://postimg.cc/image/pbr7d3svb/
And, once more, individually they were doing their work just fine, as can be seen from the black rows created by the absence of the other thread rendering its pixels (may be necessary to click on the button "download original image" to understand better)
Then my last idea was to stop using a shared memory (I was creating a single instance of the class "RayTracer", where it is dynamically allocated memory to store the pixels values until both threads are done and a file can be written with the values). Instead I created two dynamically allocated areas outside the object and passed pointers to the ray-tracing function of each thread (memory area 0 to thread0, memory 1 to thread 1)
And this is what I got:
https://postimg.cc/image/q53t5c92f/
The same problem as always.
I don't know what else to do. I don't know if a problem with algorithm (I don't think so, tried two already), with memory access (I also do not believe in that. Even when a global memory area that belonged to a single object was used, neither thread ever wrote to a position that the other thread wrote. Or they keep interchanging positions on the array, or each wrote the first or last half of it. Plus using two different areas, the problem persisted). Perhaps a bug with Pthread library? I don't know. So I would appreciate any kind of assitance.
P.S: I would like to mention again that the problems only happen with plane and occasionally with a big triangle (that covers a huge part of the window). Spheres and small triangles never shows any artifacts
I will post some MT related code:
P.S: Feel free to ask for more. I have a gitlab repository in case anyone is interested.
on main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
.......
pthread_t threads [2];
thread_data td_array [2];
void *status;
......
RayTracer rt (image.getSize(),true,n_threads);
int rc;
for (int i=0; i<2; i++) {
//cout << "main() : creating thread, " << i << endl;
td_array[i].thread_id=i;
td_array[i].rt_ptr = &rt;
td_array[i].img_ptr = ℑ
td_array[i].scene_ptr = &scene;
td_array[i].memory_pool[i] = memory_area[i]; //for passing correct external independent memory to the proper thread
//cout << "td_array.thread_index: " << td_array[i].thread_id << endl;
rc = pthread_create (&threads[i], NULL, RayTracer::run_thread, &td_array[i]);
}
if (rc) {
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
for (int i=0; i<2; i++ ) {
rc = pthread_join(threads[i], &status);
if (rc) {
cout << "Error:unable to join," << rc << endl;
exit(-1);
}
}
//tgaManager.writeImage (rt,image.getSize()); // Write file from shared memory content
for (int i=0; i<image.getSize(); i+=3) { // write file from independent memory pools
tgaManager.file_writer.put (*(memory_area[0] +i));
tgaManager.file_writer.put (*(memory_area[0] + (i+1)));
tgaManager.file_writer.put (*(memory_area[0] + (i+2)));
tgaManager.file_writer.put (*(memory_area[1] + i));
tgaManager.file_writer.put (*(memory_area[1] + (i+1)));
tgaManager.file_writer.put (*(memory_area[1] + (i+2)));
}
tgaManager.closeFile(1);
rt.deleteImgPtr (true);
delete memory_area[0], memory_area[1];
| |
on RayTracer.h
1 2 3 4 5 6 7 8 9 10 11 12
|
static void* run_thread (void* ptr) {
cout << "run_thread..." << endl;
void *nullptr;
thread_data* td = static_cast <thread_data*> (ptr);
if (td->thread_id==0) // for passing external memory pool n°0 to thread 0
td->rt_ptr->draw_multithread2 (td->img_ptr,td->scene_ptr,td->thread_id,td->memory_pool[0]);
else // for passing external memory pool n°1 to thread 1
td->rt_ptr->draw_multithread2 (td->img_ptr,td->scene_ptr,td->thread_id,td->memory_pool[1]);
//td->rt_ptr->draw (td->img_ptr,td->scene_ptr);
return nullptr;
cout << "" << endl;
}
| |
on RayTracer.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
void RayTracer::draw_multithread2 (Image *image, Scene *scene, int thread_id, unsigned char * memory_pool_number) {
.....
int ctr=0; //shared memory counters
int counter=0; //external memory areas counter
int initial_x=0; // thread 0 starts from pixel 0
if (thread_id==1) {
initial_x=1; //thread 1 starts from pixel 1
ctr=3; // thread 0 will write positions 0,1,2 (RGB) of shared memory area of a ray-tracer instance, so thread 1 will start from 3, and write 3,4,5.
}
//if (thread_id==0)
//return;
for (int y=0; y<image->getHeight(); y++) {
py = py + ystep;
for (int x=initial_x; x<image->getWidth(); x+=2) {//intercalate between pixels
memory_pool_number [counter] = min (blue*255.0f,255.0f);// writes pixel content on the external memory area that I mentioned
memory_pool_number [counter+1] = min (green*255.0f,255.0f);
memory_pool_number [counter+2] = min (red*255.0f,255.0f);
a [ctr] = min (blue*255.0f,255.0f); //shared memory area write
a [ctr+1] = min (green*255.0f,255.0f);
a [ctr+2] = min (red*255.0f,255.0f);
ctr+=6;// so thread 0 goes from 0,1,2 to 6,7,8....
counter+=3; //independent memories, so will write positions 0,1,2 for 1 pixel, then 3,4,5 for pixel 2, etc
}
}
pthread_exit (NULL);
}
| |