#include #include #include #include #define UNUSED(a) ((void) (a)) // number of time records to keep (must be power of two - 1) #define MAX_TIMES 0xFF // custom SDL_USEREVENT code for timer #define EVENT_CODE_TIMER 0 typedef struct { const GLuint type; const char *src; } shader_t; typedef struct { SDL_Window *win; // times ring buffer struct { uint32_t draw_start, draw_end; } times[MAX_TIMES]; uint32_t times_ofs; bool done, fullscreen; } context_t; static const float verts[] = { -0.5, 0.5, 0, 0.5, 0.5, 0, 0.0, -0.5, 0, }; static const char vs_src[] = "#version 320 es\n" "\n" "layout (location = 0) in mediump vec3 pos;\n" "\n" "void main() {\n" " gl_Position = vec4(pos.x, pos.y, pos.z, 1);\n" "}\n"; static const char fs_src[] = "#version 320 es\n" "\n" "out mediump vec4 color;\n" "\n" "uniform mediump float time;\n" "\n" "void main() {\n" " color = vec4(0.5f + 0.5 * sin(time * 3.141), 0, 0, 1);\n" "}\n"; static const shader_t RENDER_SHADERS[] = { { GL_VERTEX_SHADER, vs_src }, { GL_FRAGMENT_SHADER, fs_src }, }; static GLuint compile_shader( const GLenum type, const char *src ) { // create shader GLuint r = glCreateShader(type); // compile shader glShaderSource(r, 1, &src, NULL); glCompileShader(r); // check for error GLint ok; glGetShaderiv(r, GL_COMPILE_STATUS, &ok); if (!ok) { GLchar log[1024]; glGetShaderInfoLog(r, sizeof(log), NULL, log); SDL_Log("glCompileShader() failed: %s", log); exit(EXIT_FAILURE); } // return result return r; } static GLuint link_program( const size_t num_shaders, const shader_t *shaders ) { // create program GLuint r = glCreateProgram(); // compile shaders // (FIXME: check num_shaders < 128) GLuint ids[128]; if (num_shaders >= 128) { } for (size_t i = 0; i < num_shaders; i++) { // compile shader ids[i] = compile_shader(shaders[i].type, shaders[i].src); // attach shader glAttachShader(r, ids[i]); } // link program glLinkProgram(r); // check link status GLint ok; glGetProgramiv(r, GL_LINK_STATUS, &ok); if (!ok) { GLchar log[1024]; glGetProgramInfoLog(r, sizeof(log), NULL, log); SDL_Log("glLinkProgram() failed: %s", log); exit(EXIT_FAILURE); } // delete shaders for (size_t i = 0; i < num_shaders; i++) { glDeleteShader(ids[i]); } // return result return r; } static void update_viewport( SDL_Window *win ) { int w, h; // get window drawable size SDL_GL_GetDrawableSize(win, &w, &h); // set viewport size glViewport(0, 0, w, h); } static void ctx_init(context_t * const ctx) { // init sdl if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) { SDL_Log("SDL_Init() failed: %s", SDL_GetError()); exit(EXIT_FAILURE); } // create window ctx->win = SDL_CreateWindow( "Compute Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE ); // check for error if (!ctx->win) { SDL_Log("SDL_CreateWindow() failed: %s", SDL_GetError()); exit(EXIT_FAILURE); } // set gl context majory version hint if (SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES ) < 0) { SDL_Log("SDL_GL_SetAttribute() failed: %s", SDL_GetError()); exit(EXIT_FAILURE); } // set gl context majory version hint if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) { SDL_Log("SDL_GL_SetAttribute() failed: %s", SDL_GetError()); exit(EXIT_FAILURE); } // set gl context minor version hint if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2) < 0) { SDL_Log("SDL_GL_SetAttribute() failed: %s", SDL_GetError()); exit(EXIT_FAILURE); } // create GL context SDL_GLContext context = SDL_GL_CreateContext(ctx->win); if (!context) { SDL_Log("SDL_GL_CreateContext() failed: %s", SDL_GetError()); exit(EXIT_FAILURE); } // init glew // (do this immediately after GL context init) { GLenum err = glewInit(); if (err != GLEW_OK) { SDL_Log("glewInit() failed: %s", glewGetErrorString(err)); exit(EXIT_FAILURE); } } // set viewport size // (after GLEW init) update_viewport(ctx->win); // set swap interval // (after context init) if (SDL_GL_SetSwapInterval(1) < 0) { SDL_Log("SDL_GL_SetSwapInterval() failed: %s", SDL_GetError()); exit(EXIT_FAILURE); } // init times ring buffer memset(ctx->times, 0, MAX_TIMES * sizeof(uint32_t) * 2); ctx->times_ofs = 0; // init flags ctx->done = false; ctx->fullscreen = false; } static void handle_events( context_t * const ctx ) { SDL_Event ev; while (SDL_PollEvent(&ev)) { switch (ev.type) { case SDL_QUIT: ctx->done = true; break; case SDL_KEYDOWN: switch (ev.key.keysym.sym) { case SDLK_q: case SDLK_ESCAPE: ctx->done = true; break; case SDLK_F11: // toggle fullscreen ctx->fullscreen = !ctx->fullscreen; if (SDL_SetWindowFullscreen( ctx->win, ctx->fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0 ) < 0) { // log warning SDL_Log("SDL_SetWindowFullscreen() failed: %s", SDL_GetError()); ctx->fullscreen = !ctx->fullscreen; } break; } break; case SDL_WINDOWEVENT: switch (ev.window.event) { case SDL_WINDOWEVENT_SIZE_CHANGED: { // map event to window SDL_Window *ev_win = SDL_GetWindowFromID(ev.window.windowID); if (ev_win == ctx->win) { // update viewport update_viewport(ctx->win); } else { SDL_Log("ignoring SIZE_CHANGED event from unknown window"); } } break; } break; case SDL_USEREVENT: if (ev.user.code == EVENT_CODE_TIMER) { uint32_t sum = 0; #pragma omp parallel for reduction(+:sum) for (int i = 0; i < MAX_TIMES; i++) { sum += ctx->times[i].draw_end - ctx->times[i].draw_start; } // print fps SDL_Log("fps: %f", sum * 1.0 / MAX_TIMES); } break; } } } static Uint32 timer_cb( Uint32 interval, void *arg ) { SDL_Event ev; UNUSED(arg); // build synthetic event ev.type = SDL_USEREVENT; ev.user.type = SDL_USEREVENT; ev.user.code = EVENT_CODE_TIMER; ev.user.data1 = NULL; ev.user.data2 = NULL; // add event to queue SDL_PushEvent(&ev); // return interval return interval; } int main(int argc, char *argv[]) { UNUSED(argc); UNUSED(argv); // init context context_t ctx; ctx_init(&ctx); // init timer (5 seconds) SDL_TimerID timer_id = SDL_AddTimer(5000, timer_cb, NULL); // generate vertex array GLuint vao; glGenVertexArrays(1, &vao); glBindVertexArray(vao); // generate vertex buffer GLuint vbo; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*) 0); glEnableVertexAttribArray(0); // unbind vao glBindVertexArray(0); // link program, get uniforms GLuint prog = link_program(2, RENDER_SHADERS); GLint u_time = glGetUniformLocation(prog, "time"); // main loop while (!ctx.done) { // get start time uint32_t now = SDL_GetTicks(); // save start time ctx.times[ctx.times_ofs].draw_start = now; // clear screen glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); // use program, set uniform glUseProgram(prog); glUniform1f(u_time, now / 1000.0); // draw glBindVertexArray(vao); glDrawArrays(GL_TRIANGLES, 0, 3); glBindVertexArray(0); // swap buffer SDL_GL_SwapWindow(ctx.win); // save end time ctx.times[ctx.times_ofs].draw_end = SDL_GetTicks(); ctx.times_ofs = (ctx.times_ofs + 1) & MAX_TIMES; // handle events handle_events(&ctx); } // remove timer if (SDL_RemoveTimer(timer_id) == SDL_FALSE) { SDL_Log("SDL_RemoveTimer() failed"); } // delete GL context SDL_GLContext context = SDL_GL_GetCurrentContext(); if (context) { SDL_GL_DeleteContext(context); } // destroy window SDL_DestroyWindow(ctx.win); // return success return EXIT_SUCCESS; }