diff --git a/CMake/modules/Findosmesa.cmake b/CMake/modules/Findosmesa.cmake new file mode 100644 index 000000000..3194bd91a --- /dev/null +++ b/CMake/modules/Findosmesa.cmake @@ -0,0 +1,18 @@ +# Try to find OSMesa on a Unix system +# +# This will define: +# +# OSMESA_LIBRARIES - Link these to use OSMesa +# OSMESA_INCLUDE_DIR - Include directory for OSMesa +# +# Copyright (c) 2014 Brandon Schaefer + +if (NOT WIN32) + + find_package (PkgConfig) + pkg_check_modules (PKG_OSMESA QUIET osmesa) + + set (OSMESA_INCLUDE_DIR ${PKG_OSMESA_INCLUDE_DIRS}) + set (OSMESA_LIBRARIES ${PKG_OSMESA_LIBRARIES}) + +endif () diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a67af3a0..d299e82fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ endif() if (UNIX AND NOT APPLE) option(GLFW_USE_WAYLAND "Use Wayland for window creation" OFF) option(GLFW_USE_MIR "Use Mir for window creation" OFF) + option(GLFW_USE_OSMESA "Use OSMesa for offscreen context creation" OFF) endif() if (MSVC) @@ -145,6 +146,9 @@ elseif (UNIX) elseif (GLFW_USE_MIR) set(_GLFW_MIR 1) message(STATUS "Using Mir for window creation") + elseif (GLFW_USE_OSMESA) + set(_GLFW_OSMESA 1) + message(STATUS "Using OSMesa for windowless context creation") else() set(_GLFW_X11 1) message(STATUS "Using X11 for window creation") @@ -306,6 +310,18 @@ if (_GLFW_MIR) list(APPEND glfw_LIBRARIES "${XKBCOMMON_LIBRARY}") endif() +#-------------------------------------------------------------------- +# Use OSMesa for offscreen context creation +#-------------------------------------------------------------------- +if (_GLFW_OSMESA) + find_package(osmesa REQUIRED) + list(APPEND glfw_PKG_DEPS "osmesa") + + list(APPEND glfw_INCLUDE_DIRS "${OSMESA_INCLUDE_DIR}") + list(APPEND glfw_LIBRARIES "${OSMESA_LIBRARIES}" + "${CMAKE_THREAD_LIBS_INIT}") +endif() + #-------------------------------------------------------------------- # Use Cocoa for window creation and NSOpenGL for context creation #-------------------------------------------------------------------- diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 138a78166..2e5c2dbd0 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -35,8 +35,16 @@ add_executable(particles WIN32 MACOSX_BUNDLE particles.c ${ICON} ${TINYCTHREAD} add_executable(simple WIN32 MACOSX_BUNDLE simple.c ${ICON} ${GLAD}) add_executable(splitview WIN32 MACOSX_BUNDLE splitview.c ${ICON} ${GLAD}) add_executable(wave WIN32 MACOSX_BUNDLE wave.c ${ICON} ${GLAD}) +if (GLFW_USE_OSMESA) + add_executable(simple_osmesa WIN32 MACOSX_BUNDLE simple_osmesa.c ${ICON} ${GLAD}) + add_executable(particles_osmesa WIN32 MACOSX_BUNDLE particles_osmesa.c ${ICON} ${TINYCTHREAD} ${GETOPT} ${GLAD}) +endif() + target_link_libraries(particles "${CMAKE_THREAD_LIBS_INIT}" "${RT_LIBRARY}") +if (GLFW_USE_OSMESA) + target_link_libraries(particles_osmesa "${CMAKE_THREAD_LIBS_INIT}" "${RT_LIBRARY}") +endif() set(WINDOWS_BINARIES boing gears heightmap particles simple splitview wave) diff --git a/examples/particles_osmesa.c b/examples/particles_osmesa.c new file mode 100644 index 000000000..597ee2741 --- /dev/null +++ b/examples/particles_osmesa.c @@ -0,0 +1,1117 @@ +//======================================================================== +// A simple particle engine with threaded physics +// Copyright (c) Marcus Geelnard +// Copyright (c) Camilla Berglund +// +// 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. +// +// 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: +// +// 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. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#if defined(_MSC_VER) + // Make MS math.h define M_PI + #define _USE_MATH_DEFINES +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +// Define tokens for GL_EXT_separate_specular_color if not already defined +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif // GL_EXT_separate_specular_color + + +//======================================================================== +// Type definitions +//======================================================================== + +typedef struct +{ + float x, y, z; +} Vec3; + +// This structure is used for interleaved vertex arrays (see the +// draw_particles function) +// +// NOTE: This structure SHOULD be packed on most systems. It uses 32-bit fields +// on 32-bit boundaries, and is a multiple of 64 bits in total (6x32=3x64). If +// it does not work, try using pragmas or whatever to force the structure to be +// packed. +typedef struct +{ + GLfloat s, t; // Texture coordinates + GLuint rgba; // Color (four ubytes packed into an uint) + GLfloat x, y, z; // Vertex coordinates +} Vertex; + + +//======================================================================== +// Program control global variables +//======================================================================== + +// Window dimensions +float aspect_ratio; + +// "wireframe" flag (true if we use wireframe view) +int wireframe; + +// Thread synchronization +struct { + double t; // Time (s) + float dt; // Time since last frame (s) + int p_frame; // Particle physics frame number + int d_frame; // Particle draw frame number + cnd_t p_done; // Condition: particle physics done + cnd_t d_done; // Condition: particle draw done + mtx_t particles_lock; // Particles data sharing mutex +} thread_sync; + + +//======================================================================== +// Texture declarations (we hard-code them into the source code, since +// they are so simple) +//======================================================================== + +#define P_TEX_WIDTH 8 // Particle texture dimensions +#define P_TEX_HEIGHT 8 +#define F_TEX_WIDTH 16 // Floor texture dimensions +#define F_TEX_HEIGHT 16 + +// Texture object IDs +GLuint particle_tex_id, floor_tex_id; + +// Particle texture (a simple spot) +const unsigned char particle_texture[ P_TEX_WIDTH * P_TEX_HEIGHT ] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x22, 0x22, 0x11, 0x00, 0x00, + 0x00, 0x11, 0x33, 0x88, 0x77, 0x33, 0x11, 0x00, + 0x00, 0x22, 0x88, 0xff, 0xee, 0x77, 0x22, 0x00, + 0x00, 0x22, 0x77, 0xee, 0xff, 0x88, 0x22, 0x00, + 0x00, 0x11, 0x33, 0x77, 0x88, 0x33, 0x11, 0x00, + 0x00, 0x00, 0x11, 0x33, 0x22, 0x11, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +// Floor texture (your basic checkered floor) +const unsigned char floor_texture[ F_TEX_WIDTH * F_TEX_HEIGHT ] = { + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0xff, 0xf0, 0xcc, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0xf0, 0xcc, 0xee, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0x66, 0x30, 0x30, 0x30, 0x20, 0x30, 0x30, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xee, 0xf0, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0xf0, 0xf0, 0xf0, 0xf0, 0xcc, 0xf0, 0xf0, 0xf0, 0x30, 0x30, 0x55, 0x30, 0x30, 0x44, 0x30, 0x30, + 0xf0, 0xdd, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0xf0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x60, 0x30, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x33, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x20, 0x30, 0x30, 0xf0, 0xff, 0xf0, 0xf0, 0xdd, 0xf0, 0xf0, 0xff, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x55, 0x33, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0xf0, + 0x30, 0x44, 0x66, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xaa, 0xf0, 0xf0, 0xcc, 0xf0, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xff, 0xf0, 0xf0, 0xf0, 0xff, 0xf0, 0xdd, 0xf0, + 0x30, 0x30, 0x30, 0x77, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, +}; + + +//======================================================================== +// These are fixed constants that control the particle engine. In a +// modular world, these values should be variables... +//======================================================================== + +// Maximum number of particles +#define MAX_PARTICLES 3000 + +// Life span of a particle (in seconds) +#define LIFE_SPAN 8.f + +// A new particle is born every [BIRTH_INTERVAL] second +#define BIRTH_INTERVAL (LIFE_SPAN/(float)MAX_PARTICLES) + +// Particle size (meters) +#define PARTICLE_SIZE 0.7f + +// Gravitational constant (m/s^2) +#define GRAVITY 9.8f + +// Base initial velocity (m/s) +#define VELOCITY 8.f + +// Bounce friction (1.0 = no friction, 0.0 = maximum friction) +#define FRICTION 0.75f + +// "Fountain" height (m) +#define FOUNTAIN_HEIGHT 3.f + +// Fountain radius (m) +#define FOUNTAIN_RADIUS 1.6f + +// Minimum delta-time for particle phisics (s) +#define MIN_DELTA_T (BIRTH_INTERVAL * 0.5f) + + +//======================================================================== +// Particle system global variables +//======================================================================== + +// This structure holds all state for a single particle +typedef struct { + float x,y,z; // Position in space + float vx,vy,vz; // Velocity vector + float r,g,b; // Color of particle + float life; // Life of particle (1.0 = newborn, < 0.0 = dead) + int active; // Tells if this particle is active +} PARTICLE; + +// Global vectors holding all particles. We use two vectors for double +// buffering. +static PARTICLE particles[MAX_PARTICLES]; + +// Global variable holding the age of the youngest particle +static float min_age; + +// Color of latest born particle (used for fountain lighting) +static float glow_color[4]; + +// Position of latest born particle (used for fountain lighting) +static float glow_pos[4]; + + +//======================================================================== +// Object material and fog configuration constants +//======================================================================== + +const GLfloat fountain_diffuse[4] = { 0.7f, 1.f, 1.f, 1.f }; +const GLfloat fountain_specular[4] = { 1.f, 1.f, 1.f, 1.f }; +const GLfloat fountain_shininess = 12.f; +const GLfloat floor_diffuse[4] = { 1.f, 0.6f, 0.6f, 1.f }; +const GLfloat floor_specular[4] = { 0.6f, 0.6f, 0.6f, 1.f }; +const GLfloat floor_shininess = 18.f; +const GLfloat fog_color[4] = { 0.1f, 0.1f, 0.1f, 1.f }; + + +//======================================================================== +// Print usage information +//======================================================================== + +static void usage(void) +{ + printf("Usage: particles [-bfhs]\n"); + printf("Options:\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"); + printf(" Esc Exit program\n"); +} + + +//======================================================================== +// Initialize a new particle +//======================================================================== + +static void init_particle(PARTICLE *p, double t) +{ + float xy_angle, velocity; + + // Start position of particle is at the fountain blow-out + p->x = 0.f; + p->y = 0.f; + p->z = FOUNTAIN_HEIGHT; + + // Start velocity is up (Z)... + p->vz = 0.7f + (0.3f / 4096.f) * (float) (rand() & 4095); + + // ...and a randomly chosen X/Y direction + xy_angle = (2.f * (float) M_PI / 4096.f) * (float) (rand() & 4095); + p->vx = 0.4f * (float) cos(xy_angle); + p->vy = 0.4f * (float) sin(xy_angle); + + // Scale velocity vector according to a time-varying velocity + velocity = VELOCITY * (0.8f + 0.1f * (float) (sin(0.5 * t) + sin(1.31 * t))); + p->vx *= velocity; + p->vy *= velocity; + p->vz *= velocity; + + // Color is time-varying + p->r = 0.7f + 0.3f * (float) sin(0.34 * t + 0.1); + p->g = 0.6f + 0.4f * (float) sin(0.63 * t + 1.1); + p->b = 0.6f + 0.4f * (float) sin(0.91 * t + 2.1); + + // Store settings for fountain glow lighting + glow_pos[0] = 0.4f * (float) sin(1.34 * t); + glow_pos[1] = 0.4f * (float) sin(3.11 * t); + glow_pos[2] = FOUNTAIN_HEIGHT + 1.f; + glow_pos[3] = 1.f; + glow_color[0] = p->r; + glow_color[1] = p->g; + glow_color[2] = p->b; + glow_color[3] = 1.f; + + // The particle is new-born and active + p->life = 1.f; + p->active = 1; +} + + +//======================================================================== +// Update a particle +//======================================================================== + +#define FOUNTAIN_R2 (FOUNTAIN_RADIUS+PARTICLE_SIZE/2)*(FOUNTAIN_RADIUS+PARTICLE_SIZE/2) + +static void update_particle(PARTICLE *p, float dt) +{ + // If the particle is not active, we need not do anything + if (!p->active) + return; + + // The particle is getting older... + p->life -= dt * (1.f / LIFE_SPAN); + + // Did the particle die? + if (p->life <= 0.f) + { + p->active = 0; + return; + } + + // Apply gravity + p->vz = p->vz - GRAVITY * dt; + + // Update particle position + p->x = p->x + p->vx * dt; + p->y = p->y + p->vy * dt; + p->z = p->z + p->vz * dt; + + // Simple collision detection + response + if (p->vz < 0.f) + { + // Particles should bounce on the fountain (with friction) + if ((p->x * p->x + p->y * p->y) < FOUNTAIN_R2 && + p->z < (FOUNTAIN_HEIGHT + PARTICLE_SIZE / 2)) + { + p->vz = -FRICTION * p->vz; + p->z = FOUNTAIN_HEIGHT + PARTICLE_SIZE / 2 + + FRICTION * (FOUNTAIN_HEIGHT + + PARTICLE_SIZE / 2 - p->z); + } + + // Particles should bounce on the floor (with friction) + else if (p->z < PARTICLE_SIZE / 2) + { + p->vz = -FRICTION * p->vz; + p->z = PARTICLE_SIZE / 2 + + FRICTION * (PARTICLE_SIZE / 2 - p->z); + } + } +} + + +//======================================================================== +// The main frame for the particle engine. Called once per frame. +//======================================================================== + +static void particle_engine(double t, float dt) +{ + int i; + float dt2; + + // Update particles (iterated several times per frame if dt is too large) + while (dt > 0.f) + { + // Calculate delta time for this iteration + dt2 = dt < MIN_DELTA_T ? dt : MIN_DELTA_T; + + for (i = 0; i < MAX_PARTICLES; i++) + update_particle(&particles[i], dt2); + + min_age += dt2; + + // Should we create any new particle(s)? + while (min_age >= BIRTH_INTERVAL) + { + min_age -= BIRTH_INTERVAL; + + // Find a dead particle to replace with a new one + for (i = 0; i < MAX_PARTICLES; i++) + { + if (!particles[i].active) + { + init_particle(&particles[i], t + min_age); + update_particle(&particles[i], min_age); + break; + } + } + } + + dt -= dt2; + } +} + + +//======================================================================== +// Draw all active particles. We use OpenGL 1.1 vertex +// arrays for this in order to accelerate the drawing. +//======================================================================== + +#define BATCH_PARTICLES 70 // Number of particles to draw in each batch + // (70 corresponds to 7.5 KB = will not blow + // the L1 data cache on most CPUs) +#define PARTICLE_VERTS 4 // Number of vertices per particle + +static void draw_particles(GLFWwindow* window, double t, float dt) +{ + int i, particle_count; + Vertex vertex_array[BATCH_PARTICLES * PARTICLE_VERTS]; + Vertex* vptr; + float alpha; + GLuint rgba; + Vec3 quad_lower_left, quad_lower_right; + GLfloat mat[16]; + PARTICLE* pptr; + + // Here comes the real trick with flat single primitive objects (s.c. + // "billboards"): We must rotate the textured primitive so that it + // always faces the viewer (is coplanar with the view-plane). + // We: + // 1) Create the primitive around origo (0,0,0) + // 2) Rotate it so that it is coplanar with the view plane + // 3) Translate it according to the particle position + // Note that 1) and 2) is the same for all particles (done only once). + + // Get modelview matrix. We will only use the upper left 3x3 part of + // the matrix, which represents the rotation. + glGetFloatv(GL_MODELVIEW_MATRIX, mat); + + // 1) & 2) We do it in one swift step: + // Although not obvious, the following six lines represent two matrix/ + // vector multiplications. The matrix is the inverse 3x3 rotation + // matrix (i.e. the transpose of the same matrix), and the two vectors + // represent the lower left corner of the quad, PARTICLE_SIZE/2 * + // (-1,-1,0), and the lower right corner, PARTICLE_SIZE/2 * (1,-1,0). + // The upper left/right corners of the quad is always the negative of + // the opposite corners (regardless of rotation). + quad_lower_left.x = (-PARTICLE_SIZE / 2) * (mat[0] + mat[1]); + quad_lower_left.y = (-PARTICLE_SIZE / 2) * (mat[4] + mat[5]); + quad_lower_left.z = (-PARTICLE_SIZE / 2) * (mat[8] + mat[9]); + quad_lower_right.x = (PARTICLE_SIZE / 2) * (mat[0] - mat[1]); + quad_lower_right.y = (PARTICLE_SIZE / 2) * (mat[4] - mat[5]); + quad_lower_right.z = (PARTICLE_SIZE / 2) * (mat[8] - mat[9]); + + // Don't update z-buffer, since all particles are transparent! + glDepthMask(GL_FALSE); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + // Select particle texture + if (!wireframe) + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, particle_tex_id); + } + + // Set up vertex arrays. We use interleaved arrays, which is easier to + // handle (in most situations) and it gives a linear memeory access + // access pattern (which may give better performance in some + // situations). GL_T2F_C4UB_V3F means: 2 floats for texture coords, + // 4 ubytes for color and 3 floats for vertex coord (in that order). + // Most OpenGL cards / drivers are optimized for this format. + glInterleavedArrays(GL_T2F_C4UB_V3F, 0, vertex_array); + + // Wait for particle physics thread to be done + mtx_lock(&thread_sync.particles_lock); + while (!glfwWindowShouldClose(window) && + thread_sync.p_frame <= thread_sync.d_frame) + { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += 100 * 1000 * 1000; + ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000); + ts.tv_nsec %= 1000 * 1000 * 1000; + cnd_timedwait(&thread_sync.p_done, &thread_sync.particles_lock, &ts); + } + + // Store the frame time and delta time for the physics thread + thread_sync.t = t; + thread_sync.dt = dt; + + // Update frame counter + thread_sync.d_frame++; + + // Loop through all particles and build vertex arrays. + particle_count = 0; + vptr = vertex_array; + pptr = particles; + + for (i = 0; i < MAX_PARTICLES; i++) + { + if (pptr->active) + { + // Calculate particle intensity (we set it to max during 75% + // of its life, then it fades out) + alpha = 4.f * pptr->life; + if (alpha > 1.f) + alpha = 1.f; + + // Convert color from float to 8-bit (store it in a 32-bit + // integer using endian independent type casting) + ((GLubyte*) &rgba)[0] = (GLubyte)(pptr->r * 255.f); + ((GLubyte*) &rgba)[1] = (GLubyte)(pptr->g * 255.f); + ((GLubyte*) &rgba)[2] = (GLubyte)(pptr->b * 255.f); + ((GLubyte*) &rgba)[3] = (GLubyte)(alpha * 255.f); + + // 3) Translate the quad to the correct position in modelview + // space and store its parameters in vertex arrays (we also + // store texture coord and color information for each vertex). + + // Lower left corner + vptr->s = 0.f; + vptr->t = 0.f; + vptr->rgba = rgba; + vptr->x = pptr->x + quad_lower_left.x; + vptr->y = pptr->y + quad_lower_left.y; + vptr->z = pptr->z + quad_lower_left.z; + vptr ++; + + // Lower right corner + vptr->s = 1.f; + vptr->t = 0.f; + vptr->rgba = rgba; + vptr->x = pptr->x + quad_lower_right.x; + vptr->y = pptr->y + quad_lower_right.y; + vptr->z = pptr->z + quad_lower_right.z; + vptr ++; + + // Upper right corner + vptr->s = 1.f; + vptr->t = 1.f; + vptr->rgba = rgba; + vptr->x = pptr->x - quad_lower_left.x; + vptr->y = pptr->y - quad_lower_left.y; + vptr->z = pptr->z - quad_lower_left.z; + vptr ++; + + // Upper left corner + vptr->s = 0.f; + vptr->t = 1.f; + vptr->rgba = rgba; + vptr->x = pptr->x - quad_lower_right.x; + vptr->y = pptr->y - quad_lower_right.y; + vptr->z = pptr->z - quad_lower_right.z; + vptr ++; + + // Increase count of drawable particles + particle_count ++; + } + + // If we have filled up one batch of particles, draw it as a set + // of quads using glDrawArrays. + if (particle_count >= BATCH_PARTICLES) + { + // The first argument tells which primitive type we use (QUAD) + // The second argument tells the index of the first vertex (0) + // The last argument is the vertex count + glDrawArrays(GL_QUADS, 0, PARTICLE_VERTS * particle_count); + particle_count = 0; + vptr = vertex_array; + } + + // Next particle + pptr++; + } + + // 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); + + // Disable vertex arrays (Note: glInterleavedArrays implicitly called + // glEnableClientState for vertex, texture coord and color arrays) + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + + glDepthMask(GL_TRUE); +} + + +//======================================================================== +// Fountain geometry specification +//======================================================================== + +#define FOUNTAIN_SIDE_POINTS 14 +#define FOUNTAIN_SWEEP_STEPS 32 + +static const float fountain_side[FOUNTAIN_SIDE_POINTS * 2] = +{ + 1.2f, 0.f, 1.f, 0.2f, 0.41f, 0.3f, 0.4f, 0.35f, + 0.4f, 1.95f, 0.41f, 2.f, 0.8f, 2.2f, 1.2f, 2.4f, + 1.5f, 2.7f, 1.55f,2.95f, 1.6f, 3.f, 1.f, 3.f, + 0.5f, 3.f, 0.f, 3.f +}; + +static const float fountain_normal[FOUNTAIN_SIDE_POINTS * 2] = +{ + 1.0000f, 0.0000f, 0.6428f, 0.7660f, 0.3420f, 0.9397f, 1.0000f, 0.0000f, + 1.0000f, 0.0000f, 0.3420f,-0.9397f, 0.4226f,-0.9063f, 0.5000f,-0.8660f, + 0.7660f,-0.6428f, 0.9063f,-0.4226f, 0.0000f,1.00000f, 0.0000f,1.00000f, + 0.0000f,1.00000f, 0.0000f,1.00000f +}; + + +//======================================================================== +// Draw a fountain +//======================================================================== + +static void draw_fountain(void) +{ + static GLuint fountain_list = 0; + double angle; + float x, y; + int m, n; + + // The first time, we build the fountain display list + if (!fountain_list) + { + fountain_list = glGenLists(1); + glNewList(fountain_list, GL_COMPILE_AND_EXECUTE); + + glMaterialfv(GL_FRONT, GL_DIFFUSE, fountain_diffuse); + glMaterialfv(GL_FRONT, GL_SPECULAR, fountain_specular); + glMaterialf(GL_FRONT, GL_SHININESS, fountain_shininess); + + // Build fountain using triangle strips + for (n = 0; n < FOUNTAIN_SIDE_POINTS - 1; n++) + { + glBegin(GL_TRIANGLE_STRIP); + for (m = 0; m <= FOUNTAIN_SWEEP_STEPS; m++) + { + angle = (double) m * (2.0 * M_PI / (double) FOUNTAIN_SWEEP_STEPS); + x = (float) cos(angle); + y = (float) sin(angle); + + // Draw triangle strip + glNormal3f(x * fountain_normal[n * 2 + 2], + y * fountain_normal[n * 2 + 2], + fountain_normal[n * 2 + 3]); + glVertex3f(x * fountain_side[n * 2 + 2], + y * fountain_side[n * 2 + 2], + fountain_side[n * 2 +3 ]); + glNormal3f(x * fountain_normal[n * 2], + y * fountain_normal[n * 2], + fountain_normal[n * 2 + 1]); + glVertex3f(x * fountain_side[n * 2], + y * fountain_side[n * 2], + fountain_side[n * 2 + 1]); + } + + glEnd(); + } + + glEndList(); + } + else + glCallList(fountain_list); +} + + +//======================================================================== +// Recursive function for building variable tesselated floor +//======================================================================== + +static void tessellate_floor(float x1, float y1, float x2, float y2, int depth) +{ + float delta, x, y; + + // Last recursion? + if (depth >= 5) + delta = 999999.f; + else + { + x = (float) (fabs(x1) < fabs(x2) ? fabs(x1) : fabs(x2)); + y = (float) (fabs(y1) < fabs(y2) ? fabs(y1) : fabs(y2)); + delta = x*x + y*y; + } + + // Recurse further? + if (delta < 0.1f) + { + x = (x1 + x2) * 0.5f; + y = (y1 + y2) * 0.5f; + tessellate_floor(x1, y1, x, y, depth + 1); + tessellate_floor(x, y1, x2, y, depth + 1); + tessellate_floor(x1, y, x, y2, depth + 1); + tessellate_floor(x, y, x2, y2, depth + 1); + } + else + { + glTexCoord2f(x1 * 30.f, y1 * 30.f); + glVertex3f( x1 * 80.f, y1 * 80.f, 0.f); + glTexCoord2f(x2 * 30.f, y1 * 30.f); + glVertex3f( x2 * 80.f, y1 * 80.f, 0.f); + glTexCoord2f(x2 * 30.f, y2 * 30.f); + glVertex3f( x2 * 80.f, y2 * 80.f, 0.f); + glTexCoord2f(x1 * 30.f, y2 * 30.f); + glVertex3f( x1 * 80.f, y2 * 80.f, 0.f); + } +} + + +//======================================================================== +// Draw floor. We build the floor recursively and let the tessellation in the +// center (near x,y=0,0) be high, while the tessellation around the edges be +// low. +//======================================================================== + +static void draw_floor(void) +{ + static GLuint floor_list = 0; + + if (!wireframe) + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, floor_tex_id); + } + + // The first time, we build the floor display list + if (!floor_list) + { + floor_list = glGenLists(1); + glNewList(floor_list, GL_COMPILE_AND_EXECUTE); + + glMaterialfv(GL_FRONT, GL_DIFFUSE, floor_diffuse); + glMaterialfv(GL_FRONT, GL_SPECULAR, floor_specular); + glMaterialf(GL_FRONT, GL_SHININESS, floor_shininess); + + // Draw floor as a bunch of triangle strips (high tesselation + // improves lighting) + glNormal3f(0.f, 0.f, 1.f); + glBegin(GL_QUADS); + tessellate_floor(-1.f, -1.f, 0.f, 0.f, 0); + tessellate_floor( 0.f, -1.f, 1.f, 0.f, 0); + tessellate_floor( 0.f, 0.f, 1.f, 1.f, 0); + tessellate_floor(-1.f, 0.f, 0.f, 1.f, 0); + glEnd(); + + glEndList(); + } + else + glCallList(floor_list); + + glDisable(GL_TEXTURE_2D); + +} + + +//======================================================================== +// Position and configure light sources +//======================================================================== + +static void setup_lights(void) +{ + float l1pos[4], l1amb[4], l1dif[4], l1spec[4]; + float l2pos[4], l2amb[4], l2dif[4], l2spec[4]; + + // Set light source 1 parameters + l1pos[0] = 0.f; l1pos[1] = -9.f; l1pos[2] = 8.f; l1pos[3] = 1.f; + l1amb[0] = 0.2f; l1amb[1] = 0.2f; l1amb[2] = 0.2f; l1amb[3] = 1.f; + l1dif[0] = 0.8f; l1dif[1] = 0.4f; l1dif[2] = 0.2f; l1dif[3] = 1.f; + l1spec[0] = 1.f; l1spec[1] = 0.6f; l1spec[2] = 0.2f; l1spec[3] = 0.f; + + // Set light source 2 parameters + l2pos[0] = -15.f; l2pos[1] = 12.f; l2pos[2] = 1.5f; l2pos[3] = 1.f; + l2amb[0] = 0.f; l2amb[1] = 0.f; l2amb[2] = 0.f; l2amb[3] = 1.f; + l2dif[0] = 0.2f; l2dif[1] = 0.4f; l2dif[2] = 0.8f; l2dif[3] = 1.f; + l2spec[0] = 0.2f; l2spec[1] = 0.6f; l2spec[2] = 1.f; l2spec[3] = 0.f; + + glLightfv(GL_LIGHT1, GL_POSITION, l1pos); + glLightfv(GL_LIGHT1, GL_AMBIENT, l1amb); + glLightfv(GL_LIGHT1, GL_DIFFUSE, l1dif); + glLightfv(GL_LIGHT1, GL_SPECULAR, l1spec); + glLightfv(GL_LIGHT2, GL_POSITION, l2pos); + glLightfv(GL_LIGHT2, GL_AMBIENT, l2amb); + glLightfv(GL_LIGHT2, GL_DIFFUSE, l2dif); + glLightfv(GL_LIGHT2, GL_SPECULAR, l2spec); + glLightfv(GL_LIGHT3, GL_POSITION, glow_pos); + glLightfv(GL_LIGHT3, GL_DIFFUSE, glow_color); + glLightfv(GL_LIGHT3, GL_SPECULAR, glow_color); + + glEnable(GL_LIGHT1); + glEnable(GL_LIGHT2); + glEnable(GL_LIGHT3); +} + + +//======================================================================== +// Main rendering function +//======================================================================== + +static void draw_scene(GLFWwindow* window, double t) +{ + double xpos, ypos, zpos, angle_x, angle_y, angle_z; + static double t_old = 0.0; + float dt; + mat4x4 projection; + + // Calculate frame-to-frame delta time + dt = (float) (t - t_old); + t_old = t; + + mat4x4_perspective(projection, + 65.f * (float) M_PI / 180.f, + aspect_ratio, + 1.0, 60.0); + + glClearColor(0.1f, 0.1f, 0.1f, 1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadMatrixf((const GLfloat*) projection); + + // Setup camera + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // Rotate camera + angle_x = 90.0 - 10.0; + angle_y = 10.0 * sin(0.3 * t); + angle_z = 10.0 * t; + glRotated(-angle_x, 1.0, 0.0, 0.0); + glRotated(-angle_y, 0.0, 1.0, 0.0); + glRotated(-angle_z, 0.0, 0.0, 1.0); + + // Translate camera + xpos = 15.0 * sin((M_PI / 180.0) * angle_z) + + 2.0 * sin((M_PI / 180.0) * 3.1 * t); + ypos = -15.0 * cos((M_PI / 180.0) * angle_z) + + 2.0 * cos((M_PI / 180.0) * 2.9 * t); + zpos = 4.0 + 2.0 * cos((M_PI / 180.0) * 4.9 * t); + glTranslated(-xpos, -ypos, -zpos); + + glFrontFace(GL_CCW); + glCullFace(GL_BACK); + glEnable(GL_CULL_FACE); + + setup_lights(); + glEnable(GL_LIGHTING); + + glEnable(GL_FOG); + glFogi(GL_FOG_MODE, GL_EXP); + glFogf(GL_FOG_DENSITY, 0.05f); + glFogfv(GL_FOG_COLOR, fog_color); + + draw_floor(); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + + draw_fountain(); + + glDisable(GL_LIGHTING); + glDisable(GL_FOG); + + // Particles must be drawn after all solid objects have been drawn + draw_particles(window, t, dt); + + // Z-buffer not needed anymore + glDisable(GL_DEPTH_TEST); +} + + +//======================================================================== +// Window resize callback function +//======================================================================== + +static void resize_callback(GLFWwindow* window, int width, int height) +{ + glViewport(0, 0, width, height); + aspect_ratio = height ? width / (float) height : 1.f; +} + + +//======================================================================== +// Key callback functions +//======================================================================== + +static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + if (action == GLFW_PRESS) + { + switch (key) + { + case GLFW_KEY_ESCAPE: + glfwSetWindowShouldClose(window, GLFW_TRUE); + break; + case GLFW_KEY_W: + wireframe = !wireframe; + glPolygonMode(GL_FRONT_AND_BACK, + wireframe ? GL_LINE : GL_FILL); + break; + default: + break; + } + } +} + + +//======================================================================== +// Thread for updating particle physics +//======================================================================== + +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 (!glfwWindowShouldClose(window) && + thread_sync.p_frame > thread_sync.d_frame) + { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += 100 * 1000 * 1000; + ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000); + ts.tv_nsec %= 1000 * 1000 * 1000; + cnd_timedwait(&thread_sync.d_done, &thread_sync.particles_lock, &ts); + } + + if (glfwWindowShouldClose(window)) + break; + + // Update particles + particle_engine(thread_sync.t, thread_sync.dt); + + // Update frame counter + thread_sync.p_frame++; + + // Unlock mutex and signal drawing thread + mtx_unlock(&thread_sync.particles_lock); + cnd_signal(&thread_sync.p_done); + } + + return 0; +} + +// ======================================================================== +// Dumps the window's color buffer in ImageMagick .miff format. +// ======================================================================== +void dump_framebuffer(GLFWwindow* window) +{ + // Switch to the given context. + GLFWwindow* old = glfwGetCurrentContext(); + glfwMakeContextCurrent(window); + + // Fetch the framebuffer. + int width; + int height; + void* buffer; + glfwGetFramebufferSize(window, &width, &height); + buffer = malloc(width * height * 4); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + + // Write to an ImageMagick .miff file. + FILE *fp = fopen("particles.miff", "w"); + fprintf(fp, "id=ImageMagick version=1.0\n"); + fprintf(fp, "class=DirectClass matte=True\n"); + fprintf(fp, "columns=%d rows=%d\n", width, height); + fprintf(fp, "\f\n:\032"); + void* ptr; + for (ptr = buffer + (height - 1) * width * 4; + ptr >= buffer; + ptr -= width * 4) + { + fwrite(ptr, 1, width * 4, fp); + } + fclose(fp); + printf("Wrote particles.miff!\n"); + + // Toss the buffer. + free(buffer); + + // Restore the original context. + glfwMakeContextCurrent(old); +} + +//======================================================================== +// main +//======================================================================== + +int main(int argc, char** argv) +{ + int ch, width, height; + thrd_t physics_thread = 0; + GLFWwindow* window; + GLFWmonitor* monitor = NULL; + + if (!glfwInit()) + { + fprintf(stderr, "Failed to initialize GLFW\n"); + exit(EXIT_FAILURE); + } + + while ((ch = getopt(argc, argv, "fh")) != -1) + { + 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) + { + fprintf(stderr, "Failed to create GLFW window\n"); + glfwTerminate(); + exit(EXIT_FAILURE); + } + + if (monitor) + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + glfwMakeContextCurrent(window); + gladLoadGLLoader((GLADloadproc) glfwGetProcAddress); + glfwSwapInterval(1); + + glfwSetFramebufferSizeCallback(window, resize_callback); + glfwSetKeyCallback(window, key_callback); + + // Set initial aspect ratio + glfwGetFramebufferSize(window, &width, &height); + resize_callback(window, width, height); + + // Upload particle texture + glGenTextures(1, &particle_tex_id); + glBindTexture(GL_TEXTURE_2D, particle_tex_id); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, P_TEX_WIDTH, P_TEX_HEIGHT, + 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, particle_texture); + + // Upload floor texture + glGenTextures(1, &floor_tex_id); + glBindTexture(GL_TEXTURE_2D, floor_tex_id); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, F_TEX_WIDTH, F_TEX_HEIGHT, + 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, floor_texture); + + if (glfwExtensionSupported("GL_EXT_separate_specular_color")) + { + glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL_EXT, + GL_SEPARATE_SPECULAR_COLOR_EXT); + } + + // Set filled polygon mode as default (not wireframe) + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + wireframe = 0; + + // Set initial times + thread_sync.t = 0.0; + thread_sync.dt = 0.001f; + 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, window) != thrd_success) + { + glfwTerminate(); + exit(EXIT_FAILURE); + } + + glfwSetTime(0.0); + + while (!glfwWindowShouldClose(window)) + { + draw_scene(window, glfwGetTime()); + + glfwSwapBuffers(window); + glfwPollEvents(); + + if (glfwGetTime() > 5.0) + { + dump_framebuffer(window); + glfwSetWindowShouldClose(window, GLFW_TRUE); + } + } + + thrd_join(physics_thread, NULL); + + glfwDestroyWindow(window); + glfwTerminate(); + + exit(EXIT_SUCCESS); +} + diff --git a/examples/simple_osmesa.c b/examples/simple_osmesa.c new file mode 100644 index 000000000..c9dde74a1 --- /dev/null +++ b/examples/simple_osmesa.c @@ -0,0 +1,209 @@ +//======================================================================== +// Simple GLFW example +// Copyright (c) Camilla Berglund +// +// 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. +// +// 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: +// +// 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. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== +//! [code] +// + +#include +#include + +#include "linmath.h" + +#include +#include + + +static const struct +{ + float x, y; + float r, g, b; +} vertices[3] = +{ + { -0.6f, -0.4f, 1.f, 0.f, 0.f }, + { 0.6f, -0.4f, 0.f, 1.f, 0.f }, + { 0.f, 0.6f, 0.f, 0.f, 1.f } +}; + +static const char* vertex_shader_text = +"uniform mat4 MVP;\n" +"attribute vec3 vCol;\n" +"attribute vec2 vPos;\n" +"varying vec3 color;\n" +"void main()\n" +"{\n" +" gl_Position = MVP * vec4(vPos, 0.0, 1.0);\n" +" color = vCol;\n" +"}\n"; + +static const char* fragment_shader_text = +"varying vec3 color;\n" +"void main()\n" +"{\n" +" gl_FragColor = vec4(color, 1.0);\n" +"}\n"; + +static void error_callback(int error, const char* description) +{ + fprintf(stderr, "Error: %s\n", description); +} + +static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) + glfwSetWindowShouldClose(window, GLFW_TRUE); +} + +// Dumps the window's color buffer in ImageMagick .miff format. +void dumpFramebuffer(GLFWwindow* window) +{ + // Switch to the given context. + GLFWwindow* old = glfwGetCurrentContext(); + glfwMakeContextCurrent(window); + + // Fetch the framebuffer. + int width; + int height; + void* buffer; + glfwGetFramebufferSize(window, &width, &height); + buffer = malloc(width * height * 4); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + + // Write to an ImageMagick .miff file. + FILE *fp = fopen("simple.miff", "w"); + fprintf(fp, "id=ImageMagick version=1.0\n"); + fprintf(fp, "class=DirectClass matte=True\n"); + fprintf(fp, "columns=%d rows=%d\n", width, height); + fprintf(fp, "\f\n:\032"); + void* ptr; + for (ptr = buffer + (height - 1) * width * 4; + ptr >= buffer; + ptr -= width * 4) + { + fwrite(ptr, 1, width * 4, fp); + } + fclose(fp); + printf("Wrote simple.miff!\n"); + + // Toss the buffer. + free(buffer); + + // Restore the original context. + glfwMakeContextCurrent(old); +} + +int main(void) +{ + GLFWwindow* window; + GLuint vertex_buffer, vertex_shader, fragment_shader, program; + GLint mvp_location, vpos_location, vcol_location; + + glfwSetErrorCallback(error_callback); + + if (!glfwInit()) + exit(EXIT_FAILURE); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + + window = glfwCreateWindow(640, 480, "Simple example", NULL, NULL); + if (!window) + { + glfwTerminate(); + exit(EXIT_FAILURE); + } + + glfwSetKeyCallback(window, key_callback); + + glfwMakeContextCurrent(window); + gladLoadGLLoader((GLADloadproc) glfwGetProcAddress); + glfwSwapInterval(1); + + // NOTE: OpenGL error checks have been omitted for brevity + + glGenBuffers(1, &vertex_buffer); + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + vertex_shader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex_shader, 1, &vertex_shader_text, NULL); + glCompileShader(vertex_shader); + + fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment_shader, 1, &fragment_shader_text, NULL); + glCompileShader(fragment_shader); + + program = glCreateProgram(); + glAttachShader(program, vertex_shader); + glAttachShader(program, fragment_shader); + glLinkProgram(program); + + mvp_location = glGetUniformLocation(program, "MVP"); + vpos_location = glGetAttribLocation(program, "vPos"); + vcol_location = glGetAttribLocation(program, "vCol"); + + glEnableVertexAttribArray(vpos_location); + glVertexAttribPointer(vpos_location, 2, GL_FLOAT, GL_FALSE, + sizeof(float) * 5, (void*) 0); + glEnableVertexAttribArray(vcol_location); + glVertexAttribPointer(vcol_location, 3, GL_FLOAT, GL_FALSE, + sizeof(float) * 5, (void*) (sizeof(float) * 2)); + + int snap = 5; + while (!glfwWindowShouldClose(window)) + { + float ratio; + int width, height; + mat4x4 m, p, mvp; + + glfwGetFramebufferSize(window, &width, &height); + ratio = width / (float) height; + + glViewport(0, 0, width, height); + glClear(GL_COLOR_BUFFER_BIT); + + mat4x4_identity(m); + mat4x4_rotate_Z(m, m, (float) glfwGetTime()); + mat4x4_ortho(p, -ratio, ratio, -1.f, 1.f, 1.f, -1.f); + mat4x4_mul(mvp, p, m); + + glUseProgram(program); + glUniformMatrix4fv(mvp_location, 1, GL_FALSE, (const GLfloat*) mvp); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glfwSwapBuffers(window); + glfwPollEvents(); + if (--snap == 0) + { + dumpFramebuffer(window); + glfwSetWindowShouldClose(window, GLFW_TRUE); + } + } + + glfwDestroyWindow(window); + + glfwTerminate(); + exit(EXIT_SUCCESS); +} + +//! [code] diff --git a/include/GLFW/glfw3native.h b/include/GLFW/glfw3native.h index 056bc8292..65c094732 100644 --- a/include/GLFW/glfw3native.h +++ b/include/GLFW/glfw3native.h @@ -448,6 +448,30 @@ GLFWAPI EGLContext glfwGetEGLContext(GLFWwindow* window); GLFWAPI EGLSurface glfwGetEGLSurface(GLFWwindow* window); #endif +#if defined(GLFW_EXPOSE_NATIVE_OSMESA) +/*! @brief Returns the color buffer associated with the specified window. + * + * @param[out] width The width of the color buffer. + * @param[out] height The height of the color buffer. + * @param[out] format The pixel format of the color buffer (OSMESA_FORMAT_*). + * @param[out] buffer The buffer data. + * @return 1 if successful, or 0 if not. + */ +GLFWAPI int glfwGetOSMesaColorBuffer(GLFWwindow* window, int* width, + int* height, int* format, void** buffer); + +/*! @brief Returns the depth buffer associated with the specified window. + * + * @param[out] width The width of the depth buffer. + * @param[out] height The height of the depth buffer. + * @param[out] bytesPerValue The number of bytes per depth buffer element. + * @param[out] buffer The buffer data. + * @return 1 if successful, or 0 if not. + */ +GLFWAPI int glfwGetOSMesaDepthBuffer(GLFWwindow* window, int* width, + int* height, int* bytesPerValue, void** buffer); +#endif + #ifdef __cplusplus } #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5042aba38..dd50d8d7a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -45,6 +45,12 @@ elseif (_GLFW_MIR) set(glfw_SOURCES ${common_SOURCES} mir_init.c mir_monitor.c mir_window.c linux_joystick.c posix_time.c posix_tls.c xkb_unicode.c egl_context.c) +elseif (_GLFW_OSMESA) + set(glfw_HEADERS ${common_HEADERS} osmesa_platform.h + posix_time.h posix_tls.h osmesa_context.h) + set(glfw_SOURCES ${common_SOURCES} osmesa_init.c osmesa_monitor.c + osmesa_window.c posix_time.c posix_tls.c + osmesa_context.c) endif() if (APPLE) diff --git a/src/glfw_config.h.in b/src/glfw_config.h.in index f709726f0..153979f53 100644 --- a/src/glfw_config.h.in +++ b/src/glfw_config.h.in @@ -44,6 +44,8 @@ #cmakedefine _GLFW_WAYLAND // Define this to 1 if building GLFW for Mir #cmakedefine _GLFW_MIR +// Define this to 1 if building GLFW for OSMesa +#cmakedefine _GLFW_OSMESA // Define this to 1 if building as a shared library / dynamic library / DLL #cmakedefine _GLFW_BUILD_DLL diff --git a/src/internal.h b/src/internal.h index 0f35146bd..1f2525dad 100644 --- a/src/internal.h +++ b/src/internal.h @@ -171,6 +171,8 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void); #include "wl_platform.h" #elif defined(_GLFW_MIR) #include "mir_platform.h" +#elif defined(_GLFW_OSMESA) + #include "osmesa_platform.h" #else #error "No supported window creation API selected" #endif diff --git a/src/osmesa_context.c b/src/osmesa_context.c new file mode 100644 index 000000000..a04a7dc14 --- /dev/null +++ b/src/osmesa_context.c @@ -0,0 +1,213 @@ + +#include +#include + +#include "internal.h" +#include "osmesa_context.h" + +static void makeContextCurrentOSMesa(_GLFWwindow* window) +{ + if (window) + { + // Check to see if we need to allocate a new buffer. + if ((window->context.osmesa.buffer == NULL) || + (window->osmesa.width != window->context.osmesa.width) || + (window->osmesa.height != window->context.osmesa.height)) + { + // Free the current buffer, if necessary. + if (window->context.osmesa.buffer != NULL) + { + free(window->context.osmesa.buffer); + } + + // Allocate the new buffer (width * height * 1 byte per RGBA + // channel). + window->context.osmesa.buffer = (unsigned char *) malloc( + window->osmesa.width * window->osmesa.height * 4); + + // Update the context size. + window->context.osmesa.width = window->osmesa.width; + window->context.osmesa.height = window->osmesa.height; + } + + // Make the context current. + if (!OSMesaMakeCurrent(window->context.osmesa.handle, + window->context.osmesa.buffer, + 0x1401, // GL_UNSIGNED_BYTE + window->osmesa.width, window->osmesa.height)) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "OSMesa: Failed to make context current."); + return; + } + } + else + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "OSMesa: Releasing the current context is not supported."); + } + + _glfwPlatformSetCurrentContext(window); +} + +static GLFWglproc getProcAddressOSMesa(const char* procname) +{ + _GLFWwindow* window = _glfwPlatformGetCurrentContext(); + + if (window->context.osmesa.handle) + { + return (GLFWglproc) OSMesaGetProcAddress(procname); + } + + return NULL; +} + +static void destroyContextOSMesa(_GLFWwindow* window) +{ + if (window->context.osmesa.handle != NULL) + { + OSMesaDestroyContext(window->context.osmesa.handle); + window->context.osmesa.handle = NULL; + } + + if (window->context.osmesa.buffer != NULL) + { + free(window->context.osmesa.buffer); + window->context.osmesa.width = 0; + window->context.osmesa.height = 0; + } +} + +static void swapBuffersOSMesa(_GLFWwindow* window) {} + +static void swapIntervalOSMesa(int interval) {} + +static int extensionSupportedOSMesa() +{ + return GLFW_FALSE; +} + +////////////////////////////////////////////////////////////////////////// +////// GLFW internal API ////// +////////////////////////////////////////////////////////////////////////// + +GLFWbool _glfwInitOSMesa(void) +{ + int i; + const char* sonames[] = + { +#if defined(_GLFW_WIN32) + "libOSMesa.dll", + "OSMesa.dll", +#elif defined(_GLFW_COCOA) + "libOSMesa.dylib", +#else + "libOSMesa.so.6", +#endif + NULL + }; + + if (_glfw.osmesa.handle) + return GLFW_TRUE; + + for (i = 0; sonames[i]; i++) + { + _glfw.osmesa.handle = _glfw_dlopen(sonames[i]); + if (_glfw.osmesa.handle) + break; + } + + if (!_glfw.osmesa.handle) + { + _glfwInputError(GLFW_API_UNAVAILABLE, "OSMesa: Library not found"); + return GLFW_FALSE; + } + + _glfw.osmesa.prefix = (strncmp(sonames[i], "lib", 3) == 0); + + _glfw.osmesa.CreateContext = (PFNOSMESACREATECONTEXTPROC) + _glfw_dlsym(_glfw.osmesa.handle, "OSMesaCreateContext"); + _glfw.osmesa.DestroyContext = (PFNOSMESADESTROYCONTEXTPROC) + _glfw_dlsym(_glfw.osmesa.handle, "OSMesaDestroyContext"); + _glfw.osmesa.MakeCurrent = (PFNOSMESAMAKECURRENTPROC) + _glfw_dlsym(_glfw.osmesa.handle, "OSMesaMakeCurrent"); + _glfw.osmesa.GetCurrentContext = (PFNOSMESAGETCURRENTCONTEXTPROC) + _glfw_dlsym(_glfw.osmesa.handle, "OSMesaGetCurrentContext"); + _glfw.osmesa.PixelStore = (PFNOSMESAPIXELSTOREPROC) + _glfw_dlsym(_glfw.osmesa.handle, "OSMesaPixelStore"); + _glfw.osmesa.GetIntegerv = (PFNOSMESAGETINTEGERVPROC) + _glfw_dlsym(_glfw.osmesa.handle, "OSMesaGetIntegerv"); + _glfw.osmesa.GetColorBuffer = (PFNOSMESAGETCOLORBUFFERPROC) + _glfw_dlsym(_glfw.osmesa.handle, "OSMesaGetColorBuffer"); + _glfw.osmesa.GetDepthBuffer = (PFNOSMESAGETDEPTHBUFFERPROC) + _glfw_dlsym(_glfw.osmesa.handle, "OSMesaGetDepthBuffer"); + _glfw.osmesa.GetProcAddress = (PFNOSMESAGETPROCADDRESSPROC) + _glfw_dlsym(_glfw.osmesa.handle, "OSMesaGetProcAddress"); + + if (!_glfw.osmesa.CreateContext || + !_glfw.osmesa.DestroyContext || + !_glfw.osmesa.MakeCurrent || + !_glfw.osmesa.GetCurrentContext || + !_glfw.osmesa.PixelStore || + !_glfw.osmesa.GetIntegerv || + !_glfw.osmesa.GetColorBuffer || + !_glfw.osmesa.GetDepthBuffer || + !_glfw.osmesa.GetProcAddress) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "OSMesa: Failed to load required entry points"); + + _glfwTerminateOSMesa(); + return GLFW_FALSE; + } + + return GLFW_TRUE; +} + +void _glfwTerminateOSMesa(void) +{ + if (_glfw.osmesa.handle) + { + _glfw_dlclose(_glfw.osmesa.handle); + _glfw.osmesa.handle = NULL; + } +} + +GLFWbool _glfwCreateContextOSMesa(_GLFWwindow* window, + const _GLFWctxconfig* ctxconfig, + const _GLFWfbconfig* fbconfig) +{ + OSMesaContext share; + + if (ctxconfig->share) + share = ctxconfig->share->context.osmesa.handle; + + // Initialize the context. + window->context.osmesa.buffer = NULL; + window->context.osmesa.width = 0; + window->context.osmesa.height = 0; + + // Create to create an OSMesa context. + window->context.osmesa.handle = OSMesaCreateContext(OSMESA_RGBA, share); + if (window->context.osmesa.handle == 0) + { + _glfwInputError(GLFW_VERSION_UNAVAILABLE, + "OSMesa: Failed to create context."); + return GLFW_FALSE; + } + + // Set up the context API. + window->context.makeCurrent = makeContextCurrentOSMesa; + window->context.swapBuffers = swapBuffersOSMesa; + window->context.swapInterval = swapIntervalOSMesa; + window->context.extensionSupported = extensionSupportedOSMesa; + window->context.getProcAddress = getProcAddressOSMesa; + window->context.destroy = destroyContextOSMesa; + + return GLFW_TRUE; +} + +////////////////////////////////////////////////////////////////////////// +////// GLFW native API ////// +////////////////////////////////////////////////////////////////////////// + diff --git a/src/osmesa_context.h b/src/osmesa_context.h new file mode 100644 index 000000000..2283cf741 --- /dev/null +++ b/src/osmesa_context.h @@ -0,0 +1,87 @@ + +#ifndef _glfw3_osmesa_context_h_ +#define _glfw3_osmesa_context_h_ + +#define OSMESA_COLOR_INDEX GL_COLOR_INDEX +#define OSMESA_RGBA 0x1908 +#define OSMESA_BGRA 0x1 +#define OSMESA_ARGB 0x2 +#define OSMESA_RGB 0x1907 +#define OSMESA_BGR 0x4 +#define OSMESA_RGB_565 0x5 + +#define OSMESA_ROW_LENGTH 0x10 +#define OSMESA_Y_UP 0x11 + +#define OSMESA_WIDTH 0x20 +#define OSMESA_HEIGHT 0x21 +#define OSMESA_FORMAT 0x22 +#define OSMESA_TYPE 0x23 +#define OSMESA_MAX_WIDTH 0x24 +#define OSMESA_MAX_HEIGHT 0x25 + +typedef void* OSMesaContext; +typedef void (*OSMESAproc)(); + +typedef OSMesaContext (* PFNOSMESACREATECONTEXTPROC)(GLenum, OSMesaContext); +typedef void (* PFNOSMESADESTROYCONTEXTPROC)(OSMesaContext); +typedef int (* PFNOSMESAMAKECURRENTPROC)(OSMesaContext, void*, int, int, int); +typedef OSMesaContext (* PFNOSMESAGETCURRENTCONTEXTPROC)(); +typedef void (* PFNOSMESAPIXELSTOREPROC)(int, int); +typedef void (* PFNOSMESAGETINTEGERVPROC)(int, int*); +typedef int (* PFNOSMESAGETCOLORBUFFERPROC)(OSMesaContext, int*, int*, int*, void**); +typedef int (* PFNOSMESAGETDEPTHBUFFERPROC)(OSMesaContext, int*, int*, int*, void**); +typedef GLFWglproc (* PFNOSMESAGETPROCADDRESSPROC)(const char*); +#define OSMesaCreateContext _glfw.osmesa.CreateContext +#define OSMesaDestroyContext _glfw.osmesa.DestroyContext +#define OSMesaMakeCurrent _glfw.osmesa.MakeCurrent +#define OSMesaGetCurrentContext _glfw.osmesa.GetCurrentContext +#define OSMesaPixelStore _glfw.osmesa.PixelStore +#define OSMesaGetIntegerv _glfw.osmesa.GetIntegerv +#define OSMesaGetColorBuffer _glfw.osmesa.GetColorBuffer +#define OSMesaGetDepthBuffer _glfw.osmesa.GetDepthBuffer +#define OSMesaGetProcAddress _glfw.osmesa.GetProcAddress + +#define _GLFW_PLATFORM_CONTEXT_STATE _GLFWcontextOSMesa osmesa +#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE _GLFWctxlibraryOSMesa osmesa + + +// OSMesa-specific per-context data +// +typedef struct _GLFWcontextOSMesa +{ + OSMesaContext handle; + int width; + int height; + void * buffer; + +} _GLFWcontextOSMesa; + +// OSMesa-specific global data +// +typedef struct _GLFWctxlibraryOSMesa +{ + GLFWbool prefix; + + void* handle; + + PFNOSMESACREATECONTEXTPROC CreateContext; + PFNOSMESADESTROYCONTEXTPROC DestroyContext; + PFNOSMESAMAKECURRENTPROC MakeCurrent; + PFNOSMESAGETCURRENTCONTEXTPROC GetCurrentContext; + PFNOSMESAPIXELSTOREPROC PixelStore; + PFNOSMESAGETINTEGERVPROC GetIntegerv; + PFNOSMESAGETCOLORBUFFERPROC GetColorBuffer; + PFNOSMESAGETDEPTHBUFFERPROC GetDepthBuffer; + PFNOSMESAGETPROCADDRESSPROC GetProcAddress; + +} _GLFWctxlibraryOSMesa; + + +GLFWbool _glfwInitOSMesa(void); +void _glfwTerminateOSMesa(void); +GLFWbool _glfwCreateContextOSMesa(_GLFWwindow* window, + const _GLFWctxconfig* ctxconfig, + const _GLFWfbconfig* fbconfig); + +#endif // _glfw3_osmesa_context_h_ diff --git a/src/osmesa_init.c b/src/osmesa_init.c new file mode 100644 index 000000000..46f2458d4 --- /dev/null +++ b/src/osmesa_init.c @@ -0,0 +1,35 @@ + +#include "internal.h" + +////////////////////////////////////////////////////////////////////////// +////// GLFW internal API ////// +////////////////////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////////////////////// +////// GLFW platform API ////// +////////////////////////////////////////////////////////////////////////// + +int _glfwPlatformInit(void) +{ + if (!_glfwInitThreadLocalStoragePOSIX()) { + return GLFW_FALSE; + } + + _glfwInitTimerPOSIX(); + + return GLFW_TRUE; +} + +void _glfwPlatformTerminate(void) +{ + _glfwTerminateOSMesa(); + _glfwTerminateThreadLocalStoragePOSIX(); +} + +const char* _glfwPlatformGetVersionString(void) +{ + const char* version = _GLFW_VERSION_NUMBER " OSMESA"; + return version; +} + diff --git a/src/osmesa_monitor.c b/src/osmesa_monitor.c new file mode 100644 index 000000000..fb9ede251 --- /dev/null +++ b/src/osmesa_monitor.c @@ -0,0 +1,43 @@ + +#include "internal.h" + +////////////////////////////////////////////////////////////////////////// +////// GLFW internal API ////// +////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +////// GLFW platform API ////// +////////////////////////////////////////////////////////////////////////// + +_GLFWmonitor** _glfwPlatformGetMonitors(int* count) +{ + // OSMesa is headless, so no monitors. + _GLFWmonitor** monitors = NULL; + if (count != NULL) { + *count = 0; + } + return monitors; +} + +int _glfwPlatformIsSameMonitor(_GLFWmonitor* first, _GLFWmonitor* second) +{ + return GLFW_FALSE; +} + +void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos) {} + +GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* found) +{ + return NULL; +} + +void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode) {} + +void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) {} + +void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, + const GLFWgammaramp* ramp) {} + +////////////////////////////////////////////////////////////////////////// +////// GLFW native API ////// +////////////////////////////////////////////////////////////////////////// diff --git a/src/osmesa_platform.h b/src/osmesa_platform.h new file mode 100644 index 000000000..f5e114e3f --- /dev/null +++ b/src/osmesa_platform.h @@ -0,0 +1,61 @@ + +#ifndef _osmesa_platform_h_ +#define _osmesa_platform_h_ + +#include + +#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowOSMesa osmesa +#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorOSMesa osmesa +#define _GLFW_PLATFORM_CURSOR_STATE _GLFWcursorOSMesa osmesa + +#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWwinlibraryOSMesa osmesawin + +#define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE +#define _GLFW_EGL_CONTEXT_STATE +#define _GLFW_EGL_LIBRARY_CONTEXT_STATE + +#include "osmesa_context.h" +#include "posix_time.h" +#include "posix_tls.h" + +#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) +#define _glfw_dlclose(handle) dlclose(handle) +#define _glfw_dlsym(handle, name) dlsym(handle, name) + +// TODO(dalyj) "PLACEHOLDER" variables are there to silence "empty struct" +// warnings + +// OSMesa-specific per-window data +// +typedef struct _GLFWwindowOSMesa +{ + int width; + int height; +} _GLFWwindowOSMesa; + + +// OSMesa-specific global data +// +typedef struct _GLFWwinlibraryOSMesa +{ + int PLACEHOLDER; +} _GLFWwinlibraryOSMesa; + + +// OSMesa-specific per-monitor data +// +typedef struct _GLFWmonitorOSMesa +{ + int PLACEHOLDER; +} _GLFWmonitorOSMesa; + + +// OSMesa-specific per-cursor data +// +typedef struct _GLFWcursorOSMesa +{ + int PLACEHOLDER; +} _GLFWcursorOSMesa; + + +#endif // _osmesa_platform_h_ diff --git a/src/osmesa_window.c b/src/osmesa_window.c new file mode 100644 index 000000000..c171d6290 --- /dev/null +++ b/src/osmesa_window.c @@ -0,0 +1,259 @@ + +#include "internal.h" + +#include + +int createWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig) +{ + window->osmesa.width = wndconfig->width; + window->osmesa.height = wndconfig->height; + + return GLFW_TRUE; +} + +////////////////////////////////////////////////////////////////////////// +////// GLFW internal API ////// +////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +////// GLFW platform API ////// +////////////////////////////////////////////////////////////////////////// + +int _glfwPlatformCreateWindow(_GLFWwindow* window, + const _GLFWwndconfig* wndconfig, + const _GLFWctxconfig* ctxconfig, + const _GLFWfbconfig* fbconfig) +{ + if (!_glfwInitOSMesa()) + return GLFW_FALSE; + + if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) + return GLFW_FALSE; + + if (!createWindow(window, wndconfig)) + return GLFW_FALSE; + + return GLFW_TRUE; +} + +void _glfwPlatformDestroyWindow(_GLFWwindow* window) +{ + if (window->context.destroy) + window->context.destroy(window); +} + +void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) {} + +void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, + const GLFWimage* images) {} + +void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, + _GLFWmonitor* monitor, + int xpos, int ypos, + int width, int height, + int refreshRate) {} + +void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) +{ + if (xpos != NULL) *xpos = 0; + if (ypos != NULL) *ypos = 0; +} + +void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos) {} + +void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) +{ + if (width != NULL) *width = window->osmesa.width; + if (height != NULL) *height = window->osmesa.height; +} + +void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) +{ + window->osmesa.width = width; + window->osmesa.height = height; +} + +void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, + int minwidth, int minheight, + int maxwidth, int maxheight) {} + +void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int n, int d) {} + +void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, + int* height) +{ + if (width != NULL) *width = window->osmesa.width; + if (height != NULL) *height = window->osmesa.height; +} + +void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, + int* right, int* bottom) +{ + if (left != NULL) *left = 0; + if (top != NULL) *top = 0; + if (right != NULL) *right = window->osmesa.width; + if (bottom != NULL) *top = window->osmesa.height; +} + +void _glfwPlatformIconifyWindow(_GLFWwindow* window) {} + +void _glfwPlatformRestoreWindow(_GLFWwindow* window) {} + +void _glfwPlatformMaximizeWindow(_GLFWwindow* window) {} + +int _glfwPlatformWindowMaximized(_GLFWwindow* window) { + return 0; +} + +void _glfwPlatformShowWindow(_GLFWwindow* window) {} + +void _glfwPlatformUnhideWindow(_GLFWwindow* window) {} + +void _glfwPlatformHideWindow(_GLFWwindow* window) {} + +void _glfwPlatformFocusWindow(_GLFWwindow* window) {} + +int _glfwPlatformWindowFocused(_GLFWwindow* window) { return GLFW_FALSE; } + +int _glfwPlatformWindowIconified(_GLFWwindow* window) { return GLFW_FALSE; } + +int _glfwPlatformWindowVisible(_GLFWwindow* window) { return GLFW_FALSE; } + +void _glfwPlatformPollEvents(void) {} + +void _glfwPlatformWaitEvents(void) {} + +void _glfwPlatformWaitEventsTimeout(double timeout) {} + +void _glfwPlatformPostEmptyEvent(void) {} + +void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { + if (xpos != NULL) *xpos = 0; + if (ypos != NULL) *ypos = 0; +} + +void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) {} + +void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) {} + +void _glfwPlatformApplyCursorMode(_GLFWwindow* window) {} + +int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, + int xhot, int yhot) +{ + return GLFW_FALSE; +} + +int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) +{ + return GLFW_FALSE; +} + +void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) {} + +void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) {} + +void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string) {} + +const char* _glfwPlatformGetClipboardString(_GLFWwindow* window) +{ + return NULL; +} + +const char* _glfwPlatformGetKeyName(int key, int scancode) { return ""; } + +int _glfwPlatformJoystickPresent(int joy) { return 0; } + +const float* _glfwPlatformGetJoystickAxes(int joy, int* count) +{ + if (count != NULL) *count = 0; + return NULL; +} + +const unsigned char* _glfwPlatformGetJoystickButtons(int joy, int* count) +{ + if (count != NULL) *count = 0; + return NULL; +} + +const char* _glfwPlatformGetJoystickName(int joy) { return NULL; } + +char** _glfwPlatformGetRequiredInstanceExtensions(uint32_t* count) +{ + if (count != NULL) *count = 0; + return NULL; +} + +int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, + VkPhysicalDevice device, + uint32_t queuefamily) +{ + return GLFW_FALSE; +} + +VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, + _GLFWwindow* window, + const VkAllocationCallbacks* allocator, + VkSurfaceKHR* surface) +{ + // This seems like the most appropriate error to return here. + return VK_ERROR_INITIALIZATION_FAILED; +} + +////////////////////////////////////////////////////////////////////////// +////// GLFW native API ////// +////////////////////////////////////////////////////////////////////////// + +GLFWAPI int glfwGetOSMesaColorBuffer(GLFWwindow* window, int* width, + int* height, int* format, void** buffer) +{ + GLint mesaWidth; + GLint mesaHeight; + GLint mesaFormat; + void* mesaBuffer; + + assert(window != NULL); + + OSMesaContext ctx = ((_GLFWwindow*) window)->context.osmesa.handle; + + // Query OSMesa for the color buffer data. + int result = OSMesaGetColorBuffer( + ctx, &mesaWidth, &mesaHeight, &mesaFormat, &mesaBuffer); + if (result) { + // Copy the values returned by OSMesa. + if (width != NULL) *width = mesaWidth; + if (height != NULL) *height = mesaHeight; + if (format != NULL) *format = mesaFormat; + if (buffer != NULL) *buffer = mesaBuffer; + } + + return result; +} + +GLFWAPI int glfwGetOSMesaDepthBuffer(GLFWwindow* window, int* width, + int* height, int* bytesPerValue, + void** buffer) +{ + GLint mesaWidth; + GLint mesaHeight; + GLint mesaBytes; + void* mesaBuffer; + + assert(window != NULL); + + OSMesaContext ctx = ((_GLFWwindow*) window)->context.osmesa.handle; + + // Query OSMesa for the color buffer data. + int result = OSMesaGetDepthBuffer( + ctx, &mesaWidth, &mesaHeight, &mesaBytes, &mesaBuffer); + if (result) { + // Copy the values returned by OSMesa. + if (width != NULL) *width = mesaWidth; + if (height != NULL) *height = mesaHeight; + if (bytesPerValue != NULL) *bytesPerValue = mesaBytes; + if (buffer != NULL) *buffer = mesaBuffer; + } + + return result; +} +