Cleaned up particles example.

This commit is contained in:
Camilla Berglund 2014-01-11 20:46:24 +01:00
parent 457403586d
commit 89627e4cd0
2 changed files with 106 additions and 155 deletions

1
.gitignore vendored
View File

@ -44,6 +44,7 @@ examples/*.exe
examples/boing examples/boing
examples/gears examples/gears
examples/heightmap examples/heightmap
examples/particles
examples/splitview examples/splitview
examples/simple examples/simple
examples/wave examples/wave

View File

@ -1,40 +1,27 @@
//======================================================================== //========================================================================
// This is a simple, but cool particle engine (buzz-word meaning many // A simple particle engine with threaded physics
// small objects that are treated as points and drawn as textures // Copyright (c) Marcus Geelnard
// projected on simple geometry). // Copyright (c) Camilla Berglund <elmindreda@elmindreda.org>
// //
// This demonstration generates a colorful fountain-like animation. It // This software is provided 'as-is', without any express or implied
// uses several advanced OpenGL teqhniques: // warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// //
// 1) Lighting (per vertex) // Permission is granted to anyone to use this software for any purpose,
// 2) Alpha blending // including commercial applications, and to alter it and redistribute it
// 3) Fog // freely, subject to the following restrictions:
// 4) Texturing
// 5) Display lists (for drawing the static environment geometry)
// 6) Vertex arrays (for drawing the particles)
// 7) GL_EXT_separate_specular_color is used (if available)
// //
// Even more so, this program uses multi threading. The program is // 1. The origin of this software must not be misrepresented; you must not
// essentialy divided into a main rendering thread and a particle physics // claim that you wrote the original software. If you use this software
// calculation thread. My benchmarks under Windows 2000 on a single // in a product, an acknowledgment in the product documentation would
// processor system show that running this program as two threads instead // be appreciated but is not required.
// of a single thread means no difference (there may be a very marginal
// advantage for the multi threaded case). On dual processor systems I
// have had reports of 5-25% of speed increase when running this program
// as two threads instead of one thread.
// //
// The default behaviour of this program is to use two threads. To force // 2. Altered source versions must be plainly marked as such, and must not
// a single thread to be used, use the command line switch -s. // be misrepresented as being the original software.
// //
// To run a fixed length benchmark (60 s), use the command line switch -b. // 3. This notice may not be removed or altered from any source
// distribution.
// //
// Benchmark results (640x480x16, best of three tests):
//
// CPU GFX 1 thread 2 threads
// Athlon XP 2700+ GeForce Ti4200 (oc) 757 FPS 759 FPS
// P4 2.8 GHz (SMT) GeForce FX5600 548 FPS 550 FPS
//
// One more thing: Press 'w' during the demo to toggle wireframe mode.
//======================================================================== //========================================================================
#include <stdlib.h> #include <stdlib.h>
@ -60,10 +47,6 @@
#define M_PI 3.141592654 #define M_PI 3.141592654
#endif #endif
// Desired fullscreen resolution
#define WIDTH 640
#define HEIGHT 480
//======================================================================== //========================================================================
// Type definitions // Type definitions
@ -93,18 +76,12 @@ typedef struct
// Program control global variables // Program control global variables
//======================================================================== //========================================================================
// "Running" flag (true if program shall continue to run)
int running;
// Window dimensions // Window dimensions
int width, height; float aspect_ratio;
// "wireframe" flag (true if we use wireframe view) // "wireframe" flag (true if we use wireframe view)
int wireframe; int wireframe;
// "multithreading" flag (true if we use multithreading)
int multithreading;
// Thread synchronization // Thread synchronization
struct { struct {
double t; // Time (s) double t; // Time (s)
@ -245,11 +222,11 @@ const GLfloat fog_color[4] = { 0.1f, 0.1f, 0.1f, 1.f };
static void usage(void) static void usage(void)
{ {
printf("Usage: particles [-hbs]\n"); printf("Usage: particles [-bfhs]\n");
printf("Options:\n"); printf("Options:\n");
printf(" -b Benchmark (run program for 60 seconds)\n"); printf(" -f Run in full screen\n");
printf(" -s Run program as single thread (default is to use two threads)\n");
printf(" -h Display this help\n"); printf(" -h Display this help\n");
printf(" -s Run program as single thread (default is to use two threads)\n");
printf("\n"); printf("\n");
printf("Program runtime controls:\n"); printf("Program runtime controls:\n");
printf(" W Toggle wireframe mode\n"); printf(" W Toggle wireframe mode\n");
@ -411,7 +388,7 @@ static void particle_engine(double t, float dt)
// the L1 data cache on most CPUs) // the L1 data cache on most CPUs)
#define PARTICLE_VERTS 4 // Number of vertices per particle #define PARTICLE_VERTS 4 // Number of vertices per particle
static void draw_particles(double t, float dt) static void draw_particles(GLFWwindow* window, double t, float dt)
{ {
int i, particle_count; int i, particle_count;
Vertex vertex_array[BATCH_PARTICLES * PARTICLE_VERTS]; Vertex vertex_array[BATCH_PARTICLES * PARTICLE_VERTS];
@ -471,12 +448,10 @@ static void draw_particles(double t, float dt)
// Most OpenGL cards / drivers are optimized for this format. // Most OpenGL cards / drivers are optimized for this format.
glInterleavedArrays(GL_T2F_C4UB_V3F, 0, vertex_array); glInterleavedArrays(GL_T2F_C4UB_V3F, 0, vertex_array);
// Is particle physics carried out in a separate thread?
if (multithreading)
{
// Wait for particle physics thread to be done // Wait for particle physics thread to be done
mtx_lock(&thread_sync.particles_lock); mtx_lock(&thread_sync.particles_lock);
while (running && thread_sync.p_frame <= thread_sync.d_frame) while (!glfwWindowShouldClose(window) &&
thread_sync.p_frame <= thread_sync.d_frame)
{ {
struct timespec ts = { 0, 100000000 }; struct timespec ts = { 0, 100000000 };
cnd_timedwait(&thread_sync.p_done, &thread_sync.particles_lock, &ts); cnd_timedwait(&thread_sync.p_done, &thread_sync.particles_lock, &ts);
@ -488,12 +463,6 @@ static void draw_particles(double t, float dt)
// Update frame counter // Update frame counter
thread_sync.d_frame++; thread_sync.d_frame++;
}
else
{
// Perform particle physics in this thread
particle_engine(t, dt);
}
// Loop through all particles and build vertex arrays. // Loop through all particles and build vertex arrays.
particle_count = 0; particle_count = 0;
@ -577,13 +546,9 @@ static void draw_particles(double t, float dt)
pptr++; pptr++;
} }
// We are done with the particle data: Unlock mutex and signal physics // We are done with the particle data
// thread
if (multithreading)
{
mtx_unlock(&thread_sync.particles_lock); mtx_unlock(&thread_sync.particles_lock);
cnd_signal(&thread_sync.d_done); cnd_signal(&thread_sync.d_done);
}
// Draw final batch of particles (if any) // Draw final batch of particles (if any)
glDrawArrays(GL_QUADS, 0, PARTICLE_VERTS * particle_count); glDrawArrays(GL_QUADS, 0, PARTICLE_VERTS * particle_count);
@ -812,7 +777,7 @@ static void setup_lights(void)
// Main rendering function // Main rendering function
//======================================================================== //========================================================================
static void draw_scene(double t) static void draw_scene(GLFWwindow* window, double t)
{ {
double xpos, ypos, zpos, angle_x, angle_y, angle_z; double xpos, ypos, zpos, angle_x, angle_y, angle_z;
static double t_old = 0.0; static double t_old = 0.0;
@ -822,14 +787,12 @@ static void draw_scene(double t)
dt = (float) (t - t_old); dt = (float) (t - t_old);
t_old = t; t_old = t;
glViewport(0, 0, width, height);
glClearColor(0.1f, 0.1f, 0.1f, 1.f); glClearColor(0.1f, 0.1f, 0.1f, 1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadIdentity(); glLoadIdentity();
gluPerspective(65.0, (double) width / (double) height, 1.0, 60.0); gluPerspective(65.0, aspect_ratio, 1.0, 60.0);
// Setup camera // Setup camera
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
@ -875,7 +838,7 @@ static void draw_scene(double t)
glDisable(GL_FOG); glDisable(GL_FOG);
// Particles must be drawn after all solid objects have been drawn // Particles must be drawn after all solid objects have been drawn
draw_particles(t, dt); draw_particles(window, t, dt);
// Z-buffer not needed anymore // Z-buffer not needed anymore
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
@ -886,10 +849,10 @@ static void draw_scene(double t)
// Window resize callback function // Window resize callback function
//======================================================================== //========================================================================
static void resize_callback(GLFWwindow* window, int w, int h) static void resize_callback(GLFWwindow* window, int width, int height)
{ {
width = w; glViewport(0, 0, width, height);
height = h > 0 ? h : 1; // Prevent division by zero in aspect calc. aspect_ratio = height ? width / (float) height : 1.f;
} }
@ -904,7 +867,7 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action,
switch (key) switch (key)
{ {
case GLFW_KEY_ESCAPE: case GLFW_KEY_ESCAPE:
running = 0; glfwSetWindowShouldClose(window, GL_TRUE);
break; break;
case GLFW_KEY_W: case GLFW_KEY_W:
wireframe = !wireframe; wireframe = !wireframe;
@ -924,18 +887,21 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action,
static int physics_thread_main(void* arg) static int physics_thread_main(void* arg)
{ {
GLFWwindow* window = arg;
for (;;) for (;;)
{ {
mtx_lock(&thread_sync.particles_lock); mtx_lock(&thread_sync.particles_lock);
// Wait for particle drawing to be done // Wait for particle drawing to be done
while (running && thread_sync.p_frame > thread_sync.d_frame) while (!glfwWindowShouldClose(window) &&
thread_sync.p_frame > thread_sync.d_frame)
{ {
struct timespec ts = { 0, 100000000 }; struct timespec ts = { 0, 100000000 };
cnd_timedwait(&thread_sync.d_done, &thread_sync.particles_lock, &ts); cnd_timedwait(&thread_sync.d_done, &thread_sync.particles_lock, &ts);
} }
if (!running) if (glfwWindowShouldClose(window))
break; break;
// Update particles // Update particles
@ -959,30 +925,10 @@ static int physics_thread_main(void* arg)
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
int i, ch, frames, benchmark; int ch, width, height;
double t0, t;
thrd_t physics_thread = 0; thrd_t physics_thread = 0;
GLFWwindow* window; GLFWwindow* window;
GLFWmonitor* monitor = NULL;
// Use multithreading by default, but don't benchmark
multithreading = 1;
benchmark = 0;
while ((ch = getopt(argc, argv, "bhs")) != -1)
{
switch (ch)
{
case 'b':
benchmark = 1;
break;
case 'h':
usage();
exit(EXIT_SUCCESS);
case 's':
multithreading = 0;
break;
}
}
if (!glfwInit()) if (!glfwInit())
{ {
@ -990,23 +936,58 @@ int main(int argc, char** argv)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
window = glfwCreateWindow(WIDTH, HEIGHT, "Particle Engine", while ((ch = getopt(argc, argv, "fhs")) != -1)
glfwGetPrimaryMonitor(), NULL); {
switch (ch)
{
case 'f':
monitor = glfwGetPrimaryMonitor();
break;
case 'h':
usage();
exit(EXIT_SUCCESS);
}
}
if (monitor)
{
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwWindowHint(GLFW_RED_BITS, mode->redBits);
glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
width = mode->width;
height = mode->height;
}
else
{
width = 640;
height = 480;
}
window = glfwCreateWindow(width, height, "Particle Engine", monitor, NULL);
if (!window) if (!window)
{ {
fprintf(stderr, "Failed to open GLFW window\n"); fprintf(stderr, "Failed to create GLFW window\n");
glfwTerminate(); glfwTerminate();
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (monitor)
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwMakeContextCurrent(window); glfwMakeContextCurrent(window);
glfwSwapInterval(0); glfwSwapInterval(1);
glfwSetWindowSizeCallback(window, resize_callback); glfwSetWindowSizeCallback(window, resize_callback);
glfwSetKeyCallback(window, key_callback); glfwSetKeyCallback(window, key_callback);
// Set initial aspect ratio
glfwGetWindowSize(window, &width, &height);
resize_callback(window, width, height);
// Upload particle texture // Upload particle texture
glGenTextures(1, &particle_tex_id); glGenTextures(1, &particle_tex_id);
glBindTexture(GL_TEXTURE_2D, particle_tex_id); glBindTexture(GL_TEXTURE_2D, particle_tex_id);
@ -1039,65 +1020,34 @@ int main(int argc, char** argv)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
wireframe = 0; wireframe = 0;
// Clear particle system
for (i = 0; i < MAX_PARTICLES; i++)
particles[i].active = 0;
min_age = 0.f;
running = 1;
// Set initial times // Set initial times
thread_sync.t = 0.0; thread_sync.t = 0.0;
thread_sync.dt = 0.001f; thread_sync.dt = 0.001f;
if (multithreading)
{
thread_sync.p_frame = 0; thread_sync.p_frame = 0;
thread_sync.d_frame = 0; thread_sync.d_frame = 0;
mtx_init(&thread_sync.particles_lock, mtx_timed); mtx_init(&thread_sync.particles_lock, mtx_timed);
cnd_init(&thread_sync.p_done); cnd_init(&thread_sync.p_done);
cnd_init(&thread_sync.d_done); cnd_init(&thread_sync.d_done);
if (thrd_create(&physics_thread, physics_thread_main, NULL) != thrd_success) if (thrd_create(&physics_thread, physics_thread_main, window) != thrd_success)
{ {
glfwTerminate(); glfwTerminate();
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
}
t0 = glfwGetTime(); glfwSetTime(0.0);
frames = 0;
while (running) while (!glfwWindowShouldClose(window))
{ {
// Get frame time draw_scene(window, glfwGetTime());
t = glfwGetTime() - t0;
draw_scene(t);
glfwSwapBuffers(window); glfwSwapBuffers(window);
glfwPollEvents(); glfwPollEvents();
running = running && !glfwWindowShouldClose(window);
frames++;
// End of benchmark?
if (benchmark && t >= 60.0)
running = 0;
} }
t = glfwGetTime() - t0;
// Wait for particle physics thread to die
if (multithreading)
thrd_join(physics_thread, NULL); thrd_join(physics_thread, NULL);
// Display profiling information
printf("%d frames in %.2f seconds = %.1f FPS", frames, t, (double) frames / t);
printf(" (multithreading %s)\n", multithreading ? "on" : "off");
glfwDestroyWindow(window); glfwDestroyWindow(window);
glfwTerminate(); glfwTerminate();