Skip to content

Instantly share code, notes, and snippets.

@stagas
Forked from roxlu/TestFboChangeBuffer.cpp
Created September 11, 2020 15:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stagas/2343ed26ef5676b74da2e5d1205fe837 to your computer and use it in GitHub Desktop.
Save stagas/2343ed26ef5676b74da2e5d1205fe837 to your computer and use it in GitHub Desktop.
Comparing the performance of different ping/pong techniques when using FBOs. One version uses two fbos and swaps between these, the other switches the draw and read buffer. See these results https://docs.google.com/spreadsheets/d/1ZyTQGkjYQajQtu8OvgRyaXJl46F4rZJStBCEK2kebgE/edit?usp=sharing for a comparison. It seems that switching read / draw b…
#include <stdlib.h>
#include <stdio.h>
#define ROXLU_USE_MATH
#define ROXLU_USE_OPENGL
#define ROXLU_IMPLEMENTATION
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <tinylib.h>
#include <poly/Log.h>
#include <poly/Timer.h>
#include <TimerGpuGl.h>
#include <TestFboSwap.h>
#include <TestFboChangeBuffer.h>
#include <TestWorker.h>
#define TEST_1
//#define TEST_2
using namespace poly;
void button_callback(GLFWwindow* win, int bt, int action, int mods);
void cursor_callback(GLFWwindow* win, double x, double y);
void key_callback(GLFWwindow* win, int key, int scancode, int action, int mods);
void char_callback(GLFWwindow* win, unsigned int key);
void error_callback(int err, const char* desc);
void resize_callback(GLFWwindow* window, int width, int height);
int main() {
glfwSetErrorCallback(error_callback);
if(!glfwInit()) {
printf("Error: cannot setup glfw.\n");
exit(EXIT_FAILURE);
}
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* win = NULL;
int w = 1280;
int h = 720;
win = glfwCreateWindow(w, h, "FBO performance", NULL, NULL);
if(!win) {
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwSetFramebufferSizeCallback(win, resize_callback);
glfwSetKeyCallback(win, key_callback);
glfwSetCharCallback(win, char_callback);
glfwSetCursorPosCallback(win, cursor_callback);
glfwSetMouseButtonCallback(win, button_callback);
glfwMakeContextCurrent(win);
glfwSwapInterval(1);
if (!gladLoadGL()) {
printf("Cannot load GL.\n");
exit(1);
}
// ----------------------------------------------------------------
// THIS IS WHERE YOU START CALLING OPENGL FUNCTIONS, NOT EARLIER!!
// ----------------------------------------------------------------
poly_log_init();
#if defined(TEST_1)
TestFboSwap fbo_test;
if (0 != fbo_test.init()) {
exit(EXIT_FAILURE);
}
#endif
#if defined(TEST_2)
TestFboChangeBuffer fbo_test;
if (0 != fbo_test.init()) {
exit(EXIT_FAILURE);
}
#endif
TestWorker worker;
if (0 != worker.init()) {
exit(EXIT_FAILURE);
}
TimerGpuGlQuery timer;
if (0 != timer.init("perf")) {
exit(EXIT_FAILURE);
}
uint64_t dt = 0;
uint64_t s = 0;
uint64_t d = 0;
int r = 0;
uint64_t total_cpu = 0;
uint64_t total_gpu = 0;
uint64_t max_runs = 10000;
while(!glfwWindowShouldClose(win)) {
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
for (int i = 0; i < 25; ++i) {
for (uint64_t run = 0; run < max_runs; ++run) {
s = nanos();
timer.begin();
{
fbo_test.activate();
worker.performWork(fbo_test.getReadTexture());
fbo_test.swap();
}
r = timer.end(dt);
glFinish();
d = nanos() - s;
if (r == 0) {
total_gpu += dt;
total_cpu += d;
}
if (run == (max_runs-1)) {
SX_WARNING("GPU time, %llu, ns, %f, ms, "
"CPU time, %llu, ns, %f, ms, ",
total_gpu, double(total_gpu) / 1e6,
total_cpu, double(total_cpu) / 1e6);
total_gpu = 0;
total_cpu = 0;
break;
}
}
}
/*
SX_VERBOSE("GPU: % 8llu ns., %f ms., CPU: % 8llu ns., %f ms., r: %d",
dt, double(dt)/1e6,
d, double(d)/1e6,
r);
*/
break;
//glfwSwapBuffers(win);
glfwPollEvents();
}
glfwTerminate();
return EXIT_SUCCESS;
}
void char_callback(GLFWwindow* win, unsigned int key) {
}
void key_callback(GLFWwindow* win, int key, int scancode, int action, int mods) {
if(action != GLFW_PRESS) {
return;
}
switch(key) {
case GLFW_KEY_ESCAPE: {
glfwSetWindowShouldClose(win, GL_TRUE);
break;
}
};
}
void resize_callback(GLFWwindow* window, int width, int height) {
}
void cursor_callback(GLFWwindow* win, double x, double y) {
}
void button_callback(GLFWwindow* win, int bt, int action, int mods) {
}
void error_callback(int err, const char* desc) {
printf("GLFW error: %s (%d)\n", desc, err);
}
#include <poly/Log.h>
#include <TestFboChangeBuffer.h>
using namespace poly;
TestFboChangeBuffer::TestFboChangeBuffer()
:fbo(0)
,dx(0)
{
tex[0] = 0;
tex[1] = 0;
}
TestFboChangeBuffer::~TestFboChangeBuffer() {
}
int TestFboChangeBuffer::init() {
if (0 != fbo) {
SX_ERROR("Cannot initialize because we're already initialized.");
return -1;
}
glGenTextures(2, tex);
glBindTexture(GL_TEXTURE_2D, tex[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1024, 1024, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, tex[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1024, 1024, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex[0], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex[1], 0);
if (GL_FRAMEBUFFER_COMPLETE != glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
SX_ERROR("Framebuffer not yet complete.");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return -1;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return 0;
}
void TestFboChangeBuffer::activate() {
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
if (0 == dx) {
glDrawBuffer(GL_COLOR_ATTACHMENT1);
glReadBuffer(GL_COLOR_ATTACHMENT0);
}
else {
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glReadBuffer(GL_COLOR_ATTACHMENT1);
}
glViewport(0, 0, 1024, 1024);
}
void TestFboChangeBuffer::swap() {
dx = 1 - dx;
}
GLuint TestFboChangeBuffer::getReadTexture() {
return tex[dx];
}
#ifndef TEST_FBO_CHANGE_BUFFER_H
#define TEST_FBO_CHANGE_BUFFER_H
#define ROXLU_USE_MATH
#define ROXLU_USE_OPENGL
#include <glad/glad.h>
#include <tinylib.h>
class TestFboChangeBuffer {
public:
TestFboChangeBuffer();
~TestFboChangeBuffer();
int init();
void activate();
void swap();
GLuint getReadTexture();
public:
GLuint fbo;
GLuint tex[2];
int dx;
};
#endif
#include <TestFboSwap.h>
#include <poly/Log.h>
using namespace poly;
TestFboSwap::TestFboSwap()
:dx(0)
{
fbo[0] = 0;
fbo[1] = 0;
tex[0] = 0;
tex[1] = 0;
}
TestFboSwap::~TestFboSwap() {
}
int TestFboSwap::init() {
if (0 != fbo[0]) {
SX_ERROR("Alredy initialized.");
return -1;
}
if (0 != createFbo(fbo[0], tex[0])) {
return -2;
}
if (0 != createFbo(fbo[1], tex[1])) {
return -3;
}
return 0;
}
int TestFboSwap::createFbo(GLuint& fboOut, GLuint& texOut) {
if (0 != fboOut) {
SX_ERROR("Given FBO is not 0, already created?");
return -1;
}
if (0 != texOut) {
SX_ERROR("Given TEX is not 0, already created?");
return -2;
}
glGenTextures(1, &texOut);
glBindTexture(GL_TEXTURE_2D, texOut);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1024, 1024, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffers(1, &fboOut);
glBindFramebuffer(GL_FRAMEBUFFER, fboOut);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texOut, 0);
if (GL_FRAMEBUFFER_COMPLETE != glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
SX_ERROR("Framebuffer not yet complete.");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return -3;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return 0;
}
void TestFboSwap::activate() {
// glWaitSync();
glBindFramebuffer(GL_FRAMEBUFFER, fbo[dx]);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glViewport(0, 0, 1024, 1024);
}
void TestFboSwap::swap() {
dx = 1 - dx;
}
GLuint TestFboSwap::getReadTexture() {
return tex[dx - 1];
}
#ifndef TEST_FBO_SWAP_H
#define TEST_FBO_SWAP_H
#define ROXLU_USE_MATH
#define ROXLU_USE_OPENGL
#include <glad/glad.h>
#include <tinylib.h>
class TestFboSwap {
public:
TestFboSwap();
~TestFboSwap();
int init();
void activate();
void swap();
GLuint getReadTexture(); /* texture from which we can read; so which isn't rendered into. */
private:
int createFbo(GLuint& fboOut, GLuint& texOut);
public:
GLuint fbo[2];
GLuint tex[2];
int dx;
};
#endif
#include <poly/Log.h>
#include <TestWorker.h>
using namespace poly;
static const char* WORKER_VS = ""
"#version 330\n"
""
" const vec2[] pos = vec2[4]("
" vec2(-1.0, 1.0),"
" vec2(-1.0, -1.0),"
" vec2(1.0, 1.0),"
" vec2(1.0, -1.0)"
" );"
""
"const vec2 tex[] = vec2[4]("
" vec2(0.0, 1.0), "
" vec2(0.0, 0.0), "
" vec2(1.0, 1.0), "
" vec2(1.0, 0.0) "
");"
""
"out vec2 v_tex;"
""
"void main() { "
" gl_Position = vec4(pos[gl_VertexID], 0.0, 1.0);"
" v_tex = tex[gl_VertexID];"
"};"
"";
static const char* WORKER_FS = ""
"#version 330\n"
""
"uniform sampler2D u_tex;"
"in vec2 v_tex;"
"layout (location = 0) out vec4 fragcolor;"
""
"void main() {"
""
" vec4 src = texture(u_tex, v_tex); "
" src.r += 0.01;"
" src.a = 1.0;"
" fragcolor = src;"
"}"
"";
TestWorker::TestWorker()
:vert(0)
,frag(0)
,prog(0)
,vao(0)
,u_tex(-1)
{
}
int TestWorker::init() {
if (0 != vert) {
SX_ERROR("Cannot initialize the TestWorker because it's already initialized.");
return -1;
}
vert = rx_create_shader(GL_VERTEX_SHADER, WORKER_VS);
frag = rx_create_shader(GL_FRAGMENT_SHADER, WORKER_FS);
prog = rx_create_program(vert, frag, true);
glUseProgram(prog);
u_tex = glGetUniformLocation(prog, "u_tex");
if (-1 == u_tex) {
SX_ERROR("u_tex < 0, not used in shader?");
return -2;
}
glUniform1i(u_tex, 0);
glGenVertexArrays(1, &vao);
if (0 == vao) {
SX_ERROR("Failed to create the vao.");
return -3;
}
return 0;
}
int TestWorker::performWork(GLuint sourceTexture) {
if (0 == sourceTexture) {
SX_ERROR("Given texture id is invald.");
return -1;
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, sourceTexture);
glUseProgram(prog);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
return 0;
}
/*
Test Worker
===========
Simple class that just does some OpenGL
calls and applies a shader. This is used while
doing the Fbo* tests.
We create a shader and we draw a full viewport
rectangle to make sure the GPU does some work.
*/
#ifndef TEST_WORKER_H
#define TEST_WORKER_H
#define ROXLU_USE_MATH
#define ROXLU_USE_OPENGL
#include <glad/glad.h>
#include <tinylib.h>
class TestWorker {
public:
TestWorker();
int init();
int performWork(GLuint sourceTexture);
public:
GLuint vert;
GLuint frag;
GLuint prog;
GLuint vao;
GLint u_tex;
};
#endif
#include <poly/Log.h>
#include <TimerGpuGl.h>
namespace poly {
TimerGpuGlQuery::TimerGpuGlQuery()
:write_num(0)
,write_dx(0)
,prev_timestamp(0)
{
memset((char*)id, 0x00, sizeof(GLuint) * NUM_GPU_GL_TIMERS);
}
TimerGpuGlQuery::~TimerGpuGlQuery() {
// SX_ERROR("Remove GL objects. ");
}
int TimerGpuGlQuery::init(const std::string& name) {
if (0 != id[0]) {
SX_ERROR("Cannot initialize the timer query because it's already created.");
return -1;
}
glGenQueries(NUM_GPU_GL_TIMERS, id);
return 0;
}
int TimerGpuGlQuery::begin() {
if (0 == id[0]) {
SX_ERROR("Cannot beging the GPU timer query because we're not initialized.");
return -1;
}
glQueryCounter(id[write_dx], GL_TIMESTAMP);
write_num++;
write_dx = write_num % NUM_GPU_GL_TIMERS;
return 0;
}
int TimerGpuGlQuery::end(uint64_t& result) {
GLuint timer_available = GL_FALSE;
uint64_t timestamp = 0;
if (write_num > NUM_GPU_GL_TIMERS) {
glGetQueryObjectuiv(id[write_dx], GL_QUERY_RESULT_AVAILABLE, &timer_available);
if (GL_TRUE == timer_available) {
glGetQueryObjectui64v(id[write_dx], GL_QUERY_RESULT, &timestamp);
result = timestamp - prev_timestamp;
prev_timestamp = timestamp;
return 0;
}
else {
return -1;
}
}
else {
/* still filling the buffer. */
result = 0;
return -1;
}
return 0;
}
} /* namespace poly */
#ifndef POLY_TIMER_GPU_GL_H
#define POLY_TIMER_GPU_GL_H
#include <string>
#include <stdint.h>
#include <glad/glad.h>
#define NUM_GPU_GL_TIMERS 6
namespace poly {
class TimerGpuGlQuery {
public:
TimerGpuGlQuery();
~TimerGpuGlQuery();
int init(const std::string& name);
int begin();
int end(uint64_t& time);
public:
std::string name;
GLuint id[NUM_GPU_GL_TIMERS];
uint64_t prev_timestamp;
uint64_t write_num;
uint8_t write_dx;
};
class TimerGpuGl {
public:
TimerGpuGl();
~TimerGpuGl();
};
} /* namespace poly */
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment