Note-drawing program
$begingroup$
This program draws a random music note on a staff, refreshing the note every second. It compiles with g++ and also emscripten, so it is runnable in both the browser and on desktop.
This is my first attempt at graphics programming, so I'm not interested in feedback on how I'm using OpenGL. In particular, I'm vaguely considered that I'm using shaders poorly. Do I really write a new shader program for each sort of thing I have to draw?
This code uses Bitmap.h, Bitmap.cpp, platform.hpp, and platform_linux.cpp by Thomas Dalling. These files assume that stb_image.h is located on the system include path. shader.cpp
is modified from the shader code in this tutorial, but it's just standard shader-loading code. This project uses a unity build.
Makefile
all: a.out index.html
EMS_OPTS = -s FULL_ES3=1
-s WASM=1
-s NO_EXIT_RUNTIME=1
-s USE_GLFW=3
-s USE_LIBPNG=1
-DEMSCRIPTEN
CXX_FLAGS = -Wall -Wextra -std=c++17
LD_LFAGS = -lGL -lGLU -lglfw -lGLEW -lpng
index.html: main.cpp Drawable.cpp Drawable.h
emcc main.cpp -o index.html ${CXX_FLAGS} ${EMS_OPTS} --preload-file resources/whole-note.png
a.out: main.cpp Drawable.cpp Drawable.h
g++ -g main.cpp ${CXX_FLAGS} ${LD_LFAGS}
Drawable.h
#ifndef DRAWABLE_H_
#define DRAWABLE_H_
#include <chrono>
#include <memory>
#include <vector>
class Drawable
{
public:
virtual ~Drawable() {}
virtual void draw() const = 0;
virtual void update() {};
Drawable(const Drawable& other) = delete;
Drawable& operator=(const Drawable& other) = delete;
Drawable() {}
};
class Note : public Drawable
{
public:
Note(GLuint program);
~Note();
virtual void draw() const;
void setY(double value);
private:
GLuint vao, vbo, ibo, texture, program;
};
class Staff : public Drawable
{
public:
Staff(GLuint program, Note& note);
~Staff();
virtual void draw() const;
virtual void update();
private:
GLuint vao, vbo, program;
static const GLfloat points[30];
Note& note;
std::chrono::time_point<std::chrono::system_clock> start;
std::vector<float> valid_positions;
};
#endif
Drawable.cpp
#include <algorithm>
#include <random>
#include "Drawable.h"
namespace
{
GLfloat vertices = {
// X, Y, Z U, V
0.2, 0.0, 0.0, 1.0, 1.0,
-0.2, 0.0, 0.0, 0.0, 1.0,
0.2, 0.0, 0.0, 1.0, 0.0,
-0.2, 0.0, 0.0, 0.0, 0.0,
};
}
Note::Note(GLuint program) :
program(program)
{
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// X, Y, Z (dest) coordinates
glVertexAttribPointer(0,
3,
GL_FLOAT,
GL_FALSE,
5 * sizeof(GL_FLOAT),
NULL);
glEnableVertexAttribArray(0);
// U, V (src) coordinates
glVertexAttribPointer(1,
2,
GL_FLOAT,
GL_FALSE,
5 * sizeof(GL_FLOAT),
(const GLvoid *)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(1);
// TODO: Pass this in?
auto bmp = tdogl::Bitmap::bitmapFromFile(ResourcePath("whole-note.png"));
bmp.flipVertically();
// Index buffer object
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
static const GLuint indexData = {
0, 1, 2,
2, 1, 3
};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), indexData,
GL_STATIC_DRAW);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
(GLsizei)bmp.width(),
(GLsizei)bmp.height(),
0,
GL_RGBA,
GL_UNSIGNED_BYTE, bmp.pixelBuffer());
glBindTexture(GL_TEXTURE_2D, 0);
}
void Note::setY(double value)
{
static const float HALF_DIM = .2;
vertices[1] = value + HALF_DIM;
vertices[6] = value + HALF_DIM;
vertices[11] = value - HALF_DIM;
vertices[16] = value - HALF_DIM;
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
Note::~Note()
{
glDeleteProgram(program);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ibo);
glDeleteVertexArrays(1, &vao);
glDeleteTextures(1, &texture);
}
void Note::draw() const
{
glUseProgram(program);
glBindVertexArray(vao);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
GLint uniform = glGetUniformLocation(program, "texture_");
glUniform1i(uniform, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
Staff::Staff(GLuint program, Note& note) :
program(program),
note(note),
start(std::chrono::system_clock::now()),
valid_positions{.5, .55, .6, .65, .7, .75, .8, .85}
{
note.setY(valid_positions[0]);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
glVertexAttribPointer(
0,
3,
GL_FLOAT,
GL_FALSE,
0,
(void*)0
);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
}
Staff::~Staff()
{
glDeleteBuffers(1, &vbo);
glDeleteVertexArrays(1, &vao);
glDeleteProgram(program);
}
void Staff::draw() const
{
glUseProgram(program);
glBindVertexArray(vao);
glDrawArrays(GL_LINES, 0, 10);
note.draw();
}
const GLfloat Staff::points[30] = {
-1.0f, 0.9f, 0.0f,
1.0f, 0.9f, 0.0f,
-1.0f, 0.8f, 0.0f,
1.0f, 0.8f, 0.0f,
-1.0f, 0.7f, 0.0f,
1.0f, 0.7f, 0.0f,
-1.0f, 0.6f, 0.0f,
1.0f, 0.6f, 0.0f,
-1.0f, 0.5f, 0.0f,
1.0f, 0.5f, 0.0f,
};
void Staff::update()
{
const auto current_time = std::chrono::system_clock::now();
const auto duration = std::chrono::duration_cast<std::chrono::seconds>(current_time - start).count();
if (duration >= 1)
{
start = std::chrono::system_clock::now();
std::vector<float> new_position;
std::sample(valid_positions.begin(), valid_positions.end(),
std::back_inserter(new_position), 1,
std::mt19937{std::random_device{}()});
note.setY(new_position[0]);
}
}
main.cpp
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <memory>
#ifdef EMSCRIPTEN
#include <GLES3/gl3.h>
#include "emscripten.h"
#else
#include <GL/glew.h>
#include <unistd.h>
#endif
#include <GLFW/glfw3.h>
#include "platform_linux.cpp"
#include "Bitmap.h"
#include "Bitmap.cpp"
#include "shader.hpp"
#include "shader.cpp"
#include "Drawable.h"
#include "Drawable.cpp"
#define FRAMES_PER_SECOND 30
#ifdef EMSCRIPTEN
static const char* noteVertSource = "
precision mediump float;n
attribute vec4 position;n
attribute vec2 verTexCoord;n
varying vec2 fragTexCoord;n
void main()n
{n
fragTexCoord = verTexCoord;n
gl_Position = position;n
}";
static const char* noteFragSource = "
precision mediump float;n
uniform sampler2D texture_;n
varying vec2 fragTexCoord;n
void main()n
{n
gl_FragColor = texture2D(texture_, fragTexCoord);n
}";
#else
static const char* noteVertSource = "
attribute vec4 position;n
attribute vec2 verTexCoord;n
varying vec2 fragTexCoord;n
void main()n
{n
fragTexCoord = verTexCoord;n
gl_Position = position;n
}";
static const char* noteFragSource = "
uniform sampler2D texture_;n
varying vec2 fragTexCoord;n
void main()n
{n
gl_FragColor = texture2D(texture_, fragTexCoord);n
}";
#endif
static const char* vertSource = "
#ifdef EMSCRIPTENn
precision mediump float;n
#endifn
attribute vec4 position;n
void main()n
{n
gl_Position = position;n
}";
static const char* fragSource = "
#ifdef EMSCRIPTENn
precision mediump float;n
#endifn
void main()n
{n
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);n
}";
void GLAPIENTRY
MessageCallback( GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam )
{
(void) source;
(void) id;
(void) length;
(void) userParam;
const char *repr = type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "";
fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %sn",
repr, type, severity, message );
}
class GLFWContext
{
public:
GLFWContext(size_t width, size_t height, const char *name)
{
// Initialise GLFW
if( !glfwInit() )
{
throw std::runtime_error("Failed to initialize GLFW");
}
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Open a window and create its OpenGL context
window = glfwCreateWindow(width, height, name, nullptr, nullptr);
if (!window)
{
glfwTerminate();
throw std::runtime_error("Failed to open GLFW window. "
"If you have an Intel GPU, they are not 3.3 compatible. "
"Try the 2.1 version of the tutorials.n");
}
glfwMakeContextCurrent(window);
}
~GLFWContext()
{
glfwDestroyWindow(window);
glfwTerminate();
}
bool isActive()
{
return glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS &&
glfwWindowShouldClose(window) == 0;
}
void update()
{
glfwSwapBuffers(window);
glfwPollEvents();
}
private:
GLFWwindow* window;
};
class Game : public Drawable
{
public:
Game(GLFWContext& glfw) : glfw(glfw) {}
virtual void draw() const
{
glClear(GL_COLOR_BUFFER_BIT);
for (auto& object : objects)
object->draw();
}
inline void add(std::unique_ptr<Drawable> object)
{
objects.push_back(std::move(object));
}
virtual void update()
{
for (auto& object : objects) object->update();
glfw.update();
}
private:
GLFWContext& glfw;
std::vector<std::unique_ptr<Drawable>> objects;
};
void main_loop(void* arg)
{
#ifndef EMSCRIPTEN
const auto start = std::clock();
#endif
Game *game = (Game *)arg;
assert(game);
game->draw();
game->update();
unsigned int err = 0;
while ( (err = glGetError()) )
{
std::cerr << err << "n";
}
#ifndef EMSCRIPTEN
const auto seconds_elapsed = (std::clock() - start) /
static_cast<double>(CLOCKS_PER_SEC);
if (seconds_elapsed < 1.0 / FRAMES_PER_SECOND)
{
usleep(1000000 * (1.0 / FRAMES_PER_SECOND - seconds_elapsed));
}
#endif
}
int main( void )
{
GLFWContext context(1024, 768, "Music game");
// Initialize GLEW
#ifndef EMSCRIPTEN
glewExperimental = true; // Needed for core profile
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEWn");
return -1;
}
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(MessageCallback, 0);
#endif
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
// Create and compile our GLSL program from the shaders
const GLuint linesProgram = LoadShaders(vertSource, fragSource);
const GLuint noteProgram = LoadShaders(noteVertSource, noteFragSource);
Game game(context);
Note note(noteProgram);
note.setY(.75);
game.add(std::unique_ptr<Drawable>(
new Staff(linesProgram, note)));
#ifdef EMSCRIPTEN
emscripten_set_main_loop_arg(main_loop, &game, FRAMES_PER_SECOND, 1);
#else
do{
main_loop(&game);
} while (context.isActive());
#endif
return 0;
}
c++ opengl
$endgroup$
add a comment |
$begingroup$
This program draws a random music note on a staff, refreshing the note every second. It compiles with g++ and also emscripten, so it is runnable in both the browser and on desktop.
This is my first attempt at graphics programming, so I'm not interested in feedback on how I'm using OpenGL. In particular, I'm vaguely considered that I'm using shaders poorly. Do I really write a new shader program for each sort of thing I have to draw?
This code uses Bitmap.h, Bitmap.cpp, platform.hpp, and platform_linux.cpp by Thomas Dalling. These files assume that stb_image.h is located on the system include path. shader.cpp
is modified from the shader code in this tutorial, but it's just standard shader-loading code. This project uses a unity build.
Makefile
all: a.out index.html
EMS_OPTS = -s FULL_ES3=1
-s WASM=1
-s NO_EXIT_RUNTIME=1
-s USE_GLFW=3
-s USE_LIBPNG=1
-DEMSCRIPTEN
CXX_FLAGS = -Wall -Wextra -std=c++17
LD_LFAGS = -lGL -lGLU -lglfw -lGLEW -lpng
index.html: main.cpp Drawable.cpp Drawable.h
emcc main.cpp -o index.html ${CXX_FLAGS} ${EMS_OPTS} --preload-file resources/whole-note.png
a.out: main.cpp Drawable.cpp Drawable.h
g++ -g main.cpp ${CXX_FLAGS} ${LD_LFAGS}
Drawable.h
#ifndef DRAWABLE_H_
#define DRAWABLE_H_
#include <chrono>
#include <memory>
#include <vector>
class Drawable
{
public:
virtual ~Drawable() {}
virtual void draw() const = 0;
virtual void update() {};
Drawable(const Drawable& other) = delete;
Drawable& operator=(const Drawable& other) = delete;
Drawable() {}
};
class Note : public Drawable
{
public:
Note(GLuint program);
~Note();
virtual void draw() const;
void setY(double value);
private:
GLuint vao, vbo, ibo, texture, program;
};
class Staff : public Drawable
{
public:
Staff(GLuint program, Note& note);
~Staff();
virtual void draw() const;
virtual void update();
private:
GLuint vao, vbo, program;
static const GLfloat points[30];
Note& note;
std::chrono::time_point<std::chrono::system_clock> start;
std::vector<float> valid_positions;
};
#endif
Drawable.cpp
#include <algorithm>
#include <random>
#include "Drawable.h"
namespace
{
GLfloat vertices = {
// X, Y, Z U, V
0.2, 0.0, 0.0, 1.0, 1.0,
-0.2, 0.0, 0.0, 0.0, 1.0,
0.2, 0.0, 0.0, 1.0, 0.0,
-0.2, 0.0, 0.0, 0.0, 0.0,
};
}
Note::Note(GLuint program) :
program(program)
{
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// X, Y, Z (dest) coordinates
glVertexAttribPointer(0,
3,
GL_FLOAT,
GL_FALSE,
5 * sizeof(GL_FLOAT),
NULL);
glEnableVertexAttribArray(0);
// U, V (src) coordinates
glVertexAttribPointer(1,
2,
GL_FLOAT,
GL_FALSE,
5 * sizeof(GL_FLOAT),
(const GLvoid *)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(1);
// TODO: Pass this in?
auto bmp = tdogl::Bitmap::bitmapFromFile(ResourcePath("whole-note.png"));
bmp.flipVertically();
// Index buffer object
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
static const GLuint indexData = {
0, 1, 2,
2, 1, 3
};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), indexData,
GL_STATIC_DRAW);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
(GLsizei)bmp.width(),
(GLsizei)bmp.height(),
0,
GL_RGBA,
GL_UNSIGNED_BYTE, bmp.pixelBuffer());
glBindTexture(GL_TEXTURE_2D, 0);
}
void Note::setY(double value)
{
static const float HALF_DIM = .2;
vertices[1] = value + HALF_DIM;
vertices[6] = value + HALF_DIM;
vertices[11] = value - HALF_DIM;
vertices[16] = value - HALF_DIM;
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
Note::~Note()
{
glDeleteProgram(program);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ibo);
glDeleteVertexArrays(1, &vao);
glDeleteTextures(1, &texture);
}
void Note::draw() const
{
glUseProgram(program);
glBindVertexArray(vao);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
GLint uniform = glGetUniformLocation(program, "texture_");
glUniform1i(uniform, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
Staff::Staff(GLuint program, Note& note) :
program(program),
note(note),
start(std::chrono::system_clock::now()),
valid_positions{.5, .55, .6, .65, .7, .75, .8, .85}
{
note.setY(valid_positions[0]);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
glVertexAttribPointer(
0,
3,
GL_FLOAT,
GL_FALSE,
0,
(void*)0
);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
}
Staff::~Staff()
{
glDeleteBuffers(1, &vbo);
glDeleteVertexArrays(1, &vao);
glDeleteProgram(program);
}
void Staff::draw() const
{
glUseProgram(program);
glBindVertexArray(vao);
glDrawArrays(GL_LINES, 0, 10);
note.draw();
}
const GLfloat Staff::points[30] = {
-1.0f, 0.9f, 0.0f,
1.0f, 0.9f, 0.0f,
-1.0f, 0.8f, 0.0f,
1.0f, 0.8f, 0.0f,
-1.0f, 0.7f, 0.0f,
1.0f, 0.7f, 0.0f,
-1.0f, 0.6f, 0.0f,
1.0f, 0.6f, 0.0f,
-1.0f, 0.5f, 0.0f,
1.0f, 0.5f, 0.0f,
};
void Staff::update()
{
const auto current_time = std::chrono::system_clock::now();
const auto duration = std::chrono::duration_cast<std::chrono::seconds>(current_time - start).count();
if (duration >= 1)
{
start = std::chrono::system_clock::now();
std::vector<float> new_position;
std::sample(valid_positions.begin(), valid_positions.end(),
std::back_inserter(new_position), 1,
std::mt19937{std::random_device{}()});
note.setY(new_position[0]);
}
}
main.cpp
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <memory>
#ifdef EMSCRIPTEN
#include <GLES3/gl3.h>
#include "emscripten.h"
#else
#include <GL/glew.h>
#include <unistd.h>
#endif
#include <GLFW/glfw3.h>
#include "platform_linux.cpp"
#include "Bitmap.h"
#include "Bitmap.cpp"
#include "shader.hpp"
#include "shader.cpp"
#include "Drawable.h"
#include "Drawable.cpp"
#define FRAMES_PER_SECOND 30
#ifdef EMSCRIPTEN
static const char* noteVertSource = "
precision mediump float;n
attribute vec4 position;n
attribute vec2 verTexCoord;n
varying vec2 fragTexCoord;n
void main()n
{n
fragTexCoord = verTexCoord;n
gl_Position = position;n
}";
static const char* noteFragSource = "
precision mediump float;n
uniform sampler2D texture_;n
varying vec2 fragTexCoord;n
void main()n
{n
gl_FragColor = texture2D(texture_, fragTexCoord);n
}";
#else
static const char* noteVertSource = "
attribute vec4 position;n
attribute vec2 verTexCoord;n
varying vec2 fragTexCoord;n
void main()n
{n
fragTexCoord = verTexCoord;n
gl_Position = position;n
}";
static const char* noteFragSource = "
uniform sampler2D texture_;n
varying vec2 fragTexCoord;n
void main()n
{n
gl_FragColor = texture2D(texture_, fragTexCoord);n
}";
#endif
static const char* vertSource = "
#ifdef EMSCRIPTENn
precision mediump float;n
#endifn
attribute vec4 position;n
void main()n
{n
gl_Position = position;n
}";
static const char* fragSource = "
#ifdef EMSCRIPTENn
precision mediump float;n
#endifn
void main()n
{n
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);n
}";
void GLAPIENTRY
MessageCallback( GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam )
{
(void) source;
(void) id;
(void) length;
(void) userParam;
const char *repr = type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "";
fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %sn",
repr, type, severity, message );
}
class GLFWContext
{
public:
GLFWContext(size_t width, size_t height, const char *name)
{
// Initialise GLFW
if( !glfwInit() )
{
throw std::runtime_error("Failed to initialize GLFW");
}
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Open a window and create its OpenGL context
window = glfwCreateWindow(width, height, name, nullptr, nullptr);
if (!window)
{
glfwTerminate();
throw std::runtime_error("Failed to open GLFW window. "
"If you have an Intel GPU, they are not 3.3 compatible. "
"Try the 2.1 version of the tutorials.n");
}
glfwMakeContextCurrent(window);
}
~GLFWContext()
{
glfwDestroyWindow(window);
glfwTerminate();
}
bool isActive()
{
return glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS &&
glfwWindowShouldClose(window) == 0;
}
void update()
{
glfwSwapBuffers(window);
glfwPollEvents();
}
private:
GLFWwindow* window;
};
class Game : public Drawable
{
public:
Game(GLFWContext& glfw) : glfw(glfw) {}
virtual void draw() const
{
glClear(GL_COLOR_BUFFER_BIT);
for (auto& object : objects)
object->draw();
}
inline void add(std::unique_ptr<Drawable> object)
{
objects.push_back(std::move(object));
}
virtual void update()
{
for (auto& object : objects) object->update();
glfw.update();
}
private:
GLFWContext& glfw;
std::vector<std::unique_ptr<Drawable>> objects;
};
void main_loop(void* arg)
{
#ifndef EMSCRIPTEN
const auto start = std::clock();
#endif
Game *game = (Game *)arg;
assert(game);
game->draw();
game->update();
unsigned int err = 0;
while ( (err = glGetError()) )
{
std::cerr << err << "n";
}
#ifndef EMSCRIPTEN
const auto seconds_elapsed = (std::clock() - start) /
static_cast<double>(CLOCKS_PER_SEC);
if (seconds_elapsed < 1.0 / FRAMES_PER_SECOND)
{
usleep(1000000 * (1.0 / FRAMES_PER_SECOND - seconds_elapsed));
}
#endif
}
int main( void )
{
GLFWContext context(1024, 768, "Music game");
// Initialize GLEW
#ifndef EMSCRIPTEN
glewExperimental = true; // Needed for core profile
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEWn");
return -1;
}
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(MessageCallback, 0);
#endif
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
// Create and compile our GLSL program from the shaders
const GLuint linesProgram = LoadShaders(vertSource, fragSource);
const GLuint noteProgram = LoadShaders(noteVertSource, noteFragSource);
Game game(context);
Note note(noteProgram);
note.setY(.75);
game.add(std::unique_ptr<Drawable>(
new Staff(linesProgram, note)));
#ifdef EMSCRIPTEN
emscripten_set_main_loop_arg(main_loop, &game, FRAMES_PER_SECOND, 1);
#else
do{
main_loop(&game);
} while (context.isActive());
#endif
return 0;
}
c++ opengl
$endgroup$
add a comment |
$begingroup$
This program draws a random music note on a staff, refreshing the note every second. It compiles with g++ and also emscripten, so it is runnable in both the browser and on desktop.
This is my first attempt at graphics programming, so I'm not interested in feedback on how I'm using OpenGL. In particular, I'm vaguely considered that I'm using shaders poorly. Do I really write a new shader program for each sort of thing I have to draw?
This code uses Bitmap.h, Bitmap.cpp, platform.hpp, and platform_linux.cpp by Thomas Dalling. These files assume that stb_image.h is located on the system include path. shader.cpp
is modified from the shader code in this tutorial, but it's just standard shader-loading code. This project uses a unity build.
Makefile
all: a.out index.html
EMS_OPTS = -s FULL_ES3=1
-s WASM=1
-s NO_EXIT_RUNTIME=1
-s USE_GLFW=3
-s USE_LIBPNG=1
-DEMSCRIPTEN
CXX_FLAGS = -Wall -Wextra -std=c++17
LD_LFAGS = -lGL -lGLU -lglfw -lGLEW -lpng
index.html: main.cpp Drawable.cpp Drawable.h
emcc main.cpp -o index.html ${CXX_FLAGS} ${EMS_OPTS} --preload-file resources/whole-note.png
a.out: main.cpp Drawable.cpp Drawable.h
g++ -g main.cpp ${CXX_FLAGS} ${LD_LFAGS}
Drawable.h
#ifndef DRAWABLE_H_
#define DRAWABLE_H_
#include <chrono>
#include <memory>
#include <vector>
class Drawable
{
public:
virtual ~Drawable() {}
virtual void draw() const = 0;
virtual void update() {};
Drawable(const Drawable& other) = delete;
Drawable& operator=(const Drawable& other) = delete;
Drawable() {}
};
class Note : public Drawable
{
public:
Note(GLuint program);
~Note();
virtual void draw() const;
void setY(double value);
private:
GLuint vao, vbo, ibo, texture, program;
};
class Staff : public Drawable
{
public:
Staff(GLuint program, Note& note);
~Staff();
virtual void draw() const;
virtual void update();
private:
GLuint vao, vbo, program;
static const GLfloat points[30];
Note& note;
std::chrono::time_point<std::chrono::system_clock> start;
std::vector<float> valid_positions;
};
#endif
Drawable.cpp
#include <algorithm>
#include <random>
#include "Drawable.h"
namespace
{
GLfloat vertices = {
// X, Y, Z U, V
0.2, 0.0, 0.0, 1.0, 1.0,
-0.2, 0.0, 0.0, 0.0, 1.0,
0.2, 0.0, 0.0, 1.0, 0.0,
-0.2, 0.0, 0.0, 0.0, 0.0,
};
}
Note::Note(GLuint program) :
program(program)
{
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// X, Y, Z (dest) coordinates
glVertexAttribPointer(0,
3,
GL_FLOAT,
GL_FALSE,
5 * sizeof(GL_FLOAT),
NULL);
glEnableVertexAttribArray(0);
// U, V (src) coordinates
glVertexAttribPointer(1,
2,
GL_FLOAT,
GL_FALSE,
5 * sizeof(GL_FLOAT),
(const GLvoid *)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(1);
// TODO: Pass this in?
auto bmp = tdogl::Bitmap::bitmapFromFile(ResourcePath("whole-note.png"));
bmp.flipVertically();
// Index buffer object
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
static const GLuint indexData = {
0, 1, 2,
2, 1, 3
};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), indexData,
GL_STATIC_DRAW);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
(GLsizei)bmp.width(),
(GLsizei)bmp.height(),
0,
GL_RGBA,
GL_UNSIGNED_BYTE, bmp.pixelBuffer());
glBindTexture(GL_TEXTURE_2D, 0);
}
void Note::setY(double value)
{
static const float HALF_DIM = .2;
vertices[1] = value + HALF_DIM;
vertices[6] = value + HALF_DIM;
vertices[11] = value - HALF_DIM;
vertices[16] = value - HALF_DIM;
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
Note::~Note()
{
glDeleteProgram(program);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ibo);
glDeleteVertexArrays(1, &vao);
glDeleteTextures(1, &texture);
}
void Note::draw() const
{
glUseProgram(program);
glBindVertexArray(vao);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
GLint uniform = glGetUniformLocation(program, "texture_");
glUniform1i(uniform, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
Staff::Staff(GLuint program, Note& note) :
program(program),
note(note),
start(std::chrono::system_clock::now()),
valid_positions{.5, .55, .6, .65, .7, .75, .8, .85}
{
note.setY(valid_positions[0]);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
glVertexAttribPointer(
0,
3,
GL_FLOAT,
GL_FALSE,
0,
(void*)0
);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
}
Staff::~Staff()
{
glDeleteBuffers(1, &vbo);
glDeleteVertexArrays(1, &vao);
glDeleteProgram(program);
}
void Staff::draw() const
{
glUseProgram(program);
glBindVertexArray(vao);
glDrawArrays(GL_LINES, 0, 10);
note.draw();
}
const GLfloat Staff::points[30] = {
-1.0f, 0.9f, 0.0f,
1.0f, 0.9f, 0.0f,
-1.0f, 0.8f, 0.0f,
1.0f, 0.8f, 0.0f,
-1.0f, 0.7f, 0.0f,
1.0f, 0.7f, 0.0f,
-1.0f, 0.6f, 0.0f,
1.0f, 0.6f, 0.0f,
-1.0f, 0.5f, 0.0f,
1.0f, 0.5f, 0.0f,
};
void Staff::update()
{
const auto current_time = std::chrono::system_clock::now();
const auto duration = std::chrono::duration_cast<std::chrono::seconds>(current_time - start).count();
if (duration >= 1)
{
start = std::chrono::system_clock::now();
std::vector<float> new_position;
std::sample(valid_positions.begin(), valid_positions.end(),
std::back_inserter(new_position), 1,
std::mt19937{std::random_device{}()});
note.setY(new_position[0]);
}
}
main.cpp
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <memory>
#ifdef EMSCRIPTEN
#include <GLES3/gl3.h>
#include "emscripten.h"
#else
#include <GL/glew.h>
#include <unistd.h>
#endif
#include <GLFW/glfw3.h>
#include "platform_linux.cpp"
#include "Bitmap.h"
#include "Bitmap.cpp"
#include "shader.hpp"
#include "shader.cpp"
#include "Drawable.h"
#include "Drawable.cpp"
#define FRAMES_PER_SECOND 30
#ifdef EMSCRIPTEN
static const char* noteVertSource = "
precision mediump float;n
attribute vec4 position;n
attribute vec2 verTexCoord;n
varying vec2 fragTexCoord;n
void main()n
{n
fragTexCoord = verTexCoord;n
gl_Position = position;n
}";
static const char* noteFragSource = "
precision mediump float;n
uniform sampler2D texture_;n
varying vec2 fragTexCoord;n
void main()n
{n
gl_FragColor = texture2D(texture_, fragTexCoord);n
}";
#else
static const char* noteVertSource = "
attribute vec4 position;n
attribute vec2 verTexCoord;n
varying vec2 fragTexCoord;n
void main()n
{n
fragTexCoord = verTexCoord;n
gl_Position = position;n
}";
static const char* noteFragSource = "
uniform sampler2D texture_;n
varying vec2 fragTexCoord;n
void main()n
{n
gl_FragColor = texture2D(texture_, fragTexCoord);n
}";
#endif
static const char* vertSource = "
#ifdef EMSCRIPTENn
precision mediump float;n
#endifn
attribute vec4 position;n
void main()n
{n
gl_Position = position;n
}";
static const char* fragSource = "
#ifdef EMSCRIPTENn
precision mediump float;n
#endifn
void main()n
{n
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);n
}";
void GLAPIENTRY
MessageCallback( GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam )
{
(void) source;
(void) id;
(void) length;
(void) userParam;
const char *repr = type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "";
fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %sn",
repr, type, severity, message );
}
class GLFWContext
{
public:
GLFWContext(size_t width, size_t height, const char *name)
{
// Initialise GLFW
if( !glfwInit() )
{
throw std::runtime_error("Failed to initialize GLFW");
}
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Open a window and create its OpenGL context
window = glfwCreateWindow(width, height, name, nullptr, nullptr);
if (!window)
{
glfwTerminate();
throw std::runtime_error("Failed to open GLFW window. "
"If you have an Intel GPU, they are not 3.3 compatible. "
"Try the 2.1 version of the tutorials.n");
}
glfwMakeContextCurrent(window);
}
~GLFWContext()
{
glfwDestroyWindow(window);
glfwTerminate();
}
bool isActive()
{
return glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS &&
glfwWindowShouldClose(window) == 0;
}
void update()
{
glfwSwapBuffers(window);
glfwPollEvents();
}
private:
GLFWwindow* window;
};
class Game : public Drawable
{
public:
Game(GLFWContext& glfw) : glfw(glfw) {}
virtual void draw() const
{
glClear(GL_COLOR_BUFFER_BIT);
for (auto& object : objects)
object->draw();
}
inline void add(std::unique_ptr<Drawable> object)
{
objects.push_back(std::move(object));
}
virtual void update()
{
for (auto& object : objects) object->update();
glfw.update();
}
private:
GLFWContext& glfw;
std::vector<std::unique_ptr<Drawable>> objects;
};
void main_loop(void* arg)
{
#ifndef EMSCRIPTEN
const auto start = std::clock();
#endif
Game *game = (Game *)arg;
assert(game);
game->draw();
game->update();
unsigned int err = 0;
while ( (err = glGetError()) )
{
std::cerr << err << "n";
}
#ifndef EMSCRIPTEN
const auto seconds_elapsed = (std::clock() - start) /
static_cast<double>(CLOCKS_PER_SEC);
if (seconds_elapsed < 1.0 / FRAMES_PER_SECOND)
{
usleep(1000000 * (1.0 / FRAMES_PER_SECOND - seconds_elapsed));
}
#endif
}
int main( void )
{
GLFWContext context(1024, 768, "Music game");
// Initialize GLEW
#ifndef EMSCRIPTEN
glewExperimental = true; // Needed for core profile
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEWn");
return -1;
}
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(MessageCallback, 0);
#endif
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
// Create and compile our GLSL program from the shaders
const GLuint linesProgram = LoadShaders(vertSource, fragSource);
const GLuint noteProgram = LoadShaders(noteVertSource, noteFragSource);
Game game(context);
Note note(noteProgram);
note.setY(.75);
game.add(std::unique_ptr<Drawable>(
new Staff(linesProgram, note)));
#ifdef EMSCRIPTEN
emscripten_set_main_loop_arg(main_loop, &game, FRAMES_PER_SECOND, 1);
#else
do{
main_loop(&game);
} while (context.isActive());
#endif
return 0;
}
c++ opengl
$endgroup$
This program draws a random music note on a staff, refreshing the note every second. It compiles with g++ and also emscripten, so it is runnable in both the browser and on desktop.
This is my first attempt at graphics programming, so I'm not interested in feedback on how I'm using OpenGL. In particular, I'm vaguely considered that I'm using shaders poorly. Do I really write a new shader program for each sort of thing I have to draw?
This code uses Bitmap.h, Bitmap.cpp, platform.hpp, and platform_linux.cpp by Thomas Dalling. These files assume that stb_image.h is located on the system include path. shader.cpp
is modified from the shader code in this tutorial, but it's just standard shader-loading code. This project uses a unity build.
Makefile
all: a.out index.html
EMS_OPTS = -s FULL_ES3=1
-s WASM=1
-s NO_EXIT_RUNTIME=1
-s USE_GLFW=3
-s USE_LIBPNG=1
-DEMSCRIPTEN
CXX_FLAGS = -Wall -Wextra -std=c++17
LD_LFAGS = -lGL -lGLU -lglfw -lGLEW -lpng
index.html: main.cpp Drawable.cpp Drawable.h
emcc main.cpp -o index.html ${CXX_FLAGS} ${EMS_OPTS} --preload-file resources/whole-note.png
a.out: main.cpp Drawable.cpp Drawable.h
g++ -g main.cpp ${CXX_FLAGS} ${LD_LFAGS}
Drawable.h
#ifndef DRAWABLE_H_
#define DRAWABLE_H_
#include <chrono>
#include <memory>
#include <vector>
class Drawable
{
public:
virtual ~Drawable() {}
virtual void draw() const = 0;
virtual void update() {};
Drawable(const Drawable& other) = delete;
Drawable& operator=(const Drawable& other) = delete;
Drawable() {}
};
class Note : public Drawable
{
public:
Note(GLuint program);
~Note();
virtual void draw() const;
void setY(double value);
private:
GLuint vao, vbo, ibo, texture, program;
};
class Staff : public Drawable
{
public:
Staff(GLuint program, Note& note);
~Staff();
virtual void draw() const;
virtual void update();
private:
GLuint vao, vbo, program;
static const GLfloat points[30];
Note& note;
std::chrono::time_point<std::chrono::system_clock> start;
std::vector<float> valid_positions;
};
#endif
Drawable.cpp
#include <algorithm>
#include <random>
#include "Drawable.h"
namespace
{
GLfloat vertices = {
// X, Y, Z U, V
0.2, 0.0, 0.0, 1.0, 1.0,
-0.2, 0.0, 0.0, 0.0, 1.0,
0.2, 0.0, 0.0, 1.0, 0.0,
-0.2, 0.0, 0.0, 0.0, 0.0,
};
}
Note::Note(GLuint program) :
program(program)
{
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// X, Y, Z (dest) coordinates
glVertexAttribPointer(0,
3,
GL_FLOAT,
GL_FALSE,
5 * sizeof(GL_FLOAT),
NULL);
glEnableVertexAttribArray(0);
// U, V (src) coordinates
glVertexAttribPointer(1,
2,
GL_FLOAT,
GL_FALSE,
5 * sizeof(GL_FLOAT),
(const GLvoid *)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(1);
// TODO: Pass this in?
auto bmp = tdogl::Bitmap::bitmapFromFile(ResourcePath("whole-note.png"));
bmp.flipVertically();
// Index buffer object
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
static const GLuint indexData = {
0, 1, 2,
2, 1, 3
};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), indexData,
GL_STATIC_DRAW);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
(GLsizei)bmp.width(),
(GLsizei)bmp.height(),
0,
GL_RGBA,
GL_UNSIGNED_BYTE, bmp.pixelBuffer());
glBindTexture(GL_TEXTURE_2D, 0);
}
void Note::setY(double value)
{
static const float HALF_DIM = .2;
vertices[1] = value + HALF_DIM;
vertices[6] = value + HALF_DIM;
vertices[11] = value - HALF_DIM;
vertices[16] = value - HALF_DIM;
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
Note::~Note()
{
glDeleteProgram(program);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ibo);
glDeleteVertexArrays(1, &vao);
glDeleteTextures(1, &texture);
}
void Note::draw() const
{
glUseProgram(program);
glBindVertexArray(vao);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
GLint uniform = glGetUniformLocation(program, "texture_");
glUniform1i(uniform, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
Staff::Staff(GLuint program, Note& note) :
program(program),
note(note),
start(std::chrono::system_clock::now()),
valid_positions{.5, .55, .6, .65, .7, .75, .8, .85}
{
note.setY(valid_positions[0]);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
glVertexAttribPointer(
0,
3,
GL_FLOAT,
GL_FALSE,
0,
(void*)0
);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
}
Staff::~Staff()
{
glDeleteBuffers(1, &vbo);
glDeleteVertexArrays(1, &vao);
glDeleteProgram(program);
}
void Staff::draw() const
{
glUseProgram(program);
glBindVertexArray(vao);
glDrawArrays(GL_LINES, 0, 10);
note.draw();
}
const GLfloat Staff::points[30] = {
-1.0f, 0.9f, 0.0f,
1.0f, 0.9f, 0.0f,
-1.0f, 0.8f, 0.0f,
1.0f, 0.8f, 0.0f,
-1.0f, 0.7f, 0.0f,
1.0f, 0.7f, 0.0f,
-1.0f, 0.6f, 0.0f,
1.0f, 0.6f, 0.0f,
-1.0f, 0.5f, 0.0f,
1.0f, 0.5f, 0.0f,
};
void Staff::update()
{
const auto current_time = std::chrono::system_clock::now();
const auto duration = std::chrono::duration_cast<std::chrono::seconds>(current_time - start).count();
if (duration >= 1)
{
start = std::chrono::system_clock::now();
std::vector<float> new_position;
std::sample(valid_positions.begin(), valid_positions.end(),
std::back_inserter(new_position), 1,
std::mt19937{std::random_device{}()});
note.setY(new_position[0]);
}
}
main.cpp
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <memory>
#ifdef EMSCRIPTEN
#include <GLES3/gl3.h>
#include "emscripten.h"
#else
#include <GL/glew.h>
#include <unistd.h>
#endif
#include <GLFW/glfw3.h>
#include "platform_linux.cpp"
#include "Bitmap.h"
#include "Bitmap.cpp"
#include "shader.hpp"
#include "shader.cpp"
#include "Drawable.h"
#include "Drawable.cpp"
#define FRAMES_PER_SECOND 30
#ifdef EMSCRIPTEN
static const char* noteVertSource = "
precision mediump float;n
attribute vec4 position;n
attribute vec2 verTexCoord;n
varying vec2 fragTexCoord;n
void main()n
{n
fragTexCoord = verTexCoord;n
gl_Position = position;n
}";
static const char* noteFragSource = "
precision mediump float;n
uniform sampler2D texture_;n
varying vec2 fragTexCoord;n
void main()n
{n
gl_FragColor = texture2D(texture_, fragTexCoord);n
}";
#else
static const char* noteVertSource = "
attribute vec4 position;n
attribute vec2 verTexCoord;n
varying vec2 fragTexCoord;n
void main()n
{n
fragTexCoord = verTexCoord;n
gl_Position = position;n
}";
static const char* noteFragSource = "
uniform sampler2D texture_;n
varying vec2 fragTexCoord;n
void main()n
{n
gl_FragColor = texture2D(texture_, fragTexCoord);n
}";
#endif
static const char* vertSource = "
#ifdef EMSCRIPTENn
precision mediump float;n
#endifn
attribute vec4 position;n
void main()n
{n
gl_Position = position;n
}";
static const char* fragSource = "
#ifdef EMSCRIPTENn
precision mediump float;n
#endifn
void main()n
{n
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);n
}";
void GLAPIENTRY
MessageCallback( GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam )
{
(void) source;
(void) id;
(void) length;
(void) userParam;
const char *repr = type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "";
fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %sn",
repr, type, severity, message );
}
class GLFWContext
{
public:
GLFWContext(size_t width, size_t height, const char *name)
{
// Initialise GLFW
if( !glfwInit() )
{
throw std::runtime_error("Failed to initialize GLFW");
}
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Open a window and create its OpenGL context
window = glfwCreateWindow(width, height, name, nullptr, nullptr);
if (!window)
{
glfwTerminate();
throw std::runtime_error("Failed to open GLFW window. "
"If you have an Intel GPU, they are not 3.3 compatible. "
"Try the 2.1 version of the tutorials.n");
}
glfwMakeContextCurrent(window);
}
~GLFWContext()
{
glfwDestroyWindow(window);
glfwTerminate();
}
bool isActive()
{
return glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS &&
glfwWindowShouldClose(window) == 0;
}
void update()
{
glfwSwapBuffers(window);
glfwPollEvents();
}
private:
GLFWwindow* window;
};
class Game : public Drawable
{
public:
Game(GLFWContext& glfw) : glfw(glfw) {}
virtual void draw() const
{
glClear(GL_COLOR_BUFFER_BIT);
for (auto& object : objects)
object->draw();
}
inline void add(std::unique_ptr<Drawable> object)
{
objects.push_back(std::move(object));
}
virtual void update()
{
for (auto& object : objects) object->update();
glfw.update();
}
private:
GLFWContext& glfw;
std::vector<std::unique_ptr<Drawable>> objects;
};
void main_loop(void* arg)
{
#ifndef EMSCRIPTEN
const auto start = std::clock();
#endif
Game *game = (Game *)arg;
assert(game);
game->draw();
game->update();
unsigned int err = 0;
while ( (err = glGetError()) )
{
std::cerr << err << "n";
}
#ifndef EMSCRIPTEN
const auto seconds_elapsed = (std::clock() - start) /
static_cast<double>(CLOCKS_PER_SEC);
if (seconds_elapsed < 1.0 / FRAMES_PER_SECOND)
{
usleep(1000000 * (1.0 / FRAMES_PER_SECOND - seconds_elapsed));
}
#endif
}
int main( void )
{
GLFWContext context(1024, 768, "Music game");
// Initialize GLEW
#ifndef EMSCRIPTEN
glewExperimental = true; // Needed for core profile
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEWn");
return -1;
}
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(MessageCallback, 0);
#endif
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
// Create and compile our GLSL program from the shaders
const GLuint linesProgram = LoadShaders(vertSource, fragSource);
const GLuint noteProgram = LoadShaders(noteVertSource, noteFragSource);
Game game(context);
Note note(noteProgram);
note.setY(.75);
game.add(std::unique_ptr<Drawable>(
new Staff(linesProgram, note)));
#ifdef EMSCRIPTEN
emscripten_set_main_loop_arg(main_loop, &game, FRAMES_PER_SECOND, 1);
#else
do{
main_loop(&game);
} while (context.isActive());
#endif
return 0;
}
c++ opengl
c++ opengl
asked 7 hours ago
User319User319
784415
784415
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f216083%2fnote-drawing-program%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f216083%2fnote-drawing-program%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown