/* * Copyright (c) 2020 The Khronos Group Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include // OpenCL SDK includes #include #include // STL includes #include #include #include #include #include #include // std::make_tuple // TCLAP includes #include // OpenGL includes #include // GLM includes #include #include #include template struct DoubleBuffer { T front, back; void swap() { std::swap(front, back); } }; class NBody : public cl::sdk::InteropWindow { public: explicit NBody(unsigned int platform_id = 0, unsigned int device_id = 0, cl_bitfield device_type = CL_DEVICE_TYPE_DEFAULT) : InteropWindow{ sf::VideoMode(800, 800), "Gravitational NBody", sf::Style::Default, sf::ContextSettings{ 32, 0, 0, // Depth, Stencil, AA 3, 3, // OpenGL version sf::ContextSettings::Attribute::Core }, platform_id, device_id, device_type }, particle_count(8192), x_abs_range(192.f), y_abs_range(128.f), z_abs_range(32.f), mass_min(100.f), mass_max(500.f), RMB_pressed(false), dist(std::max({ x_abs_range, y_abs_range, z_abs_range }) * 3), phi(0), theta(0), needMatrixReset(true), animating(true) {} protected: virtual void initializeGL() override; // Function that initializes all OpenGL assets // needed to draw a scene virtual void initializeCL() override; // Function that initializes all OpenCL assets // needed to draw a scene virtual void updateScene() override; // Function that holds scene update guaranteed not // to conflict with drawing virtual void render() override; // Function that does the native rendering virtual void event(const sf::Event& e) override; // Function that handles render area resize private: // Simulation related variables std::size_t particle_count; float x_abs_range, y_abs_range, z_abs_range, mass_min, mass_max; // Host-side containers std::vector pos_mass; std::vector velocity; std::vector forces; // OpenCL objects cl::Device device; cl::CommandQueue queue; cl::Program cl_program; cl::Kernel kernel; cl::Sampler sampler; cl::Buffer velocity_buffer; DoubleBuffer cl_pos_mass; cl::vector interop_resources; cl::vector acquire_wait_list, release_wait_list; cl::NDRange gws, lws; // Global/local work-sizes cl::Kernel step_kernel; // Kernel // OpenGL objects cl_GLuint vertex_shader, fragment_shader, gl_program; DoubleBuffer vertex_array; DoubleBuffer gl_pos_mass; bool RMB_pressed; // Variables to enable dragging sf::Vector2 mousePos; // Variables to enable dragging float dist, phi, theta; // Mouse polar coordinates bool needMatrixReset; // Whether matrices need to be reset in shaders bool animating; void mouseDrag(const sf::Event::MouseMoveEvent& event); // Handle mouse dragging void mouseWheel( const sf::Event::MouseWheelEvent& event); // Handle mouse wheel movement void setMatrices(); // Update shader matrices }; inline bool checkError(const char* Title) { int Error; if ((Error = glGetError()) != GL_NO_ERROR) { std::string ErrorString; switch (Error) { case GL_INVALID_ENUM: ErrorString = "GL_INVALID_ENUM"; break; case GL_INVALID_VALUE: ErrorString = "GL_INVALID_VALUE"; break; case GL_INVALID_OPERATION: ErrorString = "GL_INVALID_OPERATION"; break; case GL_INVALID_FRAMEBUFFER_OPERATION: ErrorString = "GL_INVALID_FRAMEBUFFER_OPERATION"; break; case GL_OUT_OF_MEMORY: ErrorString = "GL_OUT_OF_MEMORY"; break; default: ErrorString = "UNKNOWN"; break; } std::cerr << "OpenGL Error(" << ErrorString << "): " << Title << std::endl; } return Error == GL_NO_ERROR; } void NBody::initializeGL() { if (glewInit() != GLEW_OK) std::exit(EXIT_FAILURE); auto create_shader = [](std::string file_path, cl_GLenum shader_stage) { std::ifstream shader_stream(file_path); std::string shader_string{ std::istreambuf_iterator{ shader_stream }, std::istreambuf_iterator{} }; auto pshader_string = shader_string.c_str(); GLuint shader = glCreateShader(shader_stage); glShaderSource(shader, 1, &pshader_string, NULL); checkError("glShaderSource(shader, 1, &pshader_string, NULL)"); glCompileShader(shader); checkError("glCompileShader(shader)"); GLint status = GL_FALSE; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); checkError("glGetShaderiv(shader, GL_COMPILE_STATUS, &status)"); if (status != GL_TRUE) { int log_length = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); checkError( "glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length)"); std::vector shader_log(log_length); glGetShaderInfoLog(shader, log_length, NULL, shader_log.data()); checkError("glGetShaderInfoLog(shader, log_length, NULL, " "shader_log.data())"); std::cerr << std::string(shader_log.cbegin(), shader_log.cend()) << std::endl; } return shader; }; auto create_program = [](std::initializer_list shader_stages) { GLuint program = glCreateProgram(); checkError("glCreateProgram()"); for (auto shader_stage : shader_stages) { glAttachShader(program, shader_stage); checkError("glAttachShader(program, shader_stage)"); } glLinkProgram(program); checkError("glLinkProgram(program)"); GLint status = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &status); checkError("glGetProgramiv(program, GL_LINK_STATUS, &status)"); if (status != GL_TRUE) { int log_length = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length); checkError( "glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length)"); std::vector program_log(log_length); glGetProgramInfoLog(program, log_length, NULL, program_log.data()); checkError("glGetProgramInfoLog(program, log_length, NULL, " "program_log.data())"); std::cerr << std::string(program_log.cbegin(), program_log.cend()) << std::endl; } return program; }; vertex_shader = create_shader("./nbody.vert.glsl", GL_VERTEX_SHADER); fragment_shader = create_shader("./nbody.frag.glsl", GL_FRAGMENT_SHADER); gl_program = create_program({ vertex_shader, fragment_shader }); using uni = std::uniform_real_distribution; std::generate_n(std::back_inserter(pos_mass), particle_count, [prng = std::default_random_engine(), x_dist = uni(-x_abs_range, x_abs_range), y_dist = uni(-y_abs_range, y_abs_range), z_dist = uni(-z_abs_range, z_abs_range), m_dist = uni(mass_min, mass_max)]() mutable { return cl_float4{ x_dist(prng), y_dist(prng), z_dist(prng), m_dist(prng) }; }); glUseProgram(gl_program); checkError("glUseProgram(gl_program)"); for (auto vbo_vao : { std::make_pair(&gl_pos_mass.front, &vertex_array.front), std::make_pair(&gl_pos_mass.back, &vertex_array.back) }) { glGenBuffers(1, vbo_vao.first); checkError("glGenBuffers(1, &vertex_buffer)"); glBindBuffer(GL_ARRAY_BUFFER, *vbo_vao.first); checkError("glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer)"); glBufferData(GL_ARRAY_BUFFER, pos_mass.size() * sizeof(cl_float4), pos_mass.data(), GL_STATIC_DRAW); checkError("glBufferData(GL_ARRAY_BUFFER, quad.size() * sizeof(float), " "quad.data(), GL_STATIC_DRAW)"); glBindBuffer(GL_ARRAY_BUFFER, 0); checkError("glBindBuffer(GL_ARRAY_BUFFER, 0)"); glGenVertexArrays(1, vbo_vao.second); checkError("glGenVertexArrays(1, &vertex_array)"); glBindVertexArray(*vbo_vao.second); checkError("glBindVertexArray(vertex_array)"); glBindBuffer(GL_ARRAY_BUFFER, *vbo_vao.first); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(cl_float4), (GLvoid*)(NULL)); checkError("glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, " "sizeof(cl_float4), (GLvoid *)(NULL))"); glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, sizeof(cl_float4), (GLvoid*)(0 + 3 * sizeof(float))); checkError("glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, " "sizeof(cl_float4), (GLvoid *)(0 + 3 * sizeof(float)))"); glEnableVertexAttribArray(0); checkError("glEnableVertexAttribArray(0)"); glEnableVertexAttribArray(1); checkError("glEnableVertexAttribArray(1)"); glBindVertexArray(0); checkError("glBindVertexArray(0)"); } glViewport(0, 0, getSize().x, getSize().y); checkError("glViewport(0, 0, getSize().x, getSize().y)"); glClearColor(0.f, 0.f, 0.f, 1.f); checkError("glClearColor(0.f, 0.f, 0.f, 0.f)"); glEnable(GL_DEPTH_TEST); checkError("glDisable(GL_DEPTH_TEST)"); glDepthFunc(GL_LESS); checkError("glDepthFunc(GL_LESS)"); glDisable(GL_CULL_FACE); checkError("glDisable(GL_CULL_FACE)"); glPointSize(1.5f); } void NBody::initializeCL() { device = opencl_context.getInfo().at(0); queue = cl::CommandQueue{ opencl_context, device }; // Compile kernel const char* kernel_location = "./nbody.cl"; std::ifstream kernel_stream{ kernel_location }; if (!kernel_stream.is_open()) throw std::runtime_error{ std::string{ "Cannot open kernel source: " } + kernel_location }; cl_program = cl::Program{ opencl_context, std::string{ std::istreambuf_iterator{ kernel_stream }, std::istreambuf_iterator{} } }; cl_program.build(device); kernel = cl::Kernel{ cl_program, "nbody" }; gws = cl::NDRange{ particle_count }; lws = cl::NullRange; // velocity = std::vector(particle_count, cl_float4{ 0, 0, 0, 0 // }); velocity_buffer = cl::Buffer{ opencl_context, CL_MEM_READ_WRITE, particle_count * sizeof(cl_float3), nullptr }; queue.enqueueFillBuffer(velocity_buffer, cl_float4{ 0, 0, 0, 0 }, 0, particle_count * sizeof(cl_float4)); queue.finish(); // Translate OpenGL object handles into OpenCL handles cl_pos_mass.front = cl::BufferGL{ opencl_context, CL_MEM_READ_WRITE, gl_pos_mass.front }; cl_pos_mass.back = cl::BufferGL{ opencl_context, CL_MEM_READ_WRITE, gl_pos_mass.back }; // Translate interop_resources = cl::vector{ cl_pos_mass.front, cl_pos_mass.back }; } void NBody::updateScene() { if (animating) { auto nbody = cl::KernelFunctor{ cl_program, "nbody" }; cl::Event acquire, release; queue.enqueueAcquireGLObjects(&interop_resources, nullptr, &acquire); nbody(cl::EnqueueArgs{ queue, cl::NDRange{ particle_count } }, cl_pos_mass.front, cl_pos_mass.back, velocity_buffer, static_cast(particle_count), 0.0001f); queue.enqueueReleaseGLObjects(&interop_resources, nullptr, &release); // Wait for all OpenCL commands to finish if (!cl_khr_gl_event_supported) cl::finish(); else release.wait(); // Swap front and back buffer handles cl_pos_mass.swap(); gl_pos_mass.swap(); } } void NBody::render() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); checkError("glClear(GL_COLOR_BUFFER_BIT)"); glUseProgram(gl_program); checkError("glUseProgram(gl_program)"); glBindVertexArray(vertex_array.front); checkError("glBindVertexArray(vertex_array)"); glBindBuffer(GL_ARRAY_BUFFER, gl_pos_mass.front); checkError("glBindBuffer(GL_ARRAY_BUFFER, gl_pos_mass.front)"); if (needMatrixReset) setMatrices(); glDrawArrays(GL_POINTS, 0, static_cast(particle_count)); checkError( "glDrawArrays(GL_POINTS, 0, static_cast(particle_count))"); glBindBuffer(GL_ARRAY_BUFFER, 0); checkError("glBindBuffer(GL_ARRAY_BUFFER, 0)"); glBindVertexArray(0); checkError("glBindVertexArray(0)"); // Wait for all drawing commands to finish if (!cl_khr_gl_event_supported) glFinish(); else glFlush(); checkError("glFlush()/glFinish()"); } void NBody::event(const sf::Event& event) { switch (event.type) { case sf::Event::Closed: close(); break; case sf::Event::Resized: glViewport(0, 0, getSize().x, getSize().y); checkError("glViewport(0, 0, getSize().x, getSize().y)"); needMatrixReset = true; // projection matrix need to be recalculated break; case sf::Event::KeyPressed: if (event.key.code == sf::Keyboard::Key::Space) { animating = !animating; } break; case sf::Event::MouseButtonPressed: if (event.mouseButton.button == sf::Mouse::Button::Right) { RMB_pressed = true; mousePos = sf::Vector2i{ event.mouseButton.x, event.mouseButton.y }; } break; case sf::Event::MouseButtonReleased: if (event.mouseButton.button == sf::Mouse::Button::Right) RMB_pressed = false; break; case sf::Event::MouseMoved: if (RMB_pressed) mouseDrag(event.mouseMove); break; case sf::Event::MouseWheelMoved: dist += (float)sqrt(pow((double)x_abs_range, 2) + pow((double)y_abs_range, 2)) * 1.1f * event.mouseWheel.delta * (-0.2f); dist = abs(dist); needMatrixReset = true; // view matrix need to be recalculated break; } } void NBody::mouseDrag(const sf::Event::MouseMoveEvent& event) { if (sf::Vector2i{ event.x, event.y } != mousePos) { phi += 0.01f * (event.x - mousePos.x); theta += 0.01f * (event.y - mousePos.y); needMatrixReset = true; } mousePos = sf::Vector2i{ event.x, event.y }; } void NBody::setMatrices() { // Set shader variables const float fov = 45.f; // Set camera to view the origo from the z-axis with up along the y-axis // and distance so the entire sim space is visible with given field-of-view glm::vec3 vecTarget{ 0, 0, 0 }; glm::vec3 vecUp{ 0, 1, 0 }; glm::vec3 vecEye = vecTarget + glm::vec3{ 0, 0, dist }; glm::mat4 matWorld = glm::rotate( // glm::identity() is GLM 0.9.9.1 feature and Ubuntu 18.04 // ships 0.9.9.0 glm::rotate(glm::mat4(1), theta, glm::vec3{ 1, 0, 0 }), // theta rotates around z-axis phi, glm::vec3{ 0, 0, 1 } // theta rotates around z-axis ); glm::mat4 matView = glm::lookAt(vecEye, vecTarget, vecUp); glm::mat4 matProj = glm::perspective( fov, static_cast(getSize().x) / getSize().y, 0.001f, 1000000.0f); auto loc_MVP = glGetUniformLocation(gl_program, "mat_MVP"); checkError("glGetUniformLocation(gl_program, \"mat_MVP\");"); glUniformMatrix4fv(loc_MVP, 1, GL_FALSE, glm::value_ptr(matProj * matView * matWorld)); checkError("glUniformMatrix4fv(loc_MVP, 1, GL_FALSE, " "glm::value_ptr(matProj * matView * matWorld));"); needMatrixReset = false; } int main(int argc, char* argv[]) { try { // Parse command-line options auto opts = cl::sdk::parse_cli(argc, argv); const auto& dev_opts = std::get<1>(opts).triplet; NBody window{ dev_opts.plat_index, dev_opts.dev_index, dev_opts.dev_type }; window.run(); } catch (cl::util::Error& e) { std::cerr << "OpenCL Utils error: " << e.what() << std::endl; std::exit(e.err()); } catch (cl::BuildError& e) { std::cerr << "OpenCL runtime error: " << e.what() << std::endl; for (auto& build_log : e.getBuildLog()) { std::cerr << "\tBuild log for device: " << build_log.first.getInfo() << "\n" << std::endl; std::cerr << build_log.second << "\n" << std::endl; } std::exit(e.err()); } catch (cl::Error& e) { std::cerr << "OpenCL rutnime error: " << e.what() << std::endl; std::exit(e.err()); } catch (std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; std::exit(EXIT_FAILURE); } return 0; }