Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: VBO, RenderWindow, FPS Drop  (Read 5654 times)

0 Members and 1 Guest are viewing this topic.

bkaxrf

  • Newbie
  • *
  • Posts: 10
    • View Profile
VBO, RenderWindow, FPS Drop
« on: January 09, 2011, 07:57:46 am »
Hello,  I'm not sure if this problem is related to OpenGL or SFML, hoping that someone here might have advice for me.  

I have an application that displays 25 objects using 25 VBOs.

I have a function that builds all 25 VBO's using glGenBuffers, glBufferData, and glBufferSubData.

If I call the function AFTER I create my RenderWindow,  everything works great as long as my window size is about 800x600 or less, I get 90+ fps.  If my window size is 1024x768, the fps drops to ~3, and my CPU usage goes way up, around 50-75%.

If I call the function BEFORE I create my RenderWindow, everything works great no matter the window size,  I get 90+ fps at 1680x1050, and cpu usage is low.  Also, after I create the RenderWindow I can still delete the VBOs and create new ones and the fps doesn't drop.

So the easy solution is just to do what seems to be working,  but is it okay to create the VBOs before the RenderWindow?  Shouldn't I be able to create them after?

tntexplosivesltd

  • Full Member
  • ***
  • Posts: 163
    • View Profile
VBO, RenderWindow, FPS Drop
« Reply #1 on: January 09, 2011, 08:30:42 am »
Can we see some concise code?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
VBO, RenderWindow, FPS Drop
« Reply #2 on: January 09, 2011, 01:05:14 pm »
It's ok to create VBOs before the window, SFML has a global OpenGL context which is always active and shared with all others.
Laurent Gomila - SFML developer

bkaxrf

  • Newbie
  • *
  • Posts: 10
    • View Profile
VBO, RenderWindow, FPS Drop
« Reply #3 on: January 09, 2011, 03:31:47 pm »
Sure, each object is a class, in the constructor I call
Code: [Select]
glGenBuffers(1, &VBOVertices);

This is the function that fills the VBO, called once for opaque and once for transparent:
Code: [Select]

void Model::writeToBuffer(bool trans)
{
    //These are variables in the class:
    //GLuint VBOVertices;
    //MyQuad* qo; //Opaque Quads
    //MyQuad* qt; //Transparent Quads
    //int co, ct; //Count of opaque, transparent quads
    //When this function is called, all of these variables have been set

    glBindBuffer(GL_ARRAY_BUFFER, VBOVertices);
    glBufferData(GL_ARRAY_BUFFER, sizeof(MyQuad)*ct+sizeof(MyQuad)*co, NULL, GL_DYNAMIC_DRAW);
    if (!trans)
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(MyQuad)*co, &qo[0].x1);
    else
        glBufferSubData(GL_ARRAY_BUFFER, sizeof(MyQuad)*co, sizeof(MyQuad)*ct, &qt[0].x1);
}


This is the structure for MyQuad:
Code: [Select]

struct MyQuad
{
//Position, normals, texture coords
    float x1, y1, z1;
    float nx1, ny1, nz1;
    float tx1, ty1;

    float x2, y2, z2;
    float nx2, ny2, nz2;
    float tx2, ty2;

    float x3, y3, z3;
    float nx3, ny3, nz3;
    float tx3, ty3;

    float x4, y4, z4;
    float nx4, ny4, nz4;
    float tx4, ty4;
};


This is the function that creates my renderwindow:
Code: [Select]

void DisplayManager::createWindow(int windowWidth, int windowHeight)
{
    _windowWidth = windowWidth;
    _windowHeight = windowHeight;
    if (g.context!=NULL)
    {
      g.context->Close();
      delete g.context;
      g.context = NULL;
    }
    g.context = new sf::RenderWindow(sf::VideoMode(_windowWidth, _windowHeight, 32), "Window");
}


This is the function that draws the objects,  where the actual slowdown happens.  It's called once for opaque and once for transparent:
Code: [Select]

void Model::render(bool trans)
{
  //BUFFER_OFFSET is defined as:
  //#define BUFFER_OFFSET(i) ((char *)NULL + (i))

  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);

  glBindTexture(GL_TEXTURE_2D, g.displayManager->texture );
  glBindBuffer(GL_ARRAY_BUFFER, VBOVertices);
  glVertexPointer(3, GL_FLOAT, 32, BUFFER_OFFSET(0));
  glNormalPointer(GL_FLOAT, 32, BUFFER_OFFSET(12));
  glTexCoordPointer(2, GL_FLOAT, 32, BUFFER_OFFSET(24));
  glPushMatrix();
 
  glTranslatef(posx, posy, posz);
  if (!trans) glDrawArrays(GL_QUADS, 0, co*4);
  else glDrawArrays(GL_QUADS, co*4, ct*4);

  glPopMatrix();

  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);

}


So thats about everything except for the function that actually builds the models,  but its somewhat lengthy.  Thanks for your help so far!

bkaxrf

  • Newbie
  • *
  • Posts: 10
    • View Profile
VBO, RenderWindow, FPS Drop
« Reply #4 on: January 10, 2011, 03:54:40 am »
Here is my minimal example:
Code: [Select]


#include <GLee.h>
#include <SFML/Graphics.hpp>
#include <SFML/OpenGL.hpp>
#include <iostream>
using namespace std;

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

#define QUADCOUNT 10000
#define WINDOWWIDTH 1024
#define WINDOWHEIGHT 768

struct MyQuad
{
    float x1, y1, z1, nx1, ny1, nz1, tx1, ty1;
    float x2, y2, z2, nx2, ny2, nz2, tx2, ty2;
    float x3, y3, z3, nx3, ny3, nz3, tx3, ty3;
    float x4, y4, z4, nx4, ny4, nz4, tx4, ty4;
};

sf::RenderWindow* context;
sf::Event event;
sf::Clock timer;
GLuint VBOVertices;
GLuint texture;
MyQuad* qo;
float ftime;
int fps, nfps;
float fpstime;

int main(int argc, char** argv) {

  context = new sf::RenderWindow(sf::VideoMode(WINDOWWIDTH, WINDOWHEIGHT, 32), "WindowTitle");

  qo = new struct MyQuad[QUADCOUNT];
  for (int x=0; x<QUADCOUNT; x++) {
    qo[x].x1=1; qo[x].y1=1; qo[x].z1=-5; qo[x].nx1=0; qo[x].ny1=0; qo[x].nz1=1; qo[x].tx1=1; qo[x].ty1=0;
    qo[x].x2=0; qo[x].y2=1; qo[x].z2=-5; qo[x].nx2=0; qo[x].ny2=0; qo[x].nz2=1; qo[x].tx2=0; qo[x].ty2=0;
    qo[x].x3=0; qo[x].y3=0; qo[x].z3=-5; qo[x].nx3=0; qo[x].ny3=0; qo[x].nz3=1; qo[x].tx3=0; qo[x].ty3=1;
    qo[x].x4=1; qo[x].y4=0; qo[x].z4=-5; qo[x].nx4=0; qo[x].ny4=0; qo[x].nz4=1; qo[x].tx4=1; qo[x].ty4=1;
  }

  glGenBuffers(1, &VBOVertices);
  glBindBuffer(GL_ARRAY_BUFFER, VBOVertices);
  glBufferData(GL_ARRAY_BUFFER, sizeof(MyQuad)*QUADCOUNT, NULL, GL_DYNAMIC_DRAW);
  glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(MyQuad)*QUADCOUNT, &qo[0].x1);

  delete [] qo;

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60.0,(float)WINDOWWIDTH/(float)WINDOWHEIGHT,1, 100.0);
  glViewport(0,0,WINDOWWIDTH,WINDOWHEIGHT);
  glMatrixMode(GL_MODELVIEW);

  glEnable(GL_TEXTURE_2D);
 
  sf::Image surface;
  if (!surface.LoadFromFile("texture.png")) return 1;
  glGenTextures( 1, &texture );
  glBindTexture( GL_TEXTURE_2D, texture );
  glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, surface.GetWidth(), surface.GetHeight(), 0,
                GL_RGBA, GL_UNSIGNED_BYTE, surface.GetPixelsPtr() );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
 
  while (1)
  {
    while (context->GetEvent(event))
      if (event.Type == sf::Event::KeyPressed)
      {
        if (event.Key.Code == sf::Key::Escape) {
          context->Close();
          exit(0);
        }
      }

    ftime = timer.GetElapsedTime();
    timer.Reset();
    nfps++; fpstime+=ftime; if (fpstime>1.0) { fps = nfps; nfps = 0; fpstime=0; cout << fps << endl; }

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glClearColor(0.8,0.8,1.0,1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, VBOVertices);
    glVertexPointer(3, GL_FLOAT, 32, BUFFER_OFFSET(0));
    glNormalPointer(GL_FLOAT, 32, BUFFER_OFFSET(12));
    glTexCoordPointer(2, GL_FLOAT, 32, BUFFER_OFFSET(24));
    glDrawArrays(GL_QUADS, 0, QUADCOUNT*4);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    context->Display();
  }

  return 0;
}


You'll have to have a texture.png for it to run...

Here are some fps tests I did:
Code: [Select]

QUADCOUNT 10000, WINDOWWIDTH 1024, WINDOWHEIGHT 768  =  16fps
QUADCOUNT 10000, WINDOWWIDTH 800,  WINDOWHEIGHT 600  =  16fps
QUADCOUNT 10000, WINDOWWIDTH 640,  WINDOWHEIGHT 480  =  17fps
QUADCOUNT 10000, WINDOWWIDTH 320,  WINDOWHEIGHT 240  =  63fps

QUADCOUNT 5000, WINDOWWIDTH 1024, WINDOWHEIGHT 768  =  28fps
QUADCOUNT 5000, WINDOWWIDTH 800,  WINDOWHEIGHT 600  =  31fps
QUADCOUNT 5000, WINDOWWIDTH 640,  WINDOWHEIGHT 480  =  32fps
QUADCOUNT 5000, WINDOWWIDTH 320,  WINDOWHEIGHT 240  =  125fps

QUADCOUNT 1000, WINDOWWIDTH 1024, WINDOWHEIGHT 768  =  123fps
QUADCOUNT 1000, WINDOWWIDTH 800,  WINDOWHEIGHT 600  =  143fps
QUADCOUNT 1000, WINDOWWIDTH 640,  WINDOWHEIGHT 480  =  150fps
QUADCOUNT 1000, WINDOWWIDTH 320,  WINDOWHEIGHT 240  =  560fps


It seems to be suffering from the same problem as my original code.  In my original code, I can render 425000 quads with ease,  as long as I create the VBOs before the RenderWindow.

In this minimal code,  it doesn't seem to matter if I create the RenderWindow before or after.   So now I'm really confused, I must be doing something wrong.  :shock:

bkaxrf

  • Newbie
  • *
  • Posts: 10
    • View Profile
VBO, RenderWindow, FPS Drop
« Reply #5 on: January 11, 2011, 03:12:43 pm »
So I converted the minimal code to SDL and it has the same performance, which I expected since the slowdown happens at swapbuffers, which means opengl is busy doing stuff.  At this point I'm thinking most likely my opengl VBO loading/drawing commands are incorrect somewhere.  I'll update again if I figure it out.

Edit:
The slowdown in the minimal code was because the texture I was using was too big, which was slowing down the fill rate.  So I'm back to where I started.

On a side note, I found out that
Code: [Select]

sf::ContextSettings contextsettings(32, 8, 0, 2, 0);
sf::Window context(sf::VideoMode(1024, 768, 32), "SFML OpenGL", sf::Style::Default, contextsettings);

is roughly 4 times as fast as
Code: [Select]

sf::ContextSettings contextsettings(32, 8, 0, 2, 0);
sf::RenderWindow context(sf::VideoMode(1024, 768, 32), "SFML OpenGL", sf::Style::Default, contextsettings);


So by using sf::Window and reducing the texture dimensions, I now get
Code: [Select]

QUADCOUNT 10,000, WINDOWWIDTH 1024, WINDOWHEIGHT 768  =  96fps

Which was 16fps before...huge improvement!
Unfortunately, for 100,000 quads I only get 11fps in the minimal code.

I made the sf::Window change in the original code and it still matters if I create the VBOs first or the Window first.  If I create the VBOs first I get 72fps,  if I create them after I get less than 5 fps.

Edit2:
My original code works when generating the VBOs after the window, but the more I run it, the more likely it is to have the fps drop.  It seems that when I open photoshop and close it,  the fps goes back to normal.  When I create the VBOs before the window, it avoids the FPS drop problem, but doesn't fix it in the same way opening/closing photoshop does,  because when I reverse the order it goes back to fps drop.  So now...I'm looking for a way to put opengl back into a "clean" state, and/or avoid whatever leaks I'm having.

bkaxrf

  • Newbie
  • *
  • Posts: 10
    • View Profile
VBO, RenderWindow, FPS Drop
« Reply #6 on: January 11, 2011, 07:27:21 pm »
I downloaded gDEBugger (which is amazing btw) and used it to debug my app.  

My theory is that by using extension functions from your global context, it's not working for the window context,  and by doing the VBO stuff first, it avoids using the window context.  Or possibly it does work but makes the draws slower for some reason.  I did some research and found this:
Quote

Be careful because the function returned by wglGetProcAddress is only guaranteed to work for the pixel format type of the OpenGL rendering context that was current when wglGetProcAddress was called. If you have multiple contexts created for different pixel formats, then keeping a single function addresses in a global variable as shown above may create problems. You may need to maintain distinct function addresses on a per-pixel format basis. Specifically, the Microsoft documentation for wglGetProcAddress warns:

The [Microsoft] OpenGL library supports multiple implementations of its functions. Extension functions supported in one rendering context are not necessarily available in a separate rendering context. Thus, for a given rendering context in an application, use the function addresses returned by the wglGetProcAddress function only.

http://www.opengl.org/resources/code/samples/sig99/advanced99/notes/node396.html

Also something similar here:
http://msdn.microsoft.com/en-us/library/dd374390.aspx

So my question is, does the global context have a different pixel format type?  Could this be related to my problem?

Going to give up on this problem for a while and accept that I have to create the VBOs first.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
VBO, RenderWindow, FPS Drop
« Reply #7 on: January 11, 2011, 09:42:49 pm »
Quote
So my question is, does the global context have a different pixel format type?

Yes, it is created with sf::ContextSettings(0, 0, 0) -- so no depth/stencil buffer at all compared to your window's context.

This is very interesting, thanks for finding this :)
Laurent Gomila - SFML developer