|
|
|
@ -1,40 +1,27 @@ |
|
|
|
|
//========================================================================
|
|
|
|
|
// This is a simple, but cool particle engine (buzz-word meaning many
|
|
|
|
|
// small objects that are treated as points and drawn as textures
|
|
|
|
|
// projected on simple geometry).
|
|
|
|
|
// A simple particle engine with threaded physics
|
|
|
|
|
// Copyright (c) Marcus Geelnard
|
|
|
|
|
// Copyright (c) Camilla Berglund <elmindreda@elmindreda.org>
|
|
|
|
|
//
|
|
|
|
|
// This demonstration generates a colorful fountain-like animation. It
|
|
|
|
|
// uses several advanced OpenGL teqhniques:
|
|
|
|
|
// This software is provided 'as-is', without any express or implied
|
|
|
|
|
// warranty. In no event will the authors be held liable for any damages
|
|
|
|
|
// arising from the use of this software.
|
|
|
|
|
//
|
|
|
|
|
// 1) Lighting (per vertex)
|
|
|
|
|
// 2) Alpha blending
|
|
|
|
|
// 3) Fog
|
|
|
|
|
// 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)
|
|
|
|
|
// Permission is granted to anyone to use this software for any purpose,
|
|
|
|
|
// including commercial applications, and to alter it and redistribute it
|
|
|
|
|
// freely, subject to the following restrictions:
|
|
|
|
|
//
|
|
|
|
|
// Even more so, this program uses multi threading. The program is
|
|
|
|
|
// essentialy divided into a main rendering thread and a particle physics
|
|
|
|
|
// calculation thread. My benchmarks under Windows 2000 on a single
|
|
|
|
|
// processor system show that running this program as two threads instead
|
|
|
|
|
// 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.
|
|
|
|
|
// 1. The origin of this software must not be misrepresented; you must not
|
|
|
|
|
// claim that you wrote the original software. If you use this software
|
|
|
|
|
// in a product, an acknowledgment in the product documentation would
|
|
|
|
|
// be appreciated but is not required.
|
|
|
|
|
//
|
|
|
|
|
// The default behaviour of this program is to use two threads. To force
|
|
|
|
|
// a single thread to be used, use the command line switch -s.
|
|
|
|
|
// 2. Altered source versions must be plainly marked as such, and must not
|
|
|
|
|
// 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> |
|
|
|
@ -60,10 +47,6 @@ |
|
|
|
|
#define M_PI 3.141592654 |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
// Desired fullscreen resolution
|
|
|
|
|
#define WIDTH 640 |
|
|
|
|
#define HEIGHT 480 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//========================================================================
|
|
|
|
|
// Type definitions
|
|
|
|
@ -93,18 +76,12 @@ typedef struct |
|
|
|
|
// Program control global variables
|
|
|
|
|
//========================================================================
|
|
|
|
|
|
|
|
|
|
// "Running" flag (true if program shall continue to run)
|
|
|
|
|
int running; |
|
|
|
|
|
|
|
|
|
// Window dimensions
|
|
|
|
|
int width, height; |
|
|
|
|
float aspect_ratio; |
|
|
|
|
|
|
|
|
|
// "wireframe" flag (true if we use wireframe view)
|
|
|
|
|
int wireframe; |
|
|
|
|
|
|
|
|
|
// "multithreading" flag (true if we use multithreading)
|
|
|
|
|
int multithreading; |
|
|
|
|
|
|
|
|
|
// Thread synchronization
|
|
|
|
|
struct { |
|
|
|
|
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) |
|
|
|
|
{ |
|
|
|
|
printf("Usage: particles [-hbs]\n"); |
|
|
|
|
printf("Usage: particles [-bfhs]\n"); |
|
|
|
|
printf("Options:\n"); |
|
|
|
|
printf(" -b Benchmark (run program for 60 seconds)\n"); |
|
|
|
|
printf(" -s Run program as single thread (default is to use two threads)\n"); |
|
|
|
|
printf(" -f Run in full screen\n"); |
|
|
|
|
printf(" -h Display this help\n"); |
|
|
|
|
printf(" -s Run program as single thread (default is to use two threads)\n"); |
|
|
|
|
printf("\n"); |
|
|
|
|
printf("Program runtime controls:\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)
|
|
|
|
|
#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; |
|
|
|
|
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.
|
|
|
|
|
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
|
|
|
|
|
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 }; |
|
|
|
|
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
|
|
|
|
|
thread_sync.d_frame++; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
// Perform particle physics in this thread
|
|
|
|
|
particle_engine(t, dt); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Loop through all particles and build vertex arrays.
|
|
|
|
|
particle_count = 0; |
|
|
|
@ -577,13 +546,9 @@ static void draw_particles(double t, float dt) |
|
|
|
|
pptr++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// We are done with the particle data: Unlock mutex and signal physics
|
|
|
|
|
// thread
|
|
|
|
|
if (multithreading) |
|
|
|
|
{ |
|
|
|
|
// We are done with the particle data
|
|
|
|
|
mtx_unlock(&thread_sync.particles_lock); |
|
|
|
|
cnd_signal(&thread_sync.d_done); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Draw final batch of particles (if any)
|
|
|
|
|
glDrawArrays(GL_QUADS, 0, PARTICLE_VERTS * particle_count); |
|
|
|
@ -812,7 +777,7 @@ static void setup_lights(void) |
|
|
|
|
// 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; |
|
|
|
|
static double t_old = 0.0; |
|
|
|
@ -822,14 +787,12 @@ static void draw_scene(double t) |
|
|
|
|
dt = (float) (t - t_old); |
|
|
|
|
t_old = t; |
|
|
|
|
|
|
|
|
|
glViewport(0, 0, width, height); |
|
|
|
|
|
|
|
|
|
glClearColor(0.1f, 0.1f, 0.1f, 1.f); |
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
|
|
|
|
|
|
|
|
|
glMatrixMode(GL_PROJECTION); |
|
|
|
|
glLoadIdentity(); |
|
|
|
|
gluPerspective(65.0, (double) width / (double) height, 1.0, 60.0); |
|
|
|
|
gluPerspective(65.0, aspect_ratio, 1.0, 60.0); |
|
|
|
|
|
|
|
|
|
// Setup camera
|
|
|
|
|
glMatrixMode(GL_MODELVIEW); |
|
|
|
@ -875,7 +838,7 @@ static void draw_scene(double t) |
|
|
|
|
glDisable(GL_FOG); |
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
glDisable(GL_DEPTH_TEST); |
|
|
|
@ -886,10 +849,10 @@ static void draw_scene(double t) |
|
|
|
|
// 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; |
|
|
|
|
height = h > 0 ? h : 1; // Prevent division by zero in aspect calc.
|
|
|
|
|
glViewport(0, 0, width, height); |
|
|
|
|
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) |
|
|
|
|
{ |
|
|
|
|
case GLFW_KEY_ESCAPE: |
|
|
|
|
running = 0; |
|
|
|
|
glfwSetWindowShouldClose(window, GL_TRUE); |
|
|
|
|
break; |
|
|
|
|
case GLFW_KEY_W: |
|
|
|
|
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) |
|
|
|
|
{ |
|
|
|
|
GLFWwindow* window = arg; |
|
|
|
|
|
|
|
|
|
for (;;) |
|
|
|
|
{ |
|
|
|
|
mtx_lock(&thread_sync.particles_lock); |
|
|
|
|
|
|
|
|
|
// 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 }; |
|
|
|
|
cnd_timedwait(&thread_sync.d_done, &thread_sync.particles_lock, &ts); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!running) |
|
|
|
|
if (glfwWindowShouldClose(window)) |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
// Update particles
|
|
|
|
@ -959,54 +925,69 @@ static int physics_thread_main(void* arg) |
|
|
|
|
|
|
|
|
|
int main(int argc, char** argv) |
|
|
|
|
{ |
|
|
|
|
int i, ch, frames, benchmark; |
|
|
|
|
double t0, t; |
|
|
|
|
int ch, width, height; |
|
|
|
|
thrd_t physics_thread = 0; |
|
|
|
|
GLFWwindow* window; |
|
|
|
|
GLFWmonitor* monitor = NULL; |
|
|
|
|
|
|
|
|
|
// Use multithreading by default, but don't benchmark
|
|
|
|
|
multithreading = 1; |
|
|
|
|
benchmark = 0; |
|
|
|
|
if (!glfwInit()) |
|
|
|
|
{ |
|
|
|
|
fprintf(stderr, "Failed to initialize GLFW\n"); |
|
|
|
|
exit(EXIT_FAILURE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
while ((ch = getopt(argc, argv, "bhs")) != -1) |
|
|
|
|
while ((ch = getopt(argc, argv, "fhs")) != -1) |
|
|
|
|
{ |
|
|
|
|
switch (ch) |
|
|
|
|
{ |
|
|
|
|
case 'b': |
|
|
|
|
benchmark = 1; |
|
|
|
|
case 'f': |
|
|
|
|
monitor = glfwGetPrimaryMonitor(); |
|
|
|
|
break; |
|
|
|
|
case 'h': |
|
|
|
|
usage(); |
|
|
|
|
exit(EXIT_SUCCESS); |
|
|
|
|
case 's': |
|
|
|
|
multithreading = 0; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!glfwInit()) |
|
|
|
|
if (monitor) |
|
|
|
|
{ |
|
|
|
|
fprintf(stderr, "Failed to initialize GLFW\n"); |
|
|
|
|
exit(EXIT_FAILURE); |
|
|
|
|
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", |
|
|
|
|
glfwGetPrimaryMonitor(), NULL); |
|
|
|
|
window = glfwCreateWindow(width, height, "Particle Engine", monitor, NULL); |
|
|
|
|
if (!window) |
|
|
|
|
{ |
|
|
|
|
fprintf(stderr, "Failed to open GLFW window\n"); |
|
|
|
|
fprintf(stderr, "Failed to create GLFW window\n"); |
|
|
|
|
glfwTerminate(); |
|
|
|
|
exit(EXIT_FAILURE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (monitor) |
|
|
|
|
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); |
|
|
|
|
|
|
|
|
|
glfwMakeContextCurrent(window); |
|
|
|
|
glfwSwapInterval(0); |
|
|
|
|
glfwSwapInterval(1); |
|
|
|
|
|
|
|
|
|
glfwSetWindowSizeCallback(window, resize_callback); |
|
|
|
|
glfwSetKeyCallback(window, key_callback); |
|
|
|
|
|
|
|
|
|
// Set initial aspect ratio
|
|
|
|
|
glfwGetWindowSize(window, &width, &height); |
|
|
|
|
resize_callback(window, width, height); |
|
|
|
|
|
|
|
|
|
// Upload particle texture
|
|
|
|
|
glGenTextures(1, &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); |
|
|
|
|
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
|
|
|
|
|
thread_sync.t = 0.0; |
|
|
|
|
thread_sync.dt = 0.001f; |
|
|
|
|
|
|
|
|
|
if (multithreading) |
|
|
|
|
{ |
|
|
|
|
thread_sync.p_frame = 0; |
|
|
|
|
thread_sync.d_frame = 0; |
|
|
|
|
|
|
|
|
|
mtx_init(&thread_sync.particles_lock, mtx_timed); |
|
|
|
|
cnd_init(&thread_sync.p_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(); |
|
|
|
|
exit(EXIT_FAILURE); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
t0 = glfwGetTime(); |
|
|
|
|
frames = 0; |
|
|
|
|
glfwSetTime(0.0); |
|
|
|
|
|
|
|
|
|
while (running) |
|
|
|
|
while (!glfwWindowShouldClose(window)) |
|
|
|
|
{ |
|
|
|
|
// Get frame time
|
|
|
|
|
t = glfwGetTime() - t0; |
|
|
|
|
|
|
|
|
|
draw_scene(t); |
|
|
|
|
draw_scene(window, glfwGetTime()); |
|
|
|
|
|
|
|
|
|
glfwSwapBuffers(window); |
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
// 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); |
|
|
|
|
glfwTerminate(); |
|
|
|
|
|
|
|
|
|