четверг, 19 июня 2025 г.

OpenCL: добавление поддержки видеокарт AMD в CMakeLists.txt

 Вот этот пример мы модифицируем так, чтобы он мог компилироваться для AMD видеокарт тоже. CMakeLists.txt получился вот такой:

  1. #
  2. cmake_minimum_required (VERSION 3.8)
  3. project ("MemakePrj")
  4. # Add source to this project's executable.
  5. add_executable (MemakePrj "main.cpp" "Memake/Memake.cpp" "Memake/Vector2d.cpp")
  6. # SDL2 headers
  7. target_include_directories(MemakePrj PRIVATE "SDL2-2.0.14/include")
  8. # gpu vendor: amd, nvidia
  9. set(gpu_vendor "amd")
  10. #
  11. if("${gpu_vendor}" STREQUAL "amd")
  12. message("gpu_vendor is 'amd'")
  13. # set define
  14. target_compile_definitions(MemakePrj PUBLIC GPU_VENDOR_IS_AMD)
  15. # OpenCL library
  16. set(opencl_lib_folder "$ENV{OCL_ROOT}/lib")
  17. # OpenCL headers
  18. target_include_directories(MemakePrj PRIVATE "$ENV{OCL_ROOT}/include")
  19. elseif("${gpu_vendor}" STREQUAL "nvidia")
  20. message("gpu_vendor is 'nvidia'")
  21. # set define
  22. target_compile_definitions(MemakePrj PUBLIC GPU_VENDOR_IS_NVIDIA)
  23. # OpencCL headers
  24. target_include_directories(MemakePrj PRIVATE "$ENV{CUDA_PATH}/include")
  25. # OpenCL library
  26. set(opencl_lib_folder "$ENV{CUDA_PATH}/lib")
  27. else()
  28. message("gpu_vendor is 'UNKNOWN'")
  29. endif()

  30. # add SDL_MAIN_HANDLED definition to avoid
  31. # "LNK2019 unresolved external symbol SDL_main referenced in function main_getcmdline"
  32. add_definitions( -DSDL_MAIN_HANDLED )
  33. # SDL library folder
  34. set(SDL2_lib_folder "${PROJECT_SOURCE_DIR}/SDL2-2.0.14/lib")
  35. message(${CMAKE_BUILD_TYPE})
  36. # check 32 or 64 bits
  37. if(CMAKE_SIZEOF_VOID_P EQUAL 8)
  38. # 64 bits
  39. set(SDL2_lib_folder "${SDL2_lib_folder}/x64")
  40. if("${gpu_vendor}" STREQUAL "amd")
  41. set(opencl_lib_folder "${opencl_lib_folder}")
  42. elseif("${gpu_vendor}" STREQUAL "nvidia")
  43. set(opencl_lib_folder "${opencl_lib_folder}/x64")
  44. else()
  45. message("gpu_vendor is 'UNKNOWN'")
  46. endif()
  47. elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
  48. # 32 bits
  49. set(SDL2_lib_folder "${SDL2_lib_folder}/x86")
  50. if("${gpu_vendor}" STREQUAL "amd")
  51. message("do not want to support 32-bit version for amd")
  52. elseif("${gpu_vendor}" STREQUAL "nvidia")
  53. set(opencl_lib_folder "${opencl_lib_folder}/Win32")
  54. else()
  55. message("gpu_vendor is 'UNKNOWN'")
  56. endif()
  57. endif()
  58. # link SDL2 static lib
  59. target_link_libraries(MemakePrj ${SDL2_lib_folder}/SDL2.lib)
  60. target_link_libraries(MemakePrj ${SDL2_lib_folder}/SDL2main.lib)
  61. # link OpenCL library
  62. target_link_libraries(MemakePrj ${opencl_lib_folder}/OpenCL.lib)
  63. # copy dynamic lib to folder with executable file
  64. file(COPY ${SDL2_lib_folder}/SDL2.dll DESTINATION ${PROJECT_BINARY_DIR})

 Получилось гораздо более громоздко и неудобочитаемо, чем было. В строке 12 мы указываем, какой у нас производитель видеокарты (пока что их два: AMD, Nvidia) IF внутри IF - это не то, что способствует облегчению чтения подобного кода, но это, тем не менее, позволяет собрать проект для двух и более производителей видеокарт, а это главное. Позже подумаю, как правильно разбить CMakeLists.txt на части.

В коде же это выглядит так:



вторник, 17 июня 2025 г.

Настройка OpenCL для видеокарт AMD

Прежде, чем начать, отмечу, что компания AMD на данный момент не поддерживает свой OpenCL SDK, к сожалению. Причины мне непонятны, хотя они предлагают вместо него какой-то свой собственный аналог CUDA у NVidia. Последним, к слову, наличие CUDA никак не помешало поддерживать тот же OpenCL. Такое отношение к OpenCL не может радовать, конечно же. Тот набор инструментов, что используется ниже - он поддерживается энтузиастами (за что им огромное спасибо). 

Скачать SDK можно, например, тут. После установки, установим переменную окружения OCL_ROOT и проверим, что она правильно установлена: 


Далее все делаем так же, как мы делали раньше и в тех же местах.



Тут настройка среды закончена и для запуска примера осталось только чуть подредактировать код, вместо заголовка cl.hpp мы возьмем cl2.hpp и добавим определение CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY перед заголовочным файлом для совместимости со старым кодом:


После этого можно запустить пример и убедиться, что он работает:


Заодно проверим как работает перемножение матриц в наивной реализации для CPU и GPU:


Запускалалось на Ryzen 7 7730U with Vega 8 iGPU

Заодно сравним производительность ноутбучного процессора и встроенной видеокарты с результатами настольного ПК:





четверг, 7 марта 2024 г.

2д коллайдер: добавление пользовательских препятствий / 2D collider: add custom obstacles

 В предыдущих постах (раз, два, три) у нас шарики сталкивались друг с другом и отражались от границ, но теперь я решил добавить дополнительные препятствия в виде линий, от которых шарики будут отскакивать так же, как от границ - по "оптическому" закону. И если с вертикальными и горизонтальными границами все довольно просто - проверяешь не вышел ли обьект за границу x либо y компоненты и меняешь соответствующую компоненту скорости на противоположную (т.е. умножаешь на -1). А например в случае отражения шариков друг от друга мы просто сверяем расстояния между центрами шариков S с суммой их радиусов R1 + R2 и если S <= R1 + R2 то уже идет расчет новых векторов движения. Но в случае столкновения с протяженным обьектом все будет несколько иначе. Сначала надо представить центр шарика и два конца отрезка в виде треугольника:

p1 и p2 - это точки отрезка, точка A - это центр шарика. Имея все три точки, мы можем вычислить все углы треугольника и также его высоту h, используя вот такие формулы:
h = sin(p1) * (p1A)

Последняя формула - вычисление высоты (противолежащего катета) через синус угла и гипотенузу. Хотя у меня в моем маленьком векторном "фреймворке" есть операция определения угла между двумя векторами, но по причинам, в которых мне лень сейчас разбираться, она работает только для углов +-0.5 * Пи, а вот по формулам, приведенным выше,  она работает и для тупых углов тоже (это важно). Но так же важно знать, что это довольно много вычислений и что нет смысла для каждого шарика (которых тысячи) каждый раз делать все эти вычисления сначала длины сторон, потом углов, потом высоты треугольника - это дорого. Сначала мы определим, что находимся ли мы где-то в рядом с отрезком и если да, то вот тогда уже вычисляем расстояние до него. Причем важно учесть, что в случае тупого угла у основания треугольника, надо брать не расстояние h, а расстояние p2A (расстояние от центра шарика до ближайшего конца отрезка). В общем, код выглядит так:

  1. void checkCollision(const BorderLine& bl)
  2. {
  3. // rectangle around line
  4. Point2f xyMin = bl.getXYMin();
  5. xyMin = {xyMin.x - r, xyMin.y - r};
  6. Point2f xyMax = bl.getXYMax();
  7. xyMax = { xyMax.x + r, xyMax.y + r };
  8. if (pos.x >= xyMin.x && pos.y >= xyMin.y && pos.x <= xyMax.x && pos.y <= xyMax.y)
  9. {
  10. Point2f a = pos;
  11. // sides of triangle
  12. float p1a = bl.p1.distanceTo(a);
  13. float p2a = bl.p2.distanceTo(a);
  14. float p1p2 = bl.p1.distanceTo(bl.p2);
  15. // angles
  16. float ang1 = acos((p1a * p1a + p1p2 * p1p2 - p2a * p2a) / (2 * p1a * p1p2));
  17. float ang2 = acos((p2a * p2a + p1p2 * p1p2 - p1a * p1a) / (2 * p2a * p1p2));
  18. // if both angles are sharp then calculate h (distanse from a to line)
  19. if (ang1 <= k_PI / 2 && ang2 <= k_PI / 2)
  20. {
  21. float h = sin(ang1) * p1a;
  22. if (h <= r)
  23. {
  24. // calculate contact point
  25. float cath1 = cos(ang1) * p1a;
  26. Point2f v = (bl.p2 - bl.p1).unitVector() * cath1;
  27. Point2f contactPoint = bl.p1 + v;
  28. opticCollPoint(contactPoint);
  29. }
  30. }
  31. else if (p1a <= r)
  32. {
  33. opticCollPoint(bl.p1); // first line end
  34. }
  35. else if (p2a <= r)
  36. {
  37. opticCollPoint(bl.p2); // second line end
  38. }
  39. }
  40. }
В строках 4-8 мы задаем прямоугольник, который охватывает нашу линию и проверяем, входит ли шарик в границы этого четырехугольника. Если да, то вычисляем стороны треугольника (строки 12-14) и углы у его основания (строки 16-17), причем основанием у нас является наш отрезок. Если оба угла острые, то вычисляем высоту треугольника (расстояние от центра шарика до отрезка) в строке 21. Если высота равна или меньше радиуса шарика - у нас коллизия! И тогда мы вычисляем точку контакта путем прибавления к одному из концов отрезка единичного вектора, умноженного на значение прилежащего катета (строки 25-27) и обрабатываем столкновение. Если же один из углов тупой, то смотрим, соприкасается ли шарик с одним из концов отрезка и если да, то обрабатываем столкновение (строки 31-37). Вот код этой функции:

  1. void opticCollPoint(const Point2f contactPoint)
  2. {
  3. // direction to other contact point
  4. Point2f toContPoint = contactPoint - pos;
  5. // calculate dot product
  6. float dotProd = f.dotProduct(toContPoint);
  7. // if dot product is negative then force directed away
  8. // from contact point and we do nothing
  9. if (dotProd > 0)
  10. {
  11. // angle between normal and force (moving) vectors
  12. float angle = f.angleTo(toContPoint);
  13. // the angle of incidence is equal to the angle of reflection
  14. f = Mat2x2f().rot(angle * 2) * f;
  15. f = f * (-1.f);
  16. }
  17. }
Столкновение обрабатывается по "оптическому" принципу "угол падения равен углу отражения". На самом деле именно в эту оптическую формулу и вырождается тот же испульсный способ, если один из обьектов неповижен и имеет бесконечную (или на порядки большую) по сравнению со вторым обьектом массу.
Аналогичніе функции в для OpenCL:

  1. Ball opticCollPoint(Ball b, float2 contactPoint)
  2. {
  3. float2 pos = (float2)(b.pos.x, b.pos.y);
  4. // direction to other contact point
  5. float2 toContPoint = contactPoint - pos;
  6. // calculate dot product
  7. float2 f = (float2)(b.f.x, b.f.y);
  8. float dotProd = dot(f, toContPoint);
  9. // if dot product is negative then force directed away
  10. // from contact point and we do nothing
  11. if (dotProd > 0)
  12. {
  13. // angle between normal and force (moving) vectors
  14. float angle = getAngleTo(f, toContPoint);
  15. // the angle of incidence is equal to the angle of reflection
  16. f = rotVect(f, angle * 2);
  17. f = f * (-1);
  18. b.f.x = f.x;
  19. b.f.y = f.y;
  20. }
  21. return b;
  22. }
  23.  
  24. Ball checkCollisionBL(Ball b, BorderLine bl)
  25. {
  26. // rectangle around line
  27. Point2f xyMin = getBLXYMin(bl);
  28. xyMin.x = xyMin.x - b.r;
  29. xyMin.y = xyMin.y - b.r;
  30. Point2f xyMax = getBLXYMax(bl);
  31. xyMax.x = xyMax.x + b.r;
  32. xyMax.y = xyMax.y + b.r;
  33. if (b.pos.x >= xyMin.x && b.pos.y >= xyMin.y && b.pos.x <= xyMax.x && b.pos.y <= xyMax.y)
  34. {
  35. float2 a = (float2)(b.pos.x, b.pos.y);
  36. // sides of triangle
  37. float2 blp1 = (float2)(bl.p1.x, bl.p1.y);
  38. float2 blp2 = (float2)(bl.p2.x, bl.p2.y);
  39. float p1a = getDistanceBetween(blp1, a);
  40. float p2a = getDistanceBetween(blp2, a);
  41. float p1p2 = getDistanceBetween(blp1, blp2);
  42. // angles
  43. float ang1 = acos((p1a * p1a + p1p2 * p1p2 - p2a * p2a) / (2 * p1a * p1p2));
  44. float ang2 = acos((p2a * p2a + p1p2 * p1p2 - p1a * p1a) / (2 * p2a * p1p2));
  45. // if both angles are sharp then calculate h (distanse from a to line)
  46. if (ang1 <= M_PI_2_F && ang2 <= M_PI_2_F)
  47. {
  48. float h = sin(ang1) * p1a;
  49. if (h <= b.r)
  50. {
  51. // calculate contact point
  52. float cath1 = cos(ang1) * p1a;
  53. float2 v = (blp2 - blp1);
  54. v = getUnitVector(v) * cath1;
  55. float2 contactPoint = blp1 + v;
  56. b = opticCollPoint(b, contactPoint);
  57. }
  58. }
  59. else if (p1a <= b.r) // first line end
  60. {
  61. b = opticCollPoint(b, blp1);
  62. }
  63. else if (p2a <= b.r) // second line end
  64. {
  65. b = opticCollPoint(b, blp2);
  66. }
  67. }
  68. return b;
  69. }
Пример работы:


Весь код находится здесь.