Hello, I'm making a game with python and OpenGL. I was making it with pygame but I realised quick enough that it was too slow. I've made a class which I want to reasonably act like the Surface class for pygame, where Surface objects can be blitted to other Surface objects. I'm wishing to use framebuffers for rendering textures to textures to do that.
I want to create the textures in my game code at whatever point and store the textures for use at any time. I will call a method from another class which will control the Window and flip the screen. The problem is, I can only get the textures to work when put inside this method at a certain point. I want to be able to create textures and manipulate them outside this method.
Here's the class that manages the textures which I have called Surface similar to the pygame.Surface class,
class Surface():
def __init__(self,size,extra = None):
self.__offset = (0,0)
self.children = []
self.blitted = False
self.last_offset = [0,0]
self.surface_size = [size[0],size[1]]
self.colour = [0,0,0,0]
self.data = None
self.rounded = 0
self.parent = None
self.parent_offset = (0,0)
self.texture = None
def blit(self,surface,offset,area=None, special_flags = 0):
#Create textures if not done already
if self.texture == None:
create_texture(self)
if surface.texture == None:
create_texture(surface)
#Render child to parent
frame_buffer = glGenFramebuffersEXT(1)
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame_buffer)
render_buffer = glGenRenderbuffersEXT(1)
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_buffer);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT,self.surface_size[0],self.surface_size[1])
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_buffer)
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, self.texture, 0)
glPushAttrib(GL_VIEWPORT_BIT)
glViewport(0,0,self.surface_size[0],self.surface_size[1])
draw_texture(surface.texture,offset,surface.surface_size,[float(c)/255.0 for c in surface.colour])
glPopAttrib()
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)
glDeleteFramebuffersEXT(1, [int(frame_buffer)])
glDeleteRenderbuffersEXT(1,[int(render_buffer)])
try:
offset_before_last = surface.last_offset
surface.last_offset = [offset[0] + self.__offset[0],offset[1] + self.__offset[1]]
self.children.append([0,surface]) #0 states it is a surface
surface.parent = self
surface.parent_offset = offset
if surface.get_offset() != surface.last_offset or not surface.blitted:
surface.__set_offset(surface.last_offset)
self.__recursive_offset_add(surface,offset_before_last,surface.last_offset) #Add to the children's offsets
surface.blitted = True
except AttributeError:
pass
def __recursive_offset_add(self,surface,offset_before_last,last_offset):
for child in surface.children:
try:
child.__set_offset((child.get_offset()[0] - offset_before_last[0] + last_offset[0],child.get_offset()[1] - offset_before_last[1] + last_offset[1]))
self.__recursive_offset_add(child,offset_before_last,last_offset)
except AttributeError:
pass
def get_offset(self):
return self.__offset
def __set_offset(self,offset):
self.__offset = offset
def fill(self,colour):
colour = list(colour)
if len(colour) < 4:
colour.append(255)
self.children = []
self.textures = []
self.colour = colour
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):
self.rounded = r
def get_rect(self):
return Rect(self.__offset,self.surface_size)
def __del__(self):
if self.texture != None:
glDeleteTextures([self.texture])
The class which contains the said method, named update, is shown below.
class ScaledScreen(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
def __init__(self,title,game_size,on_exit):
pygame.init()
self.title = title
self.game_size = game_size
screen_info = pygame.display.Info() #Required to set a good resolution for the game screen
self.first_screen = (screen_info.current_w, screen_info.current_h - 120) #Take 120 pixels from the height because the menu bar, window bar and dock takes space
pygame.display.set_caption(self.title)
self.clock = pygame.time.Clock()
self.game_gap = (0,0)
self.on_exit = on_exit
self.mod_key = 1024 if sys.platform == "darwin" else 64
pygame.display.set_mode(self.first_screen,RESIZABLE|DOUBLEBUF|OPENGL)
#OpenGL Parts
Surface.__init__(self,game_size)
self.textures = []
self.screen_change = True
self.opengl_window_setup()
def opengl_window_setup(self):
glViewport(0,0,self.game_scaled[0],self.game_scaled[1]) #Creates the viewport which is mapped to the window
glEnable(GL_LINE_SMOOTH,GL_FASTEST) #Create antialiased lines
glEnable(GL_BLEND) #Enable alpha blending
glEnable(GL_TEXTURE_2D) #Enable 2D Textures
glDisable(GL_DEPTH_TEST) #Disable depth
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) #The recomended blending functions.
glMatrixMode(GL_PROJECTION)
glLoadIdentity() #Load the projection matrix
gluOrtho2D(0,1280,720,0) #Set an orthorgraphic view
def update(self,events):
#Updates screen properly
win_size_done = False #Changes to True if the window size is got by the VIDEORESIZE event below
for event in events:
if event.type == QUIT:
self.on_exit()
if event.type == VIDEORESIZE:
ss = [event.w,event.h]
self.resize = True
win_size_done = True
keys = pygame.key.get_pressed() #Get the pressed keys
if pygame.key.get_mods() & self.mod_key:
if(keys[K_q] or keys[K_w]):
self.on_exit()
if 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
if self.enter_fullscreen:
self.screen_change = True
pygame.mouse.set_visible(False)
self.screen = pygame.display.set_mode((self.first_screen[0],self.first_screen[1]+ 120))
if self.scale_to_screen:
self.game_scaled = (self.screen.get_width(),self.screen.get_height())
else:
self.game_scaled = get_resolution(self.screen,(self.screen.get_width(),self.screen.get_height()),self.game_size)
self.game_gap = [(self.screen.get_width() - self.game_scaled[0])/2,(self.screen.get_height() - self.game_scaled[1])/2]
pygame.display.set_mode((0,0), FULLSCREEN|HWSURFACE|DOUBLEBUF|OPENGL)
self.fs = True
self.enter_fullscreen = False
self.resize = False
elif self.exit_fullscreen:
self.screen_change = True
pygame.mouse.set_visible(True)
pygame.display.set_mode(self.first_screen,RESIZABLE|DOUBLEBUF|OPENGL)
self.fs = False
self.resize = True
self.game_gap = (0,0)
self.exit_fullscreen = False
if self.iconify:
self.on_focus_fullscreen = True
#Scale game to screen resolution, keeping aspect ratio
if self.resize:
self.screen_change = True
if(win_size_done == False): #Sizes not gotten by resize event
ss = [self.screen.get_width(),self.screen.get_height()]
self.game_scaled = get_resolution(self.screen,ss,self.game_size)
pygame.display.set_mode(self.game_scaled,RESIZABLE|DOUBLEBUF|OPENGL)
self.resize = False #Next time do not scale unless resize or fullscreen events occur
if self.iconify:
pygame.display.iconify() #Minimise
self.iconify = False
#Open GL Screen setup
if self.screen_change:
self.opengl_window_setup()
self.screen_change = False
#Below is an example of create a texture of an image and rendering it to the screen. Doing so here works but it wont work outside this method with the rest of my game code.
image = open_image("some_image.png")
self.blit(image,(20,20))
#The example ends here
pygame.display.flip() #Flip buffer
glClear(GL_COLOR_BUFFER_BIT)
Here are two functions that may be important,
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
if surface.data == None:
surf = pygame.Surface((1,1),SRCALPHA)
surf.fill(surface.colour)
data = pygame.image.tostring(surf, "RGBA") * (surface.surface_size[0] * surface.surface_size[1])
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
def open_image(path):
img = pygame.image.load(path)
surf = Surface(img.get_size())
surf.data = pygame.image.tostring(img, "RGBA")
return surf
I've already programmed a lot of the game for pygame. I'm basically creating an OpenGL imitation. It doesn't have to work exactly the same way but I need to prevent massive amounts of my existing code being changed.
The screen always shows as white except with the example of the working image in the update method.
If anyone can help me with this, I'll be most impressed.
Thank you.