1
votes

I am trying to draw conway's game of life with opengl. It works fine in regular pygame, but I've read that glTexImage2D is the way to go for fast drawing of stuff already in an array. I've checked out the examples and docs provided but not only are a fair amount of example links dead, but they were written for python 2 so I can't even run a lot of them without translating it. I've noticed that unlike most modern GUI packages, opengl doesn't really return anything so I think I'm just not applying the texture right. (as in, in pygame you'd generate the surface then apply the returned surface). The conways code works by taking in the alive and dead values and the dtype, then doing all the necessary checking based solely on the constructor arguments meaning that that I can change it from ubyte to float in the blink of an eye, so if that's the issue that's great.

At the moment it's just a black screen. When pygame.display.flip() is removed, it just stays white, so theoretically something is being drawn somewhere to change it to black. I have a feeling the problem lies around the glbindtexture method but honestly I don't know what the solution is.

I'll put the conways code in just in case anyone wants to run it. In the pygame mode I scale the image by the scale variable, but for opengl I just want it running first so the size of the conways array will be the window size for now, hence the 400. That means it'll take a bit, but once the window title is updated that's an indicator that the update has been completed.

opengl drawer:

import pygame
from pygame.locals import *

from OpenGL.GL import *
from OpenGL.GLU import *
from random import random,seed
import numpy as np
from conways3 import Conways
from time import time

size = 400;
scale = 1;

conways = Conways(size,dead=0.0,alive=1.0,dtype=np.ubyte)

pygame.init()
flags = OPENGL|HWSURFACE|DOUBLEBUF
display = pygame.display.set_mode((size*scale, size*scale),flags)
########OPTIMIZATIONS##########
pygame.event.set_allowed([pygame.QUIT]);
###############################

running = True

clock = pygame.time.Clock()
t1 = t2 = t3 = 0
glEnable(GL_TEXTURE_2D)
try:
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
        clock.tick()
        t1 = time()
        Z = conways.update()
        t2 = time()
        tid = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, tid)
        glPixelStorei(GL_UNPACK_ALIGNMENT,1)
        glTexImage2D(GL_TEXTURE_2D,0,GL_LUMINANCE, size,size,0,GL_LUMINANCE, GL_UNSIGNED_BYTE, Z)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 

    ##    surf = pygame.surfarray.make_surface(Z)
    ##    display.blit(pygame.transform.scale(surf,(size*scale, size*scale)), (0, 0))
    ##    pygame.display.update()
        pygame.display.flip()
        t3 = time()
        pygame.time.wait(10)
        pygame.display.set_caption("fps: {:.4f} calc: {:.4f} draw: {:.4f} tot: {:.4f}".format(clock.get_fps(), t2-t1, t3-t2,t3-t1))
##        print(t2-t1)
except Exception as e:
    print('-'*20)
    print(e)

pygame.quit()

conways:

from random import random, seed
import numpy as np
from time import time

class Conways:
    def __init__(self,size,dead=False,alive=True,dtype = np.bool8):
        seed(1)

        self.using1 = True;

        self.size = size;
        self.dead = dead;
        self.alive = alive;
        self.dtype = dtype;

        self.arr1 = np.zeros((self.size,self.size),dtype=self.dtype);
        self.arr2 = np.zeros((self.size,self.size),dtype=self.dtype);

        for i in range(self.size):
                for j in range(self.size):
                        self.arr1[i][j] = self.alive*(random() < 0.5);
    def calcNeighbors(self,arr, i, j):
            count = -1*arr[i][j];    
            for x in range(-1, 2):
                    for y in range(-1, 2):
                            count += (arr[(x+i)%self.size][(y+j)%self.size] == self.alive);
            return count;
    def calcEffi(self,arr, i, j):
            count = 0
            maxi = self.size - 1
            if i > 0:
                    count     += arr[i - 1][j] == self.alive
            if i < maxi:
                    count     += arr[i + 1][j] == self.alive
            if j > 0:
                    count     += arr[i][j - 1] == self.alive
                    if i > 0:
                            count += arr[i - 1][j - 1] == self.alive
                    if i < maxi:
                            count += arr[i + 1][j - 1] == self.alive
            if j < maxi:
                    count     += arr[i][j + 1] == self.alive
                    if i > 0:
                            count += arr[i - 1][j + 1] == self.alive
                    if i < maxi:
                            count += arr[i + 1][j + 1] == self.alive
            return count;

    def calc(self,arr1, arr2):
            for i in range(self.size):
                    for j in range(self.size):
                            neighbors = self.calcEffi(arr1, i, j);
                            if neighbors < 2 or neighbors > 3:
                                    arr2[i][j] = self.dead;
                            elif neighbors == 3:
                                    arr2[i][j] = self.alive;
                            else:
                                    arr2[i][j] = arr1[i][j];
    def update(self):
            if self.using1:
                    self.calc(self.arr1,self.arr2);
            else:
                    self.calc(self.arr2,self.arr1);
            self.using1 = not self.using1;
            return self.arr2 if self.using1 else self.arr1;
1

1 Answers

1
votes

Since the array values are in the range [0, 1], you have to use float32 respectively GL_FLOAT:

conways = Conways(size,dead=0.0,alive=1.0,dtype=np.float32)
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, size, size, 0, GL_LUMINANCE, GL_FLOAT, Z)

But the major issue is that you don't draw anything. You have to draw a quad on the entire viewport, with the texture wrapped on it:

glBindTexture(GL_TEXTURE_2D, tid)
glPixelStorei(GL_UNPACK_ALIGNMENT,1)
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, size,size, 0, GL_LUMINANCE, GL_FLOAT, Z)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 

glBegin(GL_TRIANGLE_FAN)
glTexCoord2f(0, 1)
glVertex2f(-1, -1) 
glTexCoord2f(1, 1)
glVertex2f(1, -1) 
glTexCoord2f(1, 0)
glVertex2f(1, 1) 
glTexCoord2f(0, 0)
glVertex2f(-1, 1) 
glEnd()

Or use glEnableClientState, glVertexPointer, glTexCoordPointer and glDrawArrays to draw onto the viewport:

vertices   = np.array([-1, -1, 1, -1, 1, 1, -1, 1], dtype=np.float32)
tex_coords = np.array([0, 1, 1, 1, 1, 0, 0, 0], dtype=np.float32)

glEnableClientState(GL_VERTEX_ARRAY)
glVertexPointer(2, GL_FLOAT, 0, vertices)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glTexCoordPointer(2, GL_FLOAT, 0, tex_coords)

glDrawArrays(GL_TRIANGLE_FAN, 0, 4)

glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_TEXTURE_COORD_ARRAY)

But note, that drawing by glBegin/glEnd sequences and also client-side capability of the OpenGL fixed function pipeline is deprecated since decades. Read about Fixed Function Pipeline and see Vertex Specification and Shader for a state of the art way of rendering.