Files
ANSLibs/ANS-HWiNFO/external/OpenCL/samples/extensions/khr/nbody/main.cpp

514 lines
18 KiB
C++

/*
* 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 <GL/glew.h>
// OpenCL SDK includes
#include <CL/Utils/Utils.hpp>
#include <CL/SDK/SDK.hpp>
// STL includes
#include <iostream>
#include <valarray>
#include <random>
#include <algorithm>
#include <fstream>
#include <tuple> // std::make_tuple
// TCLAP includes
#include <tclap/CmdLine.h>
// OpenGL includes
#include <SFML/OpenGL.hpp>
// GLM includes
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
template <typename T> 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<cl_float4> pos_mass;
std::vector<cl_float3> velocity;
std::vector<cl_float3> 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::BufferGL> cl_pos_mass;
cl::vector<cl::Memory> interop_resources;
cl::vector<cl::Event> 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<cl_GLuint> vertex_array;
DoubleBuffer<cl_GLuint> gl_pos_mass;
bool RMB_pressed; // Variables to enable dragging
sf::Vector2<int> 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<GLchar>{
shader_stream },
std::istreambuf_iterator<GLchar>{} };
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<GLchar> 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<GLuint> 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<GLchar> 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<float>;
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<CL_CONTEXT_DEVICES>().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<char>{
kernel_stream },
std::istreambuf_iterator<char>{} } };
cl_program.build(device);
kernel = cl::Kernel{ cl_program, "nbody" };
gws = cl::NDRange{ particle_count };
lws = cl::NullRange;
// velocity = std::vector<cl_float4>(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::Memory>{ cl_pos_mass.front, cl_pos_mass.back };
}
void NBody::updateScene()
{
if (animating)
{
auto nbody =
cl::KernelFunctor<cl::BufferGL, cl::BufferGL, cl::Buffer, cl_uint,
cl_float>{ 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<cl_uint>(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<GLsizei>(particle_count));
checkError(
"glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(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<glm::mat4>() 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<float>(
fov, static_cast<float>(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<cl::sdk::options::Diagnostic,
cl::sdk::options::SingleDevice>(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<CL_DEVICE_NAME>() << "\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;
}