OpenGL plugin on Windows

Related: Python OpenGL compatibility test

I have been trying to get OpenGL working for awhile by using the code from the link above. None of the desktop versions work: 2.0 and 4.1 Core crash and 2.1 shows an empty frame. For some reason, ES2 isn’t available as a profile. It’s not listed under D:\Program Files\Krita (x64)\lib\site-packages\PyQt5. ES2 is available in the PyQt5 package. I got around context.versionFunctions(profile) returning None by importing the profiles directly from PyQt5._QOpenGLFunctions_2_1 import QOpenGLFunctions_2_1.

Any suggestions? I think the next thing to try is to somehow get the ES2 profile into Krita’s Python libs. Technically, it doesn’t have to be OpenGL; I mainly need a way to interactively update frames pixel by pixel similar to the shaders in the code linked above.

System Info
OpenGL Info
 
  Vendor:  "Google Inc. (NVIDIA)" 
  Renderer:  "ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11-31.0.15.4633)" 
  Driver version:  "OpenGL ES 3.0.0 (ANGLE 2.1.0 git hash: f2280c0c5f93+krita_qt5)" 
  Shading language:  "OpenGL ES GLSL ES 3.00 (ANGLE 2.1.0 git hash: f2280c0c5f93+krita_qt5)" 
  Requested format:  QSurfaceFormat(version 3.0, options QFlags<QSurfaceFormat::FormatOption>(), depthBufferSize 24, redBufferSize 8, greenBufferSize 8, blueBufferSize 8, alphaBufferSize 8, stencilBufferSize 8, samples -1, swapBehavior QSurfaceFormat::DoubleBuffer, swapInterval 0, colorSpace QSurfaceFormat::DefaultColorSpace, profile  QSurfaceFormat::NoProfile) 
  Current format:  QSurfaceFormat(version 3.0, options QFlags<QSurfaceFormat::FormatOption>(), depthBufferSize 24, redBufferSize 8, greenBufferSize 8, blueBufferSize 8, alphaBufferSize 8, stencilBufferSize 8, samples 0, swapBehavior QSurfaceFormat::DefaultSwapBehavior, swapInterval 0, colorSpace QSurfaceFormat::DefaultColorSpace, profile  QSurfaceFormat::NoProfile) 
  GL version: 3.0 
  Supports deprecated functions false 
  Is OpenGL ES: true 
  supportsBufferMapping: true 
  supportsBufferInvalidation: false 
  forceDisableTextureBuffers: true 
  Extensions: 
    ...

QPA OpenGL Detection Info 
  supportsDesktopGL: true 
  supportsAngleD3D11: true 
  isQtPreferAngle: true 
  Detected renderers: 
    (Supported) ANGLE (Microsoft, Microsoft Basic Render Driver Direct3D11 vs_5_0 ps_5_0, D3D11-10.0.22621.3527) (OpenGL ES 3.0.0 (ANGLE 2.1.0 git hash: f2280c0c5f93+krita_qt5)) 
    (Supported) NVIDIA GeForce GTX 1660 SUPER/PCIe/SSE2 (4.6.0 NVIDIA 546.33) 
    (Supported) ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11-31.0.15.4633) (OpenGL ES 3.0.0 (ANGLE 2.1.0 git hash: f2280c0c5f93+krita_qt5))  
1 Like

Check that Current Renderer is OpenGL

Ps: Qt:s OpenGL classes are confusing, most of time it’s better to use
PyOpenGL, just extract whl to pykrita/my-site-packages and make sure that path is included to sys.path

import sys

# before importing OpenGL make sure that package is available
my_site_packages = r'something something \pykrita\my-site-packages'
if my_site_packages not in sys.path:
    sys.path.append(my_site_packages)

# PyOpenGL should be available
from OpenGL import GL

/AkiR

2 Likes

Thanks @AkiR! I was able to get the code working without modification after switching the renderer from ANGLE to OpenGL.

I’m still worried about cross platform capability, but I think I should handle that on a case by case basis.

Have you gotten OpenGL to work using ANGLE?

I’m proud to say that after much trial and error I finally got OpenGL code that works for both the ANGLE and OpenGL renderers.

It does not require any external packages like PyOpenGL. In fact, PyOpenGL doesn’t even work with ANGLE, because PyOpenGL targets desktop OpenGL.

Here’s what didn’t work. Pretty much all the documentation says to use .functions or .extraFunctions or .versionFunctions of QOpenGLContext. For some reason, .functions and .extraFunctions aren’t available in PyQt, and .versionFunctions only works for OpenGL versions 3.2 and up. Importing PyQt5._QOpenGLFunctions_2_0, PyQt5._QOpenGLFunctions_2_1, and PyQt5._QOpenGLFunctions_4_1_Core also do not work.

The key insight is using QOpenGLContext.getProcAddress. I created a small wrapper function to make it easier to get and use the OpenGL functions. I referenced QOpenGLFunctions and QOpenGLExtraFunctions to build the CFUNCTYPE. For enums and constants, I referenced WebGL constants - Web APIs | MDN.

_funcTypes = {
    'glDrawArrays': CFUNCTYPE(None, c_int, c_int, c_int),
    'glViewport': CFUNCTYPE(None, c_int, c_int, c_int, c_int),
}

def getGLFunc(context, name):
    sipPointer = context.getProcAddress(name.encode())
    funcType = _funcTypes.get(name)
    return funcType(int(sipPointer))

Let me know if this works or not on your platform. :slight_smile:

Full Code
from ctypes import CFUNCTYPE, c_int
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QOpenGLShader, QOpenGLShaderProgram
from PyQt5.QtWidgets import QOpenGLWidget

_vertex_code = """\
void main(void) {
    vec2 pos;
    pos = 2.0 * vec2(gl_VertexID % 2, gl_VertexID / 2) - 1.0;
    gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);
}"""

# https://www.shadertoy.com/view/4dsGzH
_fragment_code = """\
uniform float iTime;
out vec4 out_color;

vec3 COLOR1 = vec3(0.0, 0.0, 0.3);
vec3 COLOR2 = vec3(0.5, 0.0, 0.0);
float BLOCK_WIDTH = 0.01;

void main(void) {
    vec2 uv = gl_FragCoord.xy / vec2(800.0, 600.0);

    vec3 final_color = vec3(1.0);
    vec3 bg_color = vec3(0.0);
    vec3 wave_color = vec3(0.0);

    float c1 = mod(uv.x, 2.0 * BLOCK_WIDTH);
    c1 = step(BLOCK_WIDTH, c1);
    float c2 = mod(uv.y, 2.0 * BLOCK_WIDTH);
    c2 = step(BLOCK_WIDTH, c2);

    bg_color = mix(uv.x * COLOR1, uv.y * COLOR2, c1 * c2);

    float wave_width = 0.01;
    uv  = -1.0 + 2.0 * uv;
    uv.y += 0.1;
    for(float i = 0.0; i < 10.0; i++) {
        uv.y += (0.07 * sin(uv.x + i/7.0 + iTime));
        wave_width = abs(1.0 / (150.0 * uv.y));
        wave_color += vec3(wave_width * 1.9, wave_width, wave_width * 1.5);
    }
    final_color = bg_color + wave_color;
    out_color = vec4(final_color, 1.0);
}"""

_funcTypes = {
    'glDrawArrays': CFUNCTYPE(None, c_int, c_int, c_int),
    'glViewport': CFUNCTYPE(None, c_int, c_int, c_int, c_int),
}

def getGLFunc(context, name):
    sipPointer = context.getProcAddress(name.encode())
    funcType = _funcTypes.get(name)
    return funcType(int(sipPointer))

def getVersionHeader(context):
    version = context.format().version()
    if context.isOpenGLES():
        profile = 'es'
        precision = 'precision highp float;\n'
    else:
        profile = 'core'
        precision = ''
    versionHeader = f'#version {version[0]}{version[1]}0 {profile}\n{precision}'
    return versionHeader

class MyViewport(QOpenGLWidget):
    def initializeGL(self):
        self._iTime = 0.0

        self.glDrawArrays = getGLFunc(self.context(), 'glDrawArrays')
        self.glViewport = getGLFunc(self.context(), 'glViewport')
        self.GL_TRIANGLE_STRIP = 0x0005

        # shader_program
        versionHeader = getVersionHeader(self.context())
        vert = QOpenGLShader(QOpenGLShader.Vertex)
        vert.compileSourceCode(versionHeader + _vertex_code)
        frag = QOpenGLShader(QOpenGLShader.Fragment)
        frag.compileSourceCode(versionHeader + _fragment_code)
        self._prog = QOpenGLShaderProgram()
        self._prog.addShader(vert)
        self._prog.addShader(frag)
        self._prog.link()
        self._timer = QTimer(self)
        self._timer.timeout.connect(self.update)
        self._timer.start(int(1000.0 / 60.0))

    def paintGL(self):
        self._iTime += 0.01
        self._prog.bind()
        self._prog.setUniformValue("iTime", self._iTime)
        self.glDrawArrays(self.GL_TRIANGLE_STRIP, 0, 4)

    def resizeGL(self, width, height):
        self.glViewport(0, 0, width, height)

view = MyViewport()
view.show()
3 Likes

This topic was automatically closed 4 days after the last reply. New replies are no longer allowed.