Advertisement
Guest User

Untitled

a guest
Mar 26th, 2019
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.60 KB | None | 0 0
  1. #include <CL/cl.h>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <iostream>
  5. #include <string>
  6. #include <fstream>
  7. #include <time.h>
  8.  
  9. using namespace std;
  10.  
  11. const char *getCLErrorString(cl_int err);
  12. bool checkErrors(cl_int err, const char *message);
  13. bool checkErrors(cl_int err, const string &message);
  14. void getPlatformIDs(cl_uint *numPlatforms, cl_platform_id **platforms);
  15. void deletePlatformIDs(cl_platform_id *platforms);
  16. void getGPUDeviceIDs(cl_uint numPlatforms, cl_platform_id *platforms, cl_uint **numDevices, cl_device_id ***devices);
  17. void getGPUDeviceIDsForPlatform(cl_platform_id platform, cl_uint *numDevices, cl_device_id **devices);
  18. void deleteGPUDeviceIDs(cl_uint numPlatforms, cl_uint *numDevices, cl_device_id **devices);
  19. void printDeviceInfo(cl_device_id device);
  20. bool createContext(const cl_device_id *device, cl_context *context);
  21. bool createCommandQueue(cl_context context, cl_device_id device, cl_command_queue *commandQueue);
  22. void printProgramBuildInfo(cl_program program, cl_device_id device);
  23. bool createAndBuildProgram(const char *filename, cl_device_id device, cl_context context, cl_program *program);
  24. bool createKernel(cl_program program, const char *kernelName, cl_kernel *kernel);
  25.  
  26. int main(int argc, char **argv)
  27. {
  28. cl_uint num_platforms = 0;
  29. cl_platform_id *platforms = NULL;
  30. cl_uint *num_devices = NULL;
  31. cl_device_id **devices = NULL;
  32.  
  33. getPlatformIDs(&num_platforms, &platforms);
  34. getGPUDeviceIDs(num_platforms, platforms, &num_devices, &devices);
  35.  
  36. int use_platform = 0;
  37. int use_device = 0;
  38. cl_platform_id platform = platforms[use_platform];
  39. cl_device_id device = devices[use_platform][use_device];
  40. cl_context context = 0;
  41. cl_command_queue command_queue = 0;
  42.  
  43. if (!createContext(&device, &context))
  44. {
  45. deleteGPUDeviceIDs(num_platforms, num_devices, devices);
  46. deletePlatformIDs(platforms);
  47. exit(EXIT_FAILURE);
  48. }
  49.  
  50. if (!createCommandQueue(context, device, &command_queue))
  51. {
  52. clReleaseContext(context);
  53. deleteGPUDeviceIDs(num_platforms, num_devices, devices);
  54. deletePlatformIDs(platforms);
  55. exit(EXIT_FAILURE);
  56. }
  57.  
  58. cl_program program1 = 0;
  59. cl_program program2 = 0;
  60. cl_program program3 = 0;
  61. cl_kernel kernel1 = 0;
  62. cl_kernel kernel2 = 0;
  63. cl_kernel kernel3 = 0;
  64. createAndBuildProgram("addVec.cl", device, context, &program1);
  65. createAndBuildProgram("subVec.cl", device, context, &program2);
  66. createAndBuildProgram("multVec.cl", device, context, &program3);
  67. createKernel(program1, "addVec", &kernel1);
  68. createKernel(program2, "subVec", &kernel2);
  69. createKernel(program3, "multVec", &kernel3);
  70.  
  71. // ::TODO::
  72.  
  73.  
  74.  
  75. cl_int err = 0;
  76. cl_uint size = 1000000;
  77. size_t bytes = size*sizeof(int);
  78. size_t global_work_size = size;
  79.  
  80.  
  81. float *tab1 = (float*)malloc(bytes);
  82. float *tab2 = (float*)malloc(bytes);
  83. float *tab3 = (float*)malloc(bytes);
  84.  
  85.  
  86. clock_t t1 = clock();
  87.  
  88. for (int i = 0; i < size; i++){
  89. tab1[i] = rand() % 21 - 10;
  90. tab2[i] = rand() % 21 - 10;
  91. tab3[i] = rand() % 21 - 10;
  92. }
  93.  
  94. for (int i = 0; i < size; i++){
  95. tab3[i] = tab1[i] + tab2[i];
  96. }
  97.  
  98. float czasCPURazem = (((float)clock() - (float)t1)) / CLOCKS_PER_SEC;
  99. printf("Czas CPU razem: %f\n", czasCPURazem);
  100.  
  101. cl_mem d_vec1 = clCreateBuffer(context, CL_MEM_READ_WRITE,
  102. bytes, NULL, &err);
  103. cl_mem d_vec2 = clCreateBuffer(context, CL_MEM_READ_WRITE,
  104. bytes, NULL, &err);
  105. cl_mem d_vec3 = clCreateBuffer(context, CL_MEM_READ_WRITE,
  106. bytes, NULL, &err);
  107.  
  108. clock_t t2 = clock();
  109. clEnqueueWriteBuffer(command_queue, d_vec1, CL_TRUE, 0, sizeof(float) * size, tab1, 0, NULL, NULL);
  110. clFinish(command_queue);
  111. clEnqueueWriteBuffer(command_queue, d_vec2, CL_TRUE, 0, sizeof(float) * size, tab2, 0, NULL, NULL);
  112. clFinish(command_queue);
  113. clEnqueueWriteBuffer(command_queue, d_vec3, CL_TRUE, 0, sizeof(float) * size, tab3, 0, NULL, NULL);
  114. clFinish(command_queue);
  115. float czasKopiowania = (((float)clock() - (float)t2)) / CLOCKS_PER_SEC;
  116. printf("Czas kopiowania: %f\n", czasKopiowania);
  117.  
  118.  
  119. // Check for errors.
  120. err = clSetKernelArg(kernel1, 0, sizeof(cl_mem), &d_vec1);
  121. err |= clSetKernelArg(kernel1, 1, sizeof(cl_mem), &d_vec2);
  122. err |= clSetKernelArg(kernel1, 2, sizeof(cl_mem), &d_vec3);
  123. err |= clSetKernelArg(kernel1, 3, sizeof(cl_uint), &size);
  124. // Check for errors.
  125. clock_t t3 = clock();
  126. err = clEnqueueNDRangeKernel(command_queue, kernel1, 1, NULL,
  127. &global_work_size, NULL, 0, NULL, NULL);
  128. err = clFinish(command_queue);
  129.  
  130. float czasWykonania = (((float)clock() - (float)t3)) / CLOCKS_PER_SEC;
  131. printf("Czas wykonania: %f\n", czasWykonania);
  132. // Check for errors.
  133.  
  134. float czasRazem = czasKopiowania + czasWykonania;
  135. printf("Czas GPU razem: %f\n", czasRazem);
  136.  
  137. clEnqueueReadBuffer(command_queue, d_vec3, CL_TRUE, 0, sizeof(float) * size, tab3, 0, NULL, NULL);
  138. clFinish(command_queue);
  139. for (int i = 0; i < size; i++){
  140. //printf("%f, ", tab3[i]);
  141. }
  142. printf("\n");
  143.  
  144. ///////////
  145. free(tab1);
  146. free(tab2);
  147. free(tab3);
  148. clReleaseMemObject(d_vec1);
  149. clReleaseKernel(kernel1);
  150. clReleaseProgram(program1);
  151. clReleaseMemObject(d_vec2);
  152. clReleaseKernel(kernel2);
  153. clReleaseProgram(program2);
  154. clReleaseMemObject(d_vec3);
  155. clReleaseKernel(kernel3);
  156. clReleaseProgram(program3);
  157. clReleaseCommandQueue(command_queue);
  158. clReleaseContext(context);
  159. deleteGPUDeviceIDs(num_platforms, num_devices, devices);
  160. deletePlatformIDs(platforms);
  161. system("pause");
  162. return 0;
  163. }
  164.  
  165. const char *getCLErrorString(cl_int err)
  166. {
  167. switch (err)
  168. {
  169. case CL_SUCCESS: return "CL_SUCCESS";
  170. case CL_DEVICE_NOT_FOUND: return "CL_DEVICE_NOT_FOUND";
  171. case CL_DEVICE_NOT_AVAILABLE: return "CL_DEVICE_NOT_AVAILABLE";
  172. case CL_COMPILER_NOT_AVAILABLE: return "CL_COMPILER_NOT_AVAILABLE";
  173. case CL_MEM_OBJECT_ALLOCATION_FAILURE: return "CL_MEM_OBJECT_ALLOCATION_FAILURE";
  174. case CL_OUT_OF_RESOURCES: return "CL_OUT_OF_RESOURCES";
  175. case CL_OUT_OF_HOST_MEMORY: return "CL_OUT_OF_HOST_MEMORY";
  176. case CL_PROFILING_INFO_NOT_AVAILABLE: return "CL_PROFILING_INFO_NOT_AVAILABLE";
  177. case CL_MEM_COPY_OVERLAP: return "CL_MEM_COPY_OVERLAP";
  178. case CL_IMAGE_FORMAT_MISMATCH: return "CL_IMAGE_FORMAT_MISMATCH";
  179. case CL_IMAGE_FORMAT_NOT_SUPPORTED: return "CL_IMAGE_FORMAT_NOT_SUPPORTED";
  180. case CL_BUILD_PROGRAM_FAILURE: return "CL_BUILD_PROGRAM_FAILURE";
  181. case CL_MAP_FAILURE: return "CL_MAP_FAILURE";
  182. case CL_MISALIGNED_SUB_BUFFER_OFFSET: return "CL_MISALIGNED_SUB_BUFFER_OFFSET";
  183. case CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST: return "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST";
  184. case CL_INVALID_VALUE: return "CL_INVALID_VALUE";
  185. case CL_INVALID_DEVICE_TYPE: return "CL_INVALID_DEVICE_TYPE";
  186. case CL_INVALID_PLATFORM: return "CL_INVALID_PLATFORM";
  187. case CL_INVALID_DEVICE: return "CL_INVALID_DEVICE";
  188. case CL_INVALID_CONTEXT: return "CL_INVALID_CONTEXT";
  189. case CL_INVALID_QUEUE_PROPERTIES: return "CL_INVALID_QUEUE_PROPERTIES";
  190. case CL_INVALID_COMMAND_QUEUE: return "CL_INVALID_COMMAND_QUEUE";
  191. case CL_INVALID_HOST_PTR: return "CL_INVALID_HOST_PTR";
  192. case CL_INVALID_MEM_OBJECT: return "CL_INVALID_MEM_OBJECT";
  193. case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR";
  194. case CL_INVALID_IMAGE_SIZE: return "CL_INVALID_IMAGE_SIZE";
  195. case CL_INVALID_SAMPLER: return "CL_INVALID_SAMPLER";
  196. case CL_INVALID_BINARY: return "CL_INVALID_BINARY";
  197. case CL_INVALID_BUILD_OPTIONS: return "CL_INVALID_BUILD_OPTIONS";
  198. case CL_INVALID_PROGRAM: return "CL_INVALID_PROGRAM";
  199. case CL_INVALID_PROGRAM_EXECUTABLE: return "CL_INVALID_PROGRAM_EXECUTABLE";
  200. case CL_INVALID_KERNEL_NAME: return "CL_INVALID_KERNEL_NAME";
  201. case CL_INVALID_KERNEL_DEFINITION: return "CL_INVALID_KERNEL_DEFINITION";
  202. case CL_INVALID_KERNEL: return "CL_INVALID_KERNEL";
  203. case CL_INVALID_ARG_INDEX: return "CL_INVALID_ARG_INDEX";
  204. case CL_INVALID_ARG_VALUE: return "CL_INVALID_ARG_VALUE";
  205. case CL_INVALID_ARG_SIZE: return "CL_INVALID_ARG_SIZE";
  206. case CL_INVALID_KERNEL_ARGS: return "CL_INVALID_KERNEL_ARGS";
  207. case CL_INVALID_WORK_DIMENSION: return "CL_INVALID_WORK_DIMENSION";
  208. case CL_INVALID_WORK_GROUP_SIZE: return "CL_INVALID_WORK_GROUP_SIZE";
  209. case CL_INVALID_WORK_ITEM_SIZE: return "CL_INVALID_WORK_ITEM_SIZE";
  210. case CL_INVALID_GLOBAL_OFFSET: return "CL_INVALID_GLOBAL_OFFSET";
  211. case CL_INVALID_EVENT_WAIT_LIST: return "CL_INVALID_EVENT_WAIT_LIST";
  212. case CL_INVALID_EVENT: return "CL_INVALID_EVENT";
  213. case CL_INVALID_OPERATION: return "CL_INVALID_OPERATION";
  214. case CL_INVALID_GL_OBJECT: return "CL_INVALID_GL_OBJECT";
  215. case CL_INVALID_BUFFER_SIZE: return "CL_INVALID_BUFFER_SIZE";
  216. case CL_INVALID_MIP_LEVEL: return "CL_INVALID_MIP_LEVEL";
  217. case CL_INVALID_GLOBAL_WORK_SIZE: return "CL_INVALID_GLOBAL_WORK_SIZE";
  218. case CL_INVALID_PROPERTY: return "CL_INVALID_PROPERTY";
  219. default: return "Unknown OpenCL error!";
  220. }
  221. }
  222.  
  223. bool checkErrors(cl_int err, const char *message)
  224. {
  225. if (CL_SUCCESS == err)
  226. {
  227. return false;
  228. }
  229.  
  230. cerr << message << endl;
  231. cerr << "Error code: " << getCLErrorString(err) << endl;
  232. system("pause > nul");
  233. return true;
  234. }
  235.  
  236. bool checkErrors(cl_int err, const string &message)
  237. {
  238. return checkErrors(err, message.c_str());
  239. }
  240.  
  241. void getPlatformIDs(cl_uint *numPlatforms, cl_platform_id **platforms)
  242. {
  243. cl_int err;
  244.  
  245. err = clGetPlatformIDs(0, NULL, numPlatforms);
  246. if (checkErrors(err, "Error while obtaining the number of OpenCL platforms!"))
  247. {
  248. exit(EXIT_FAILURE);
  249. }
  250.  
  251. cout << "The number of available OpenCL platforms: " << *numPlatforms << endl;
  252.  
  253. *platforms = new cl_platform_id[*numPlatforms];
  254. err = clGetPlatformIDs(*numPlatforms, *platforms, NULL);
  255. if (checkErrors(err, "Error while obtaining the IDs of OpenCL platforms!"))
  256. {
  257. deletePlatformIDs(*platforms);
  258. exit(EXIT_FAILURE);
  259. }
  260.  
  261. cout << "The IDs of OpenCL platforms obtained." << endl << endl;
  262. }
  263.  
  264. void deletePlatformIDs(cl_platform_id *platforms)
  265. {
  266. delete[] platforms;
  267. }
  268.  
  269. void getGPUDeviceIDs(cl_uint numPlatforms, cl_platform_id *platforms, cl_uint **numDevices, cl_device_id ***devices)
  270. {
  271. cl_uint sum_num_devices = 0;
  272.  
  273. *numDevices = new cl_uint[numPlatforms];
  274. *devices = new cl_device_id*[numPlatforms];
  275. for (cl_uint i = 0; i < numPlatforms; ++i)
  276. {
  277. cout << ">> OpenCL platform #" << i << endl;
  278. (*numDevices)[i] = 0;
  279. (*devices)[i] = NULL;
  280. getGPUDeviceIDsForPlatform(platforms[i], *numDevices + i, *devices + i);
  281. sum_num_devices += (*numDevices)[i];
  282. for (cl_uint j = 0; j < (*numDevices)[i]; ++j)
  283. {
  284. cout << "> OpenCL GPU device #" << j << ":" << endl;
  285. printDeviceInfo((*devices)[i][j]);
  286. }
  287. cout << endl;
  288. }
  289. if (0 == sum_num_devices)
  290. {
  291. cerr << "OpenCL GPU devices were not found!" << endl;
  292. deleteGPUDeviceIDs(numPlatforms, *numDevices, *devices);
  293. deletePlatformIDs(platforms);
  294. system("pause > nul");
  295. exit(EXIT_FAILURE);
  296. }
  297. }
  298.  
  299. void getGPUDeviceIDsForPlatform(cl_platform_id platform, cl_uint *numDevices, cl_device_id **devices)
  300. {
  301. cl_int err;
  302.  
  303. err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 0, NULL, numDevices);
  304. if (checkErrors(err, "Error while obtaining the number of available OpenCL GPU devices!"))
  305. {
  306. return;
  307. }
  308.  
  309. cout << "The number of available OpenCL GPU devices: " << *numDevices << endl;
  310. if (0 == *numDevices)
  311. {
  312. return;
  313. }
  314.  
  315. *devices = new cl_device_id[*numDevices];
  316. err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, *numDevices, *devices, NULL);
  317. if (checkErrors(err, "Error while obtaining the IDs of OpenCL GPU devices!"))
  318. {
  319. delete[] * devices;
  320. *devices = NULL;
  321. *numDevices = 0;
  322. return;
  323. }
  324.  
  325. cout << "The IDs of OpenCL GPU devices obtained." << endl;
  326. }
  327.  
  328. void deleteGPUDeviceIDs(cl_uint numPlatforms, cl_uint *numDevices, cl_device_id **devices)
  329. {
  330. for (cl_uint i = 0; i < numPlatforms; ++i)
  331. {
  332. delete[] devices[i];
  333. }
  334. delete[] devices;
  335. delete[] numDevices;
  336. }
  337.  
  338. void printDeviceInfo(cl_device_id device)
  339. {
  340. cl_int err;
  341. size_t param_value_size = 0;
  342. char *buffer = NULL;
  343.  
  344. // CL_DEVICE_NAME
  345. err = clGetDeviceInfo(device, CL_DEVICE_NAME, 0, NULL, &param_value_size);
  346. if (!checkErrors(err, "Error while obtaining the size of device name string!"))
  347. {
  348. buffer = new char[param_value_size];
  349. clGetDeviceInfo(device, CL_DEVICE_NAME, param_value_size, static_cast<void *>(buffer), NULL);
  350. cout << "CL_DEVICE_NAME: " << buffer << endl;
  351. delete[] buffer;
  352. buffer = NULL;
  353. }
  354.  
  355. // CL_DEVICE_VENDOR
  356. err = clGetDeviceInfo(device, CL_DEVICE_VENDOR, 0, NULL, &param_value_size);
  357. if (!checkErrors(err, "Error while obtaining the size of vendor name string!"))
  358. {
  359. buffer = new char[param_value_size];
  360. clGetDeviceInfo(device, CL_DEVICE_VENDOR, param_value_size, static_cast<void *>(buffer), NULL);
  361. cout << "CL_DEVICE_VENDOR: " << buffer << endl;
  362. delete[] buffer;
  363. buffer = NULL;
  364. }
  365.  
  366. // CL_DEVICE_VERSION
  367. err = clGetDeviceInfo(device, CL_DEVICE_VERSION, 0, NULL, &param_value_size);
  368. if (!checkErrors(err, "Error while obtaining the size of OpenCL version string!"))
  369. {
  370. buffer = new char[param_value_size];
  371. clGetDeviceInfo(device, CL_DEVICE_VERSION, param_value_size, static_cast<void *>(buffer), NULL);
  372. cout << "CL_DEVICE_VERSION: " << buffer << endl;
  373. delete[] buffer;
  374. buffer = NULL;
  375. }
  376.  
  377. // CL_DEVICE_PROFILE
  378. err = clGetDeviceInfo(device, CL_DEVICE_PROFILE, 0, NULL, &param_value_size);
  379. if (!checkErrors(err, "Error while obtaining the size of OpenCL profile string!"))
  380. {
  381. buffer = new char[param_value_size];
  382. clGetDeviceInfo(device, CL_DEVICE_PROFILE, param_value_size, static_cast<void *>(buffer), NULL);
  383. cout << "CL_DEVICE_PROFILE: " << buffer << endl;
  384. delete[] buffer;
  385. buffer = NULL;
  386. }
  387.  
  388. // CL_DRIVER_VERSION
  389. err = clGetDeviceInfo(device, CL_DRIVER_VERSION, 0, NULL, &param_value_size);
  390. if (!checkErrors(err, "Error while obtaining the size of OpenCL software driver version string!"))
  391. {
  392. buffer = new char[param_value_size];
  393. clGetDeviceInfo(device, CL_DRIVER_VERSION, param_value_size, static_cast<void *>(buffer), NULL);
  394. cout << "CL_DRIVER_VERSION: " << buffer << endl;
  395. delete[] buffer;
  396. buffer = NULL;
  397. }
  398. }
  399.  
  400. bool createContext(const cl_device_id *device, cl_context *context)
  401. {
  402. cl_int err;
  403.  
  404. *context = clCreateContext(NULL, 1, device, NULL, NULL, &err);
  405. if (checkErrors(err, "Error while creating an OpenCL context!"))
  406. {
  407. *context = 0;
  408. return false;
  409. }
  410.  
  411. cout << "An OpenCL context created." << endl << endl;
  412. return true;
  413. }
  414.  
  415. bool createCommandQueue(cl_context context, cl_device_id device, cl_command_queue *commandQueue)
  416. {
  417. cl_int err;
  418.  
  419. *commandQueue = clCreateCommandQueue(context, device, 0, &err);
  420. if (checkErrors(err, "Error while creating a command-queue!"))
  421. {
  422. *commandQueue = 0;
  423. return false;
  424. }
  425.  
  426. cout << "A command-queue created." << endl << endl;
  427. return true;
  428. }
  429.  
  430. void printProgramBuildInfo(cl_program program, cl_device_id device)
  431. {
  432. cl_int err;
  433. size_t param_value_size = 0;
  434. char *buffer = NULL;
  435.  
  436. err = clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &param_value_size);
  437. if (!checkErrors(err, "Error while obtaining the size of build log string!"))
  438. {
  439. buffer = new char[param_value_size];
  440. clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, param_value_size, static_cast<void *>(buffer), NULL);
  441. cout << "Build log:" << endl << buffer << endl;
  442. delete[] buffer;
  443. buffer = NULL;
  444. }
  445. }
  446.  
  447. bool createAndBuildProgram(const char *filename, cl_device_id device, cl_context context, cl_program *program)
  448. {
  449. cl_int err;
  450. ifstream file_cl(filename);
  451. string source_code;
  452. string line;
  453. char *buffer = NULL;
  454.  
  455. if (!file_cl.is_open())
  456. {
  457. cerr << "Error while opening the file '" << filename << "'!" << endl;
  458. return false;
  459. }
  460.  
  461. while (file_cl.good())
  462. {
  463. getline(file_cl, line);
  464. source_code += line;
  465. }
  466. file_cl.close();
  467. buffer = new char[source_code.length() + 1];
  468. sprintf(buffer, "%s", source_code.c_str());
  469. *program = clCreateProgramWithSource(context, 1, const_cast<const char **>(&buffer), NULL, &err);
  470. delete[] buffer;
  471. buffer = NULL;
  472. if (checkErrors(err, string("Error while creating a program object for the source code '") + filename + "'!"))
  473. {
  474. *program = 0;
  475. return false;
  476. }
  477.  
  478. err = clBuildProgram(*program, 0, NULL, NULL, NULL, NULL);
  479. if (checkErrors(err, string("Error while building a program executable from the source code '") + filename + "'!"))
  480. {
  481. printProgramBuildInfo(*program, device);
  482. clReleaseProgram(*program);
  483. *program = 0;
  484. return false;
  485. }
  486.  
  487. cout << "An OpenCL program created and built from the source code '" << filename << "'." << endl << endl;
  488. return true;
  489. }
  490.  
  491. bool createKernel(cl_program program, const char *kernelName, cl_kernel *kernel)
  492. {
  493. cl_int err;
  494.  
  495. *kernel = clCreateKernel(program, kernelName, &err);
  496. if (checkErrors(err, string("Error while creating a kernel object for a function '") + kernelName + "'!"))
  497. {
  498. *kernel = 0;
  499. return false;
  500. }
  501.  
  502. cout << "A kernel object for a function '" << kernelName << "' created." << endl << endl;
  503. return true;
  504. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement