213 lines
7.4 KiB
C++
213 lines
7.4 KiB
C++
|
|
#include "GpuApplication.h"
|
|
|
|
#define BOUNDARY 0.9f
|
|
#define PUSBACK 0.05f
|
|
|
|
GpuParticleSystem::GpuParticleSystem(std::string kernelFile) {
|
|
this->kernelFile = kernelFile;
|
|
srand(0);
|
|
}
|
|
|
|
bool GpuParticleSystem::InitGL() {
|
|
// Initialize GLFW, which is used to create a window
|
|
if (!glfwInit()) {
|
|
std::cerr << "Failed to initialize GLFW" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Set GLFW to not create an OpenGL context (version 330 core) (also at the top of each shader file)
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
|
|
|
// Create a GLFW window
|
|
window = glfwCreateWindow(800, 800, "GPGPU particle system", nullptr, nullptr);
|
|
if (!window) {
|
|
std::cerr << "Failed to create GLFW window" << std::endl;
|
|
glfwTerminate();
|
|
return false;
|
|
}
|
|
|
|
// Make the OpenGL context current
|
|
glfwMakeContextCurrent(window);
|
|
|
|
// Initialize GLAD, which simplifies hadling function pointers to OpenGL
|
|
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
|
|
std::cerr << "Failed to initialized GLAD" << std::endl;
|
|
glfwTerminate();
|
|
return false;
|
|
}
|
|
|
|
// Enable depth testing in OpenGL
|
|
glEnable(GL_DEPTH_TEST);
|
|
// define the resolution of the screen texture
|
|
glViewport(0, 0, 800, 800);
|
|
// define the clear color (black)
|
|
glClearColor(0, 0, 0, 1);
|
|
// define the size (in pixels) of the points drawn
|
|
glPointSize(4);
|
|
|
|
// setup OpenGL shader
|
|
// first, find the folder where the shader files are stored
|
|
std::string shadersPath;
|
|
std::size_t pos = kernelFile.find("ParticleSystem.cl");
|
|
if (pos != std::string::npos) {
|
|
// Replace "PerlinNoise.cl" with "shaders/vertex.glsl"
|
|
shadersPath = kernelFile.substr(0, pos) + "shaders/";
|
|
}
|
|
std::cout << "Reading GLSL files from " << shadersPath << std::endl;
|
|
if (!shader.init((shadersPath + "vertex_particles.glsl").c_str(), (shadersPath + "fragment_particles.glsl").c_str())) {
|
|
return false;
|
|
}
|
|
// link this OpenGL shader so that it is used
|
|
shader.use();
|
|
|
|
// create and bind the VAO, which is always necessary
|
|
glGenVertexArrays(1, &VAO);
|
|
glBindVertexArray(VAO);
|
|
// we need to pass data (positions and velocities) to the vertex shader,
|
|
// so we'll use VBOs for this.
|
|
glGenBuffers(2, VBOs);
|
|
|
|
return true;
|
|
}
|
|
|
|
void GpuParticleSystem::InitPosVel() {
|
|
positions = std::vector<float>(nrParticles * 2, 0);
|
|
velocities = std::vector<float>(nrParticles * 2, 0);
|
|
|
|
// --------------
|
|
// Your code here
|
|
// Initialize positions and velocities so that the particles start off on the left side of the screen.
|
|
for (int i = 0; i < nrParticles; i++) {
|
|
positions[i * 2] = -BOUNDARY + PUSBACK;
|
|
positions[i * 2 + 1] = -BOUNDARY + PUSBACK + (2 * BOUNDARY - 2 * PUSBACK) * i / nrParticles;
|
|
}
|
|
// --------------
|
|
}
|
|
|
|
void GpuParticleSystem::Run() {
|
|
|
|
InitPosVel();
|
|
|
|
// --------------
|
|
// Your code here
|
|
// Setup OpenCL so that the 'updateParticles' kernel can be executed for every frame in the loop below
|
|
cl::Buffer positions_device(context, CL_MEM_READ_WRITE, sizeof(float) * nrParticles * 2);
|
|
cl::Buffer velocities_device(context, CL_MEM_READ_WRITE, sizeof(float) * nrParticles * 2);
|
|
|
|
cl::KernelFunctor<cl::Buffer, cl::Buffer, float, float, float> kernelFunctor(program, "updateParticles");
|
|
cl::NDRange rangeGlobal(nrParticles);
|
|
cl::EnqueueArgs enqueArgs(queue, rangeGlobal);
|
|
|
|
// Write initial data to the device buffers.
|
|
queue.enqueueWriteBuffer(positions_device, CL_TRUE, 0, sizeof(float) * nrParticles * 2, positions.data());
|
|
queue.enqueueWriteBuffer(velocities_device, CL_TRUE, 0, sizeof(float) * nrParticles * 2, velocities.data());
|
|
// --------------
|
|
|
|
// Setup the OpenGL VBOs that will pass the position and velocity of each particle to the vertex shader
|
|
glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * nrParticles * 2, positions.data(), GL_DYNAMIC_DRAW);
|
|
// tell the vertex shader that on location = 0 there is a vec2 positions
|
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (void*)0);
|
|
glEnableVertexAttribArray(0);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * nrParticles * 2, velocities.data(), GL_DYNAMIC_DRAW);
|
|
// tell the vertex shader that on location = 1 there is a vec2 velocities
|
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (void*)0);
|
|
glEnableVertexAttribArray(1);
|
|
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
bool quit = false;
|
|
printf(
|
|
"Keyboard input options:\n"
|
|
"Press r to reset the simulation.\n"
|
|
);
|
|
while (!glfwWindowShouldClose(window) && !quit) {
|
|
|
|
// calculate the duration of the previous frame
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
std::chrono::duration<double> duration = end - start;
|
|
auto dt = duration.count(); // in seconds
|
|
start = end;
|
|
|
|
quit = HandleUserInput();
|
|
if (reset) {
|
|
InitPosVel();
|
|
// --------------
|
|
// Your code here
|
|
// Update the OpenCL cl::Buffers holding the positions and velocities
|
|
printf("Resetting the simulation\n");
|
|
queue.enqueueWriteBuffer(positions_device, CL_TRUE, 0, sizeof(float) * nrParticles * 2, positions.data());
|
|
queue.enqueueWriteBuffer(velocities_device, CL_TRUE, 0, sizeof(float) * nrParticles * 2, velocities.data());
|
|
// --------------
|
|
reset = false;
|
|
}
|
|
|
|
// --------------
|
|
// Your code here
|
|
// Update the particles' position and velocities using the OpenCL kernel
|
|
// Make sure that the OpenGL VBOs are updated with these new values (hint: glBindBuffer, glBufferSubData )
|
|
|
|
// Launch the kernel
|
|
cl::Event event = kernelFunctor(enqueArgs, positions_device, velocities_device, dt, cursorX, cursorY);
|
|
|
|
// Read the data from the device buffer into the host vector
|
|
queue.enqueueReadBuffer(positions_device, CL_TRUE, 0, sizeof(float) * nrParticles * 2, positions.data());
|
|
queue.enqueueReadBuffer(velocities_device, CL_TRUE, 0, sizeof(float) * nrParticles * 2, velocities.data());
|
|
|
|
// Update the OpenGL VBOs
|
|
glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
|
|
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * nrParticles * 2, positions.data());
|
|
glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);
|
|
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * nrParticles * 2, velocities.data());
|
|
// --------------
|
|
|
|
// render to screen
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
// clear the screen buffer and depth buffer
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
// draw nrParticles points
|
|
glDrawArrays(GL_POINTS, 0, nrParticles);
|
|
|
|
// Swap the front and back buffers, so the screen texture that we just drew on becomes visible
|
|
glfwSwapBuffers(window);
|
|
// Poll for and process events, e.g. keyboard handling
|
|
glfwPollEvents();
|
|
}
|
|
}
|
|
|
|
bool GpuParticleSystem::HandleUserInput() {
|
|
bool quit = false;
|
|
// quit if Escape of q is pressed
|
|
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
|
|
glfwSetWindowShouldClose(window, true);
|
|
}
|
|
if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS || glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
|
|
quit = true;
|
|
}
|
|
|
|
// reset simulation if r is pressed
|
|
if (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS) {
|
|
reset = true;
|
|
}
|
|
|
|
// update the cursor position
|
|
double xpos, ypos;
|
|
glfwGetCursorPos(window, &xpos, &ypos);
|
|
// convert to OpenGL's NDC space
|
|
cursorX = (2.0f * xpos) / 800.0f - 1.0f;
|
|
cursorY = 1.0f - (2.0f * ypos) / 800.0f;
|
|
|
|
return quit;
|
|
}
|
|
|
|
void GpuParticleSystem::Cleanup() {
|
|
glDeleteVertexArrays(1, &VAO);
|
|
glDeleteBuffers(2, VBOs);
|
|
glfwDestroyWindow(window);
|
|
glfwTerminate();
|
|
}
|