Frame Buffer Objects slow and inconsistent

Hello, I’m making a simple 2D game which hence should be very fast. Everything seems to render the way i want it to at the moment but when frame buffers are used every frame in parts of the game, it slows down a lot. On one part of the game which uses one FBO, it runs about 40fps but will occasionally freeze for a second or two. The parts of the game where primitives and textures are only rendered directly to the screen, run fine. It must be these dreaded FBOs.

I’m using them to create rendered graphics which I then render collectively afterwards. This allows me to better apply alpha transparency and render things more than once without re-rendering the components.

The slow-downs happen even with minute frame buffers.

Now for my code. It’s using python but the pyOpenGL code is similar to code found on other languages. Thank you if anyone can help me with this.

I included the entire relevant module so nothing can be missed. It shouldn’t be too difficult to look for OpenGL parts. setup_framebuffer and end_framebuffer may be important functions.

#!/usr/bin/env python2.3
#
#  Automatic Game Scaling and 2D surface library for OpenGL
#
#  Allows resize of a Window while scaling the game, keeping the aspect ratio.
#
#  Created by Matthew Mitchell on 13/09/2009.
#  Copyright (c) 2009 Matthew Mitchell. All rights reserved.
#
#Import modules
import sys,os
import math as maths
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from OpenGL.GL.ARB.framebuffer_object import *
from OpenGL.GL.EXT.framebuffer_object import *
import time,threading
arc_factors = ((1.0, 0.0), (0.99984769515639127, 0.017452406437283512), (0.99939082701909576, 0.034899496702500969), (0.99862953475457383, 0.052335956242943835), (0.9975640502598242, 0.069756473744125302), (0.99619469809174555, 0.087155742747658166), (0.99452189536827329, 0.10452846326765347), (0.99254615164132198, 0.12186934340514748), (0.99026806874157036, 0.13917310096006544), (0.98768834059513777, 0.15643446504023087), (0.98480775301220802, 0.17364817766693033), (0.98162718344766398, 0.1908089953765448), (0.97814760073380569, 0.20791169081775934), (0.97437006478523525, 0.224951054343865), (0.97029572627599647, 0.24192189559966773), (0.96592582628906831, 0.25881904510252074), (0.96126169593831889, 0.27563735581699916), (0.95630475596303544, 0.29237170472273677), (0.95105651629515353, 0.3090169943749474), (0.94551857559931685, 0.3255681544571567), (0.93969262078590843, 0.34202014332566871), (0.93358042649720174, 0.35836794954530027), (0.92718385456678742, 0.37460659341591201), (0.92050485345244037, 0.39073112848927377), (0.91354545764260087, 0.40673664307580021), (0.90630778703664994, 0.42261826174069944), (0.89879404629916704, 0.4383711467890774), (0.8910065241883679, 0.45399049973954675), (0.88294759285892699, 0.46947156278589081), (0.87461970713939574, 0.48480962024633706), (0.86602540378443871, 0.49999999999999994), (0.85716730070211233, 0.51503807491005416), (0.84804809615642596, 0.5299192642332049), (0.83867056794542405, 0.54463903501502708), (0.82903757255504162, 0.5591929034707469), (0.8191520442889918, 0.57357643635104605), (0.80901699437494745, 0.58778525229247314), (0.79863551004729283, 0.60181502315204827), (0.7880107536067219, 0.61566147532565829), (0.7771459614569709, 0.62932039104983739), (0.76604444311897812, 0.64278760968653925), (0.75470958022277201, 0.65605902899050728), (0.74314482547739424, 0.66913060635885824), (0.73135370161917046, 0.68199836006249848), (0.71933980033865119, 0.69465837045899725), (0.70710678118654757, 0.70710678118654746), (0.70710678118654746, 0.70710678118654757), (0.69465837045899725, 0.71933980033865119), (0.68199836006249848, 0.73135370161917046), (0.66913060635885824, 0.74314482547739424), (0.65605902899050728, 0.75470958022277201), (0.64278760968653925, 0.76604444311897812), (0.62932039104983739, 0.7771459614569709), (0.61566147532565829, 0.7880107536067219), (0.60181502315204827, 0.79863551004729283), (0.58778525229247314, 0.80901699437494745), (0.57357643635104605, 0.8191520442889918), (0.5591929034707469, 0.82903757255504162), (0.54463903501502708, 0.83867056794542405), (0.5299192642332049, 0.84804809615642596), (0.51503807491005416, 0.85716730070211233), (0.49999999999999994, 0.86602540378443871), (0.48480962024633706, 0.87461970713939574), (0.46947156278589081, 0.88294759285892699), (0.45399049973954675, 0.8910065241883679), (0.4383711467890774, 0.89879404629916704), (0.42261826174069944, 0.90630778703664994), (0.40673664307580021, 0.91354545764260087), (0.39073112848927377, 0.92050485345244037), (0.37460659341591201, 0.92718385456678742), (0.35836794954530027, 0.93358042649720174), (0.34202014332566871, 0.93969262078590843), (0.3255681544571567, 0.94551857559931685), (0.3090169943749474, 0.95105651629515353), (0.29237170472273677, 0.95630475596303544), (0.27563735581699916, 0.96126169593831889), (0.25881904510252074, 0.96592582628906831), (0.24192189559966773, 0.97029572627599647), (0.224951054343865, 0.97437006478523525), (0.20791169081775934, 0.97814760073380569), (0.1908089953765448, 0.98162718344766398), (0.17364817766693033, 0.98480775301220802), (0.15643446504023087, 0.98768834059513777), (0.13917310096006544, 0.99026806874157036), (0.12186934340514748, 0.99254615164132198), (0.10452846326765347, 0.99452189536827329), (0.087155742747658166, 0.99619469809174555), (0.069756473744125302, 0.9975640502598242), (0.052335956242943835, 0.99862953475457383), (0.034899496702500969, 0.99939082701909576), (0.017452406437283512, 0.99984769515639127), (0.0, 1.0))
MUSICEND = USEREVENT
def find_angle(dy,dx): #Find angle from dx and dy
	if dx == 0: #Can't divide by 0
		angle = 1.5707963267948966 #Should be pi/2 radians or 90 degrees
		if dy > 0:
			angle = -angle
	else:
		angle = maths.atan(float(dy)/float(dx))  #Find angle of line
	return angle
def get_resolution(ss,gs):
	gap = float(gs[0]) / float(gs[1])
	sap = float(ss[0]) / float(ss[1])
	if gap > sap:
		#Game aspect ratio is greater than screen (wider) so scale width
		factor = float(gs[0]) /float(ss[0])
		new_h = gs[1]/factor #Divides the height by the factor which the width changes so the aspect ratio remians the same.
		game_scaled = (ss[0],new_h)
	elif gap < sap:
		#Game aspect ratio is less than the screens.
		factor = float(gs[1]) /float(ss[1])
		new_w = gs[0]/factor #Divides the width by the factor which the height changes so the aspect ratio remians the same.
		game_scaled = (new_w,ss[1])
	else:
		game_scaled = ss
	return game_scaled
def sound(f):
	if len(sys.argv) > 1:
		if sys.argv[1] == "-m":
			return DummySound()
	return pygame.mixer.Sound(os.path.dirname(sys.argv[0]) + f)
def play_music(f):
	if f != "":
		mute = False
		if len(sys.argv) > 1:
			if sys.argv[1] == "-m":
				mute = True
		if not mute:
			global music_stop
			music_stop = False
			pygame.mixer.music.load(os.path.dirname(sys.argv[0]) + f)
			pygame.mixer.music.play(0)
			pygame.mixer.music.set_endevent(MUSICEND)
def gaussian_blur(*args):
	pass
def create_texture(surface):
	surface.texture = glGenTextures(1)
	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity() #Loads model matrix
	glBindTexture(GL_TEXTURE_2D, surface.texture) #Binds the current 2D texture to the texture to be drawn
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) #Required to be set for maping the pixel data
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) #Similar as above
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface.surface_size[0], surface.surface_size[1], 0, GL_RGBA,GL_UNSIGNED_BYTE, surface.data) #Put surface pixel data into texture
	if surface.data is None:
		setup_framebuffer(surface)
		c = [float(sc)/255.0 for sc in surface.colour] #Divide colours by 255 because OpenGL uses 0-1
		if surface.background_alpha != None:
			c[3] = float(surface.background_alpha)/255.0
		glClearColor(*c)
		glClear(GL_COLOR_BUFFER_BIT)
		end_framebuffer()
	Surface.texture_ready.append(surface)
def open_image(path):
	img = pygame.image.load(path)
	surf = Surface(img.get_size())
	surf.data = pygame.image.tostring(img, "RGBA")
	return surf
def add_line(surface,c,a,b,w = 1,antialias = False):
	if surface.__class__ != Game: #Only use a frame buffer if the line isn't being drawn to the screen.
		setup_framebuffer(surface)
	glDisable(GL_TEXTURE_2D)
	if antialias:
		glEnable(GL_LINE_SMOOTH) #Enable line smoothing.
	c = [float(sc)/255.0 for sc in c] #Divide colours by 255 because OpenGL uses 0-1
	if len(c) != 4:
		c.append(1) #Add a value for aplha transparency if needed
	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity() #Loads model matrix
	glColor4f(*c)
	glLineWidth(w)
	glBegin(GL_LINES)
	glVertex2f(*a)
	glVertex2f(*b)
	glEnd()
	if antialias:
		glDisable(GL_LINE_SMOOTH) #Disable line smoothing.
	glEnable(GL_TEXTURE_2D)
	if surface.__class__ != Game:
		end_framebuffer()
def setup_framebuffer(surface):
	#Create texture if not done already
	if surface.texture is None:
		create_texture(surface)
	#Render child to parent
	if surface.frame_buffer is None:
		surface.frame_buffer =  glGenFramebuffersEXT(1)
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, surface.frame_buffer)
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, surface.texture, 0)
	glPushAttrib(GL_VIEWPORT_BIT)
	glViewport(0,0,surface._scale[0],surface._scale[1])
	glMatrixMode(GL_PROJECTION)
	glLoadIdentity() #Load the projection matrix
	gluOrtho2D(0,surface._scale[0],0,surface._scale[1])
def end_framebuffer():
	glPopAttrib()
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)
	glMatrixMode(GL_PROJECTION)
	glLoadIdentity() #Load the projection matrix
	gluOrtho2D(0,1280,720,0) #Set an orthorgraphic view
def add_lines(surface,c,coordinates,w =1,antialias = True):
	if surface.__class__ == Surface: #Only use a frame buffer if the line isn't being drawn to the screen.
		setup_framebuffer(surface)
	last = None
	glDisable(GL_TEXTURE_2D)
	if antialias:
		glEnable(GL_LINE_SMOOTH) #Enable line smoothing.
	c = [float(sc)/255.0 for sc in c] #Divide colours by 255 because OpenGL uses 0-1
	if len(c) != 4:
		c.append(1) #Add a value for aplha transparency if needed
	glColor4f(*c)
	glLineWidth(w)
	glBegin(GL_LINE_STRIP)
	for coordinate in coordinates: #Loop though the coordinates and draw the lines
		glVertex2f(*coordinate)
	glEnd()
	if antialias:
		glDisable(GL_LINE_SMOOTH) #Disable line smoothing.
	glEnable(GL_TEXTURE_2D)
	if surface.__class__ == Surface: #Only use a frame buffer if the line isn't being drawn to the screen.
		end_framebuffer()
def font_convert(font):
	surf = Surface(font.get_size())
	surf.data = pygame.image.tostring(font, "RGBA")
	return surf
def rotate_coordinate(coordinate,centre,angle):
	dx = coordinate[0] - centre[0] #Remove centre point ordinates so the rotation only applies from this center.
	dy = - coordinate[1] + centre[1] #Remember that the y-axis is flipped with Open GL top low to bottom high
	return (centre[0] + dx*maths.cos(angle) + dy*maths.sin(angle),centre[1] - dy*maths.cos(angle) + dx*maths.sin(angle)) #Rotation trigonometry with the centre point added
def draw_texture(texture,offset,size,a,rounded,sides,angle,point):
	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity() #Loads model matrix
	glColor4f(1,1,1,float(a)/255.0)
	glBindTexture(GL_TEXTURE_2D, texture)
	if rounded == 0:
		if angle == 0:
			glBegin(GL_QUADS)
			glTexCoord2f(0.0, 0.0)
			glVertex2i(*offset) #Top Left
			glTexCoord2f(0.0, 1.0)
			glVertex2i(offset[0],offset[1] + size[1]) #Bottom Left
			glTexCoord2f(1.0, 1.0)
			glVertex2i(offset[0] + size[0],offset[1] + size[1]) #Bottom, Right
			glTexCoord2f(1.0, 0.0)
			glVertex2i(offset[0] + size[0],offset[1]) #Top, Right
			glEnd()
		else:
			glBegin(GL_QUADS)
			glTexCoord2f(0.0, 0.0)
			glVertex2f(*rotate_coordinate(offset,point,angle)) #Top Left
			glTexCoord2f(0.0, 1.0)
			glVertex2f(*rotate_coordinate((offset[0],offset[1] + size[1]),point,angle)) #Bottom Left
			glTexCoord2f(1.0, 1.0)
			glVertex2f(*rotate_coordinate((offset[0] + size[0],offset[1] + size[1]),point,angle)) #Bottom, Right
			glTexCoord2f(1.0, 0.0)
			glVertex2f(*rotate_coordinate((offset[0] + size[0],offset[1]),point,angle)) #Top, Right
			glEnd()
	else:
		global arc_factors
		arc = [[o*rounded for o in c] for c in arc_factors]
		glBegin(GL_POLYGON)
		if sides % 2:
			for c in arc:
				coordinates = (offset[0] + rounded - c[0],offset[1] + rounded - c[1])
				glTexCoord2f((coordinates[0]-offset[0])/size[0],(coordinates[1]-offset[1])/size[1])
				glVertex2f(*coordinates)
		else:
			glTexCoord2f(0.0, 0.0)
			glVertex2f(*rotate_coordinate(offset,point,angle)) #Top Left
		if sides % 4 > 1:
			for c in arc[::-1]:
				coordinates = (offset[0] + size[0] - rounded + c[0],offset[1] + rounded - c[1])
				glTexCoord2f((coordinates[0]-offset[0])/size[0],(coordinates[1]-offset[1])/size[1])
				glVertex2f(*coordinates)
		else:
			glTexCoord2f(1.0, 0.0)
			glVertex2f(*rotate_coordinate((offset[0] + size[0],offset[1]),point,angle)) #Top, Right
		if sides % 8 > 3:
			for c in arc:
				coordinates = (offset[0] + size[0] - rounded + c[0],offset[1] + size[1] - rounded + c[1])
				glTexCoord2f((coordinates[0]-offset[0])/size[0],(coordinates[1]-offset[1])/size[1])
				glVertex2f(*coordinates)
		else:
			glTexCoord2f(1.0, 1.0)
			glVertex2f(*rotate_coordinate((offset[0] + size[0],offset[1] + size[1]),point,angle)) #Bottom, Right
		if sides > 7:
			for c in arc[::-1]:
				coordinates = (offset[0] + rounded - c[0],offset[1] + size[1] - rounded + c[1])
				glTexCoord2f((coordinates[0]-offset[0])/size[0],(coordinates[1]-offset[1])/size[1])
				glVertex2f(*coordinates)
		else:
			glTexCoord2f(0.0, 1.0)
			glVertex2f(*rotate_coordinate((offset[0],offset[1] + size[1]),point,angle)) #Bottom Left
		glEnd()
def texture_to_texture(target,surface,offset,rounded,rotation,point):
	#Create texture if not done already
	if surface.texture is None:
		create_texture(surface)
	#Render child to parent
	setup_framebuffer(target)
	draw_texture(surface.texture,offset,surface._scale,surface.colour[3],rounded,surface.rounded_sides,rotation,point)
	end_framebuffer()
def texture_to_screen(surface,offset,rotation,point):
	if surface.texture is None:
		create_texture(surface)
	draw_texture(surface.texture,offset,surface._scale,surface.colour[3],surface.rounded,surface.rounded_sides,rotation,point)
class DummySound():
	def play(self):
		pass
	def set_volume(self):
		pass
class Surface():
	texture_ready = []
	def __init__(self,size,extra = None):
		self._offset = (0,0)
		self.children = []
		self.blitted = False
		self.last_offset = [0,0]
		self.surface_size = list(size)
		self.colour = [0,0,0,255]
		self.data = None
		self.rounded = 0
		self.parent = None
		self.parent_offset = (0,0)
		self.texture = None
		self.frame_buffer = None
		self._scale = size
		self.background_alpha = None
		self.rounded_sides = 0
	def blit(self,surface,offset,rotation = 0,point = (0,0)):
		texture_to_texture(self,surface,offset,surface.rounded,rotation,point)
		if surface not in self.children:
			self.children.append(surface)
		if surface.parent_offset != offset or not surface.blitted:
			surface.parent_offset = offset
			surface._offset = [offset[0] + self._offset[0],offset[1] + self._offset[1]]
			surface.recursive_offset_change() #Add to the children's offsets
			surface.blitted = True
	def set_background_alpha(self,alpha):
		self.background_alpha = float(alpha)/255.0
	def recursive_offset_change(self):
		for child in self.children:
			child._offset = (self._offset[0] + child.parent_offset[0],self._offset[1] + child.parent_offset[1])
			child.recursive_offset_change()
	def get_offset(self):
		return self._offset
	def fill(self,colour):
		colour = list(colour)
		if len(colour) < 4:
			colour.append(255)
		self.children = []
		self.textures = []
		self.colour = colour
		if self.texture != None:
			glDeleteTextures([self.texture])
			self.data = None
			create_texture(self)
	def get_size(self):
		return self.surface_size
	def get_width(self):
		return self.surface_size[0]
	def get_height(self):
		return self.surface_size[1]
	def round_corners(self,r,sides = 15):
		self.rounded = r
		self.rounded_sides = sides
	def get_rect(self):
		return Rect(self._offset,self.surface_size)
	def scale(self,scale):
		self._scale = scale
		return self
	def __del__(self):
		if self.texture != None:
			glDeleteTextures([self.texture])
		if self.frame_buffer != None:
			glDeleteFramebuffersEXT(1, [int(self.frame_buffer)])
class Game(Surface):
	game_size = None
	first_screen = None
	screen = None
	fs = False #Fullscreen false to start
	clock = None
	resize = True
	game_gap = None
	game_scaled = (0,0)
	title = None
	fps = -1
	enter_fullscreen = False
	exit_fullscreen = False
	scale_to_screen = False
	iconify = False
	on_focus_fullscreen = False
	f_key = False
	fade = 0
	p_key = False
	music_stop = False
	unfade = False
	event_after_fade = -1
	loaded = False
	fade = 255
	unfade = True
	homedir = os.path.expanduser("~")
	fade_screen = False
	keys = []
	events = []
	sections = []
	back_key = False
	transfer_args = ()
	mouse_pos = (0,0)
	def __init__(self,title,game_size,on_exit = sys.exit):
		self.keys = [False] * 323
		self.events = []
		pygame.font.init()
		pygame.mixer.init()
		self.title = title
		self.game_size = game_size
		self.first_screen = (1280,720) #Take 120 pixels from the height because the menu bar, window bar and dock takes space
		glutInit(sys.argv)
		glutInitWindowPosition(0,0)
		glutInitWindowSize(*game_size)
		glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA)
		glutGameModeString("1280x720:32@60") #720 HD
		glutCreateWindow(title)
		glutSetIconTitle(title)
		self.callbacks()
		self.game_gap = (0,0)
		self.on_exit = on_exit
		self.mod_key = 1024 if sys.platform == "darwin" else 64
		Surface.__init__(self,game_size)
		self.screen_change = True
		self.frames = [time.time()]
		self.fps = 60
		self.last_time = 0
		self.fade_surface = Surface([1280,720])
	def callbacks(self):
		glutReshapeFunc(self.reshaped)
		glutKeyboardFunc(self.keydown)
		glutKeyboardUpFunc(self.keyup)
		glutSpecialFunc(self.specialdown)
		glutSpecialUpFunc(self.specialup)
		glutDisplayFunc(self.game_loop)
		glutIdleFunc(self.game_loop)
		glutMouseFunc(self.mouse_func)
		glutPassiveMotionFunc(self.mouse_move)
		glutMotionFunc(self.mouse_move)
		glViewport(0,0,self.first_screen[0],self.first_screen[1]) #Creates the viewport which is mapped to the window
		glEnable(GL_BLEND) #Enable alpha blending
		glEnable(GL_TEXTURE_2D) #Enable 2D Textures
		glEnable(GL_POLYGON_SMOOTH) #Enable antialiased polygons
		glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST)
		glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
		glMatrixMode(GL_PROJECTION)
		glLoadIdentity() #Load the projection matrix
		gluOrtho2D(0,1280,720,0) #Set an orthorgraphic view
	def add_section(self,section_object):
		self.sections.append(section_object)
	def mouse_func(self,button, state, x, y):
		self.events.append((state,button,x,y))
	def mouse_move(self,x,y):
		self.events.append((MOUSEMOTION,x - self.mouse_pos[0], y - self.mouse_pos[1]))
		self.mouse_pos = (x,y)
	def keydown(self,char,x,y):
		#300 miliusecond delay, 50 milisecond repeat
		self.change_keys(char,True)
	def keyup(self,char,x,y):
		self.change_keys(char,False)
	def change_keys(self,char,bool):
		char = ord(char)
		#Switch backspace and delete
		if char == 8:
			char = 127
		elif char == 127:
			char = 8
		self.keys[char] = bool
	def specialdown(self,char,x,y):
		if char == GLUT_KEY_UP:
			self.keys[K_UP] = True
		if char == GLUT_KEY_DOWN:
			self.keys[K_DOWN] = True
		if char == GLUT_KEY_LEFT:
			self.keys[K_LEFT] = True
		if char == GLUT_KEY_RIGHT:
			self.keys[K_RIGHT] = True
	def specialup(self,char,x,y):
		if char == GLUT_KEY_UP:
			self.keys[K_UP] = False
		if char == GLUT_KEY_DOWN:
			self.keys[K_DOWN] = False
		if char == GLUT_KEY_LEFT:
			self.keys[K_LEFT] = False
		if char == GLUT_KEY_RIGHT:
			self.keys[K_RIGHT] = False
	def reshaped(self,w,h):
		#Scale game to screen resolution, keeping aspect ratio
		self.screen_change = True
		self.game_scaled = get_resolution((w,h),self.game_size)
		glutReshapeWindow(*self.game_scaled)
		glViewport(0,0,self.game_scaled[0],self.game_scaled[1])
		glutPositionWindow((1280- w)/2,(720 - h)/2)
	def game_loop(self):
		self.section.loop()
		if self.unfade:
			if self.fade == 255:
				play_music(self.section.music)
			if self.fade > 0:
				self.fade -= 5
			else:
				self.music_stop = False
				self.unfade = False
		if self.fade_screen and not self.unfade: #Fade out
			if self.fade == 0:
				sound("/sounds/menu3/fade.ogg").play()
				self.music_stop = True
				pygame.mixer.music.fadeout(850)
			if self.fade < 255:
				self.fade += 5
			else:
				self.fade_screen = False
				self.unfade = True
		if self.fade_screen == False:
			if self.event_after_fade != -1:
				self.section = self.sections[self.event_after_fade]
				self.section.transfer(*self.transfer_args)
				self.transfer_args = ()
				self.event_after_fade = -1
		self.fade_surface.fill((0,0,0,self.fade))
		self.blit(self.fade_surface,(0,0))
		for event in self.events:
			if event[1] == MUSICEND and self.music_stop == False:
				play_music(self.section.music)
		self.events = [] #Remove events
		global draw_texture_time
		#Updates screen properly
		for event in self.events:
			if event.type == QUIT:
				self.on_exit()
		if True:
			if self.keys[K_f]:
				if self.f_key == False:
					self.f_key = True
					if self.fs == False:
						self.enter_fullscreen = True
					else:
						self.exit_fullscreen = True
			else:
				self.f_key = False
		if self.on_focus_fullscreen and pygame.display.get_active():
			self.on_focus_fullscreen = False
			self.enter_fullscreen = True
		pixel_data = []
		if self.enter_fullscreen or self.exit_fullscreen:
			for surface in Surface.texture_ready:
				if surface.texture != None:
					glBindTexture(GL_TEXTURE_2D, surface.texture)
					glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,surface.data)
					surface.texture = None
				if surface.frame_buffer != None:
					pixel_data.append((surface,None))
					glReadPixels(0,0,surface.surface_size[0],surface.surface_size[1],GL_BGRA,GL_UNSIGNED_BYTE,pixel_data[-1][1])
			Surface.texture_ready = []
		if self.enter_fullscreen:
			glutEnterGameMode()
			self.callbacks()
			self.fs = True
			self.enter_fullscreen = False
		elif self.exit_fullscreen:
			glutSetCursor(GLUT_CURSOR_INHERIT)
			self.fs = False
			glutLeaveGameMode()
			self.callbacks()
			self.exit_fullscreen = False
			if self.iconify:
				self.on_focus_fullscreen = True
		if self.enter_fullscreen or self.exit_fullscreen:
			for surface, data in pixel_data:
				surface.frame_buffer =  glGenFramebuffersEXT(1)
				setup_framebuffer(surface)
				glDrawPixels(surface.surface_size[0],surface.surface_size[1],GL_RGBA,GL_UNSIGNED_BYTE,data)
				end_framebuffer()
		if self.iconify:
			pygame.display.iconify() #Minimise
			self.iconify = False
		glFlush()
		glutSwapBuffers() #Flip buffer
		glClear(GL_COLOR_BUFFER_BIT)
		self.frames.append(time.time())
		time_d = (self.frames[-1] - self.frames[-2])
		if time_d < 0.01667:
			time.sleep(0.01667 - time_d)
			self.frames[-1] = time.time()
		self.fps = len(self.frames)/(self.frames[-1] - self.frames[0])
		if self.fps > 60:
			self.fps = 60
		self.frames = [frame for frame in self.frames if (self.frames[-1] - frame) < 1]
		glutSetWindowTitle(self.title + " - " + str(int(self.fps)) + "fps")
	def blit(self,surface,offset,rotation = 0,point = (0,0)):
		if surface.get_offset() != offset or not surface.blitted:
			surface._offset = offset
			surface.recursive_offset_change() #Add to the children's offsets
		surface.blitted = True
		texture_to_screen(surface,offset,rotation,point)
	def transfer_section(self,section,args=()):
		self.transfer_args = args
		self.event_after_fade = section
		self.fade_screen = True

I am guessing the FBOs are either not a power of two or too big.

My old ATI card was seriously slow using FBO’s. I found if i used glScissor on it, to just use the area I wanted it would speed up a lot.

Thank you very much for the reply.

So that’s the problem? They need to be powers of two? Hmm. I did read about that but apparently later versions of OpenGL work without powers of two. It said nothing about speed problems.

So, what I need to do it make the height and width, go to the next power of two and do the same with textures? And that will give a transparent edge I do not need to worry about?

And thank you dukey, the FBOs are slow when very small as well as large. They are almost always shown completely on the screen also.

Are you by chance using a Radeon 9600? I remember I had troubles doing RTT on it ages ago, so I guess when it got the FBO extension it was just some patch-work. But I specifically tested textureRect on R9550 (basically the same card) last year, and it was really fast (maybe only 50% slower than POT textures).

I want the game to run well on all modern graphics processors.

I have a MacBook White with the nVidia 9400M chip. Apparently that’s a good graphics processor.

I might try the powers of two thing myself but I’m almost clueless how to implement it.

Edit:

Yes, completely clueless:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface.surface_size[0], surface.surface_size[1], 0, GL_RGBA,GL_UNSIGNED_BYTE, surface.data) #Put surface pixel data into texture

If I change the size for that then the pixels wont be placed right.

It’s difficult to do because the python cProfile module doesn’t work with glut but I’m looking for the slow areas.

So far I’ve found that setup_framebuffer, end_framebuffer and render_texture all slow down the game considerably when frame buffers are being used.

It seems whenever the framebuffers are being rendered to most of those three functions slow down considerably. Not considerably in-fact, massively.

I really do need help. This is completely alien to me. I just keep thinking that it’s ridiculous. It’s a simple 2D game. I have used 3D games and applications with no problem on this machine.

I can only assume the fix must be simple because of this.

I tried to run your program but nothing happens.
To be honnest, I am not experienced at all in python.
I got python 2.6, PIL, PyOpenGL, numpy, pygame.
Tried stepping the program execution in debug mode but I am quite lost.

What is the exact command line needed to run it ? And is the program complete ?

Thank you for looking.

That isn’t the whole game. That is just a module with the OpenGL things inside it.

I will post the entire game, as it is now, in one moment, in which case the main.py should be run with the python command or a python launcher.

This post will be updated soon.

This is it: http://godofgod.co.uk/my_files/opengl_problem.zip

To reproduce the error you will need a large image. I think it needs to be over 2000x2000 pixels. Go onto the mapmaker, select new and then your image. Then start moving the image with the navigation hand. This is one part of the game which will use framebuffers a lot. It will slow down massively. Then try adding a spawn point on the items. Then click preview. Press up to shoot. This will cause the ammo to change on the HUD which uses frame buffers. It will slow doen massively.

An easy way to to play the Anaconda minigame. It plays quite fast but freezes every now and then. I’m guessing it’s the frame buffers again.

Maybe the problems will be different for other people but I want it to work well for everyone that has a reasonably modern GPU. By that I see no reason why anything from the last decade wont suffice.

I could “play” anaconda. Silk smooth during a minute, could not be patient enough to wait more :slight_smile:
However, on the mapmaker, clicking “New” crashed consistently with a python stacktrace, so I could not test.

Vista 64 SP2, geforce GTX 275, fw 191.07

BTW, it is a good idea to provide a simple test case for others to test/debug, as few people like to dig through a whole game to debug it for you.

The only code which OpenGL is what I’ve shown in the first post and I doubt I have made any programming errors. It’s all to do with the performance of OpenGL from what I can see.

Clicking “New” isn’t supposed to crash, obviously, and it works for me. Do you still have the error with the traceback?

Do you doubt the problem is having non-power of two textures and FBOs then?

The easiest way to reproduce the problem is via the mapmaker. I didn’t expect it not to work.


...
Frameuffer Time - 0.00200009346008
Frame Time - 0.0166833321253
Frameuffer Time Percentage - 11.9885730564
Traceback (most recent call last):
  File "C:\Python26\Lib\site-packages\OpenGL\GLUT\special.py", line 120, in safeCall
    return function( *args, **named )
  File "D:\opengl_problem\opengl_problem\scalelib.py", line 435, in game_loop
    self.section.loop()
  File "D:\opengl_problem\opengl_problem\main.py", line 880, in loop
    result = self.file_browser.loop(mouse_pos,event)
  File "D:\opengl_problem\opengl_problem\main.py", line 580, in loop
    self.folder_viewpoint.mouse(mouse_pos,event)
  File "D:\opengl_problem\opengl_problem\main.py", line 476, in mouse
    self.render(self.scrollbar.get_pos())
  File "D:\opengl_problem\opengl_problem\main.py", line 458, in render
    self.viewport.blit(self.contents,(0,length*pos))
  File "D:\opengl_problem\opengl_problem\scalelib.py", line 264, in blit
    texture_to_texture(self,surface,offset,surface.rounded,rotation,point)
  File "D:\opengl_problem\opengl_problem\scalelib.py", line 231, in texture_to_texture
    create_texture(surface)
  File "D:\opengl_problem\opengl_problem\scalelib.py", line 76, in create_texture
    setup_framebuffer(surface)
  File "D:\opengl_problem\opengl_problem\scalelib.py", line 124, in setup_framebuffer
    gluOrtho2D(0,surface._scale[0],0,surface._scale[1])
  File "D:\OpenGL\error.py", line 208, in glCheckError
    baseOperation = baseOperation,
GLError: GLError(
	err = 1281,
	description = 'valeur non valide',
	baseOperation = gluOrtho2D,
	cArguments = (0, 0, 0, 0)
)
GLUT Idle callback <bound method Game.game_loop of <scalelib.Game instance at 0x03829A80>> with (),{} failed: returning None GLError(
	err = 1281,
	description = 'valeur non valide',
	baseOperation = gluOrtho2D,
	cArguments = (0, 0, 0, 0)
)

And by the way, I can no longer run anaconda, don’t know why, maybe something about not being able to run fullscreen and/or having the window task bar on the left, above game window… :


Frameuffer Time - 0.00300002098083
Frame Time - 0.0164098387859
Frameuffer Time Percentage - 18.2818431063
Traceback (most recent call last):
  File "C:\Python26\Lib\site-packages\OpenGL\GLUT\special.py", line 120, in safeCall
    return function( *args, **named )
  File "D:\opengl_problem\opengl_problem\scalelib.py", line 457, in game_loop
    self.section.transfer(*self.transfer_args)
  File "D:\opengl_problem\opengl_problem\main.py", line 1230, in transfer
    for x in range(0,self.snake_length,2):
TypeError: range() integer end argument expected, got float.
GLUT Idle callback <bound method Game.game_loop of <scalelib.Game instance at 0x03829A80>> with (),{} failed: returning None range() integer end argument expected, got float.

At least the music sound entertaining :slight_smile:

Thank you. I made the menu music. Someone else made the Anaconda music, who is very good and will make many other songs for the game.

Thanks for showing the errors…

For the range error, you should get:

DeprecationWarning: integer argument expected, got float

I get that with python 2.6. Maybe you have a later 2.6 version, I have no idea. I suppose the line should be:

for x in range(0,int(self.snake_length),2):

As for the “New” problem, I think changing the line 421:

	self.contents = Surface((0,0))

to

	self.contents = Surface((1,1))

Will fix the problem. Not sure why the contents surfaces isn’t being filled with folders though.

I’ve waited long enough so I’m going to move this to the top.

The Anaconda part of this game will be made for my A2 Computing project. I should have finished it by now so I suppose I’ll have to remove the framebuffer from the Anaconda game so I can move on with the rest of the project. I still need the framebuffers to work for the main game. I have people with expectations for this game and I have gone through much of it already so it is very important still.

Sorry but I tried your patches to make it work, to no avail. Sorry for you.

Really? Same errors?

Don’t waste your time with this. I really do appreciate you trying.

I guess I could isolate the problematic code and put together a test piece…

Okay, the code I have below should replace main.py. It shows that a single part which uses FBOs is slowing the game but only a tiny bit. It isn’t slow enough to make a significance in the overall games speed. There must be something else going on which is still related with the FBOs. :confused:

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
#
#  main.py
#
#  Created by Matthew Mitchell on 15/09/2009.
#  Copyright © 2009 Matthew Mitchell. All rights reserved.
#  
#  Test file for FBO issues
from scalelib import *
def exit_game():
	game.section.exit()
	sys.exit()
class Test():
	#The game which is the main part of the entire program
	music = ""
	def __init__(self):
		self.hud_corner_bottom_left = Surface((200,100))
		self.hud_corner_bottom_left.round_corners(20,2)
		self.ammo_font = pygame.font.Font(os.path.dirname(sys.argv[0]) + "/NEUROPOL.ttf",40)
		self.text = font_convert(self.ammo_font.render(str("TESTING"),True,[255,255,255,255]))
	def transfer(self):
		pass
	def loop(self):
		self.hud_corner_bottom_left.fill([100,100,200,230])
		self.hud_corner_bottom_left.blit(self.text,((200 - self.text.get_width())/2,(100 - self.text.get_height())/2))
		game.blit(self.hud_corner_bottom_left,(0,620))
	def exit(self):
		pass
if __name__ == '__main__': #Run if being run directly and not as a module
	game = Game("TimeSplitters Platinum Pre-Alpha",[1280,720],exit_game)  #1280x720 HD resolution game. Creates an object with the game Surface
	game.add_section(Test())
	game.section = game.sections[0]
	glutMainLoop(); #Main loop for glut