This repository has been archived on 2024-12-30. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
2024CG-project-render/src/GpuParticleSystem.cpp

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();
}