Jump to content

  • Log In with Google      Sign In   
  • Create Account

kloffy

Member Since 10 Apr 2005
Offline Last Active Yesterday, 11:52 PM

Posts I've Made

In Topic: 2.5D Movement Problem

27 July 2016 - 05:55 PM

If you want code, look at gluUnProject. Whenever I needed it, I used to look at the Mesa3D sources, but it seems the most recent ones don't have it anymore.

Anyway, older version clones can easily be found:

https://github.com/krh/mesa/blob/master/src/glu/sgi/libutil/project.c

In Topic: Drawing Multiple Instanced Meshes

27 July 2016 - 05:17 AM

Turns out I was already 90% there towards using multi draw indirect. Just had to stick the parameters into a draw indirect buffer.

        def commands(mesh):
            mesh_count = len(mesh.meshes)
            for i in range(mesh_count):
                count = mesh.index_count(i)
                first_index = mesh.index_offset(i)
                base_vertex = mesh.vertex_offset(i)
                instance_count = self.instance_count//mesh_count
                base_instance = i*instance_count
                yield count, instance_count, first_index, base_vertex, base_instance
        
        self.command_data = np.array(list(commands(self.mesh)), dtype=[
            ("count", np.uint32, 1),
            ("instanceCount", np.uint32, 1),
            ("firstIndex", np.uint32, 1),
            ("baseVertex", np.uint32, 1),
            ("baseInstance", np.uint32, 1),
        ])

It does wonders for performance, especially with the Python bindings, since it eliminates almost all of the interpreter/wrapper overhead. Nice.

In Topic: Drawing Multiple Instanced Meshes

26 July 2016 - 01:42 AM

Just a quick update on the reducing draw calls issue: I still don't see a way of doing it without one of the glDraw*Indirect variants. However, that would require pretty recent extensions, some of which are not in core GL yet:

https://www.opengl.org/registry/specs/ARB/multi_draw_indirect.txt

https://www.opengl.org/registry/specs/ARB/shader_draw_parameters.txt

I think for now I might just have to live with the extra draw calls.

In Topic: Drawing Multiple Instanced Meshes

25 July 2016 - 03:57 AM

I have implemented the glDrawElementsInstancedBaseVertexBaseInstance version:
 
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import os
import sys

import ctypes

import numpy as np

from PyQt5 import QtCore, QtGui, QtOpenGL, QtWidgets

import OpenGL
from OpenGL import GL
from OpenGL.GL import shaders

# Helper Functions (Omitted unused types for brevity...)

INPUT_FUNCTIONS = {
    (GL.GL_FLOAT, (1,)): GL.glVertexAttribPointer,
    (GL.GL_FLOAT, (2,)): GL.glVertexAttribPointer,
    (GL.GL_FLOAT, (3,)): GL.glVertexAttribPointer,
    (GL.GL_FLOAT, (4,)): GL.glVertexAttribPointer,
}

UNIFORM_FUNCTIONS = {
    (GL.GL_FLOAT, ()): GL.glUniform1f,
    (GL.GL_FLOAT, (1,)): GL.glUniform1fv,
    (GL.GL_FLOAT, (2,)): GL.glUniform2fv,
    (GL.GL_FLOAT, (3,)): GL.glUniform3fv,
    (GL.GL_FLOAT, (4,)): GL.glUniform4fv,
    (GL.GL_FLOAT, (2, 2)): GL.glUniformMatrix2fv,
    (GL.GL_FLOAT, (3, 3)): GL.glUniformMatrix3fv,
    (GL.GL_FLOAT, (4, 4)): GL.glUniformMatrix4fv,
}

def input_setter(program, key, type):
    location = GL.glGetAttribLocation(program, key)
    function = INPUT_FUNCTIONS[type]
    gltype, shape = type
    def _input_setter(type, value, stride, offset=0, divisor=0):
        GL.glEnableVertexAttribArray(location)
        GL.glBindBuffer(type, value)
        function(location, shape[0], gltype, GL.GL_FALSE, stride, ctypes.c_void_p(int(offset)))
        GL.glVertexAttribDivisor(location, divisor)
        GL.glBindBuffer(type, 0)
    return _input_setter

def uniform_setter(program, key, type):
    location = GL.glGetUniformLocation(program, key)
    function = UNIFORM_FUNCTIONS[type]
    gltype, shape = type
    return {
        0: lambda value, count=1, transpose=False: function(location, value),
        1: lambda value, count=1, transpose=False: function(location, count, value),
        2: lambda value, count=1, transpose=False: function(location, count, transpose, value)
    }[len(shape)]


VS_SOURCE = """
#version 420
layout(location=0) in vec4 position;
layout(location=1) in vec4 color;

layout(location=2) in vec4 instance_position;
layout(location=3) in vec4 instance_color;

out VertexData {
    vec4 position;
    vec4 color;
} vs;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
   vs.position = model * (position * vec4(vec3(0.1), 1.0) + instance_position);
   vs.color = color * instance_color;
   
   gl_Position = projection * view * vs.position;
}
"""


FS_SOURCE = """
#version 420
in VertexData {
    vec4 position;
    vec4 color;
} vs;

layout(location=0) out vec4 frag_color;
void main()
{
   frag_color = vs.color; //vec4(1.0f, 1.0f, 1.0f, 1.0f);
}
"""


def vec(v, n=4, dtype=np.float32):
    result = np.zeros(n, dtype=dtype)
    result[:min(n,len(v))] = v
    return result


def dir(v, n=4, dtype=np.float32):
    result = vec(v, n=n, dtype=dtype)
    result[-1] = 0.0
    return result


def pos(v, n=4, dtype=np.float32):
    result = vec(v, n=n, dtype=dtype)
    result[-1] = 1.0
    return result


def color_from_ordinal(i):
    return np.array([(i >> 0) & 1, (i >> 1) & 1, (i >> 2) & 1, 1], dtype=np.float32)


class Mesh(object):
    def __init__(self, vertices, indices):
        self.vertices = vertices
        self.indices = indices

class MultiMesh(object):
    @property
    def vertices(self):
        return np.concatenate([mesh.vertices for mesh in self.meshes])
    @property
    def indices(self):
        return np.concatenate([mesh.indices for mesh in self.meshes])
    def __init__(self, meshes):
        self.meshes = meshes
    def vertex_count(self, i):
        return len(self.meshes[i].vertices)
    def vertex_offset(self, i):
        return sum(self.vertex_count(j) for j in range(i))
    def index_count(self, i):
        return len(self.meshes[i].indices)
    def index_offset(self, i):
        return sum(self.index_count(j) for j in range(i))

def make_triangle():
    vertex_count = 3
    vertices = np.zeros(vertex_count, dtype=[("position", np.float32, 4),("color", np.float32, 4)])
    vertices["position"] = [pos(v) for v in [[0.0, +1.0, 0.0], [-1.0, -1.0, 0.0], [+1.0, -1.0, 0.0]]]
    vertices["color"] = [[1.0, 1.0, 1.0, 1.0]] * vertex_count
    indices = np.arange(vertex_count, dtype=np.uint8)
    return Mesh(vertices, indices)


def make_rectangle():
    vertex_count = 4
    vertices = np.zeros(vertex_count, dtype=[("position", np.float32, 4),("color", np.float32, 4)])
    vertices["position"] = [pos(v) for v in [[-1.0, -1.0, 0.0], [-1.0, +1.0, 0.0], [+1.0, -1.0, 0.0], [+1.0, +1.0, 0.0]]]
    vertices["color"] = [[1.0, 1.0, 1.0, 1.0]] * vertex_count
    indices = np.arange(vertex_count, dtype=np.uint8)
    return Mesh(vertices, indices)


class MainWindow(QtGui.QOpenGLWindow):
    def initializeGL(self):
        self.createProgram()
        
        self.instance_count = 100
        instance_data = np.zeros(self.instance_count, dtype=[("instance_position", np.float32, 4), ("instance_color", np.float32, 4),])
        instance_data["instance_position"] = [dir(v) for v in 2.0 * np.random.rand(self.instance_count, 4) - 1.0]
        instance_data["instance_color"] = [color_from_ordinal(i) for i in np.random.randint(1, 8, size=self.instance_count)] 
        
        self.mesh = MultiMesh([make_triangle(), make_rectangle()])
        
        self.createVAO(instance_data, self.mesh.vertices, self.mesh.indices)
    
    def createProgram(self):
        self.program = shaders.compileProgram(
            shaders.compileShader(VS_SOURCE, GL.GL_VERTEX_SHADER),
            shaders.compileShader(FS_SOURCE, GL.GL_FRAGMENT_SHADER),
        )
        
        self.inputs = {key: input_setter(self.program, key, type) for key, type in {
            "instance_position": (GL.GL_FLOAT, (4,)),
            "instance_color": (GL.GL_FLOAT, (4,)),
            "position": (GL.GL_FLOAT, (4,)),
            "color": (GL.GL_FLOAT, (4,)),
        }.items()}
        
        self.uniforms =  {key: uniform_setter(self.program, key, type) for key, type in {
            "model": (GL.GL_FLOAT, (4, 4)),
            "view": (GL.GL_FLOAT, (4, 4)),
            "projection": (GL.GL_FLOAT, (4, 4)),
        }.items()}
    
    def createVAO(self, instance_data, vertex_data, index_data):
        self.vao = GL.glGenVertexArrays(1)
        GL.glBindVertexArray(self.vao)
        
        # Create Buffers
        self.instance_vbo = GL.glGenBuffers(1)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.instance_vbo)
        GL.glBufferData(GL.GL_ARRAY_BUFFER, instance_data.nbytes, instance_data, GL.GL_STATIC_DRAW)
        
        self.vbo = GL.glGenBuffers(1)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vbo)
        GL.glBufferData(GL.GL_ARRAY_BUFFER, vertex_data.nbytes, vertex_data, GL.GL_STATIC_DRAW)
        
        self.ibo = GL.glGenBuffers(1)
        GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, self.ibo)
        GL.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, index_data.nbytes, index_data, GL.GL_STATIC_DRAW)
        
        self.inputs["instance_position"](GL.GL_ARRAY_BUFFER, self.instance_vbo, instance_data.itemsize, 0 * np.dtype(np.float32).itemsize, divisor=1)
        self.inputs["instance_color"](GL.GL_ARRAY_BUFFER, self.instance_vbo, instance_data.itemsize, 4 * np.dtype(np.float32).itemsize, divisor=1)
        
        self.inputs["position"](GL.GL_ARRAY_BUFFER, self.vbo, vertex_data.itemsize, 0 * np.dtype(np.float32).itemsize)
        self.inputs["color"](GL.GL_ARRAY_BUFFER, self.vbo, vertex_data.itemsize, 4 * np.dtype(np.float32).itemsize)
        
        GL.glBindVertexArray(0)
    
    def resizeGL(self, width, height):
        self.size = np.array([width, height], dtype=np.float32)
        self.aspect = self.size/np.min(self.size)

    def paintGL(self):
        GL.glClearColor(0.5, 0.5, 0.5, 1.0)
        GL.glClear(GL.GL_COLOR_BUFFER_BIT)
        
        try:
            GL.glUseProgram(self.program)
            
            self.uniforms["model"](np.identity(4, dtype=np.float32))
            self.uniforms["view"](np.identity(4, dtype=np.float32))
            self.uniforms["projection"](np.diag(np.r_[1.0/self.aspect,1,1]))
            
            GL.glBindVertexArray(self.vao)
            
            mesh_count = len(self.mesh.meshes)
            for i in range(mesh_count):
                index_count = self.mesh.index_count(i)
                index_offset = ctypes.c_void_p(self.mesh.index_offset(i) * np.dtype(np.uint8).itemsize)
                base_vertex = self.mesh.vertex_offset(i)
                instance_count = self.instance_count//mesh_count
                base_instance = i*instance_count
                GL.glDrawElementsInstancedBaseVertexBaseInstance(GL.GL_TRIANGLE_STRIP, index_count, GL.GL_UNSIGNED_BYTE, index_offset, instance_count, base_vertex, base_instance)
            
        finally:
            GL.glBindVertexArray(0)
            GL.glUseProgram(0)
    
    def keyReleaseEvent(self, event):
        if event.key() == QtCore.Qt.Key_Escape:
            QtWidgets.QApplication.instance().quit()


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    
    format = QtGui.QSurfaceFormat()
    format.setDepthBufferSize(24)
    format.setStencilBufferSize(8)
    format.setProfile(QtGui.QSurfaceFormat.CoreProfile)
    format.setVersion(4, 2)
    
    window = MainWindow()
    window.setFormat(format)
    window.resize(640, 480)
    window.show()
    
    sys.exit(app.exec_())

Apologies that the code is a bit verbose (OpenGL... :-/), but it is completely self-contained. Just needs PyQT5, PyOpenGL and numpy. The key section is the paint method:
 
    def paintGL(self):
        GL.glClearColor(0.5, 0.5, 0.5, 1.0)
        GL.glClear(GL.GL_COLOR_BUFFER_BIT)
        
        try:
            GL.glUseProgram(self.program)
            
            self.uniforms["model"](np.identity(4, dtype=np.float32))
            self.uniforms["view"](np.identity(4, dtype=np.float32))
            self.uniforms["projection"](np.diag(np.r_[1.0/self.aspect,1,1]))
            
            GL.glBindVertexArray(self.vao)
            
            mesh_count = len(self.mesh.meshes)
            for i in range(mesh_count):
                index_count = self.mesh.index_count(i)
                index_offset = ctypes.c_void_p(self.mesh.index_offset(i) * np.dtype(np.uint8).itemsize)
                base_vertex = self.mesh.vertex_offset(i)
                instance_count = self.instance_count//mesh_count
                base_instance = i*instance_count
                GL.glDrawElementsInstancedBaseVertexBaseInstance(GL.GL_TRIANGLE_STRIP, index_count, GL.GL_UNSIGNED_BYTE, index_offset, instance_count, base_vertex, base_instance)
            
        finally:
            GL.glBindVertexArray(0)
            GL.glUseProgram(0)

Consequently, this version has one draw call per mesh. Still scratching my head over how to do it with a single draw call.

In Topic: Drawing Multiple Instanced Meshes

24 July 2016 - 06:44 AM

Yes, having a single draw call was my initial goal, but I haven't quite figured out an elegant way to achieve it.

You can generally do this using a combination of "draw id" and "instance id" (I forget their GL names). You might also need an extra indirection buffer to map a particular mesh/instance to an offset within the attribute buffer if the mesh attributes are not of uniform width.

Fewer draw calls means that the hardware can parallelize better, and gives the driver fewer chances or need to do behind-your-back state flushing or other expensive calculations.


Could you elaborate more or perhaps point me to some material on the topic? I can kind of see it with shader storage buffer objects and somehow indexing into them using instance attributes, but I am not sure if that is what you mean.

PARTNERS