Related
I am trying to figure out how to use OpenGL with QtQuickPaintedItem. According to the documentation, this seems to be possible, and there are several "helper" classes for that.
I tried this code below, and I also tried to create QOpenGLPaintDevice, QOpenGLContext, and QOpenGLFramebufferObject and pass their instances to their functions in many combinations, but it did not work either.
/// Simple3d.h
#ifndef SIMPLE3D_H
#define SIMPLE3D_H
#include<QtQuick/QQuickPaintedItem>
#include<QOpenGLFunctions>
// Not sure if I need to use any of these classes.
//#include<QOpenGLPaintDevice>
//#include<QOpenGLContext>
//#include<QOpenGLFramebufferObject>
//#include<QOpenGLShaderProgram>
//#include <GL/gl.h>
/** #brief inherets from QtQuickItem and it draws
* triangle.
*
*/
class Simple3d : public QQuickPaintedItem
{
Q_OBJECT
public:
/** Call this function to be able to instantiate this
* item in a QML file.
*/
static void register_qml_item();
Simple3d();
void paint(QPainter * painter) override;
virtual ~Simple3d() override;
private:
bool mDebug;
/** OpenGL stuff */
bool mOpenGlInitialized;
GLuint mProgramId;
static const GLuint mVertexArrayID = 0;
QOpenGLFunctions *mGlFuncs;
// Not sure when is a good time to update these:
GLsizei mWidth, mHeight;
GLuint load_shader(GLenum type, const char *shaderSrc);
void verify_initialized();
void draw_open_gl();
};
#endif
/// Simple3d.cpp
#include "simple3d.h"
#include <QSGGeometryNode>
#include <QSGVertexColorMaterial>
#include <QtMath>
#include<QOpenGLFunctions>
#include<QPainter>
#include<QGuiApplication>
using namespace std;
Simple3d::Simple3d()
{
mDebug = true;
mOpenGlInitialized = false;
mGlFuncs = nullptr;
setFlag(ItemHasContents, true);
}
void Simple3d::paint(QPainter *painter)
{
if (mDebug) qDebug("Simple3d is running paint()");
if (!mGlFuncs){
mGlFuncs = new QOpenGLFunctions();
mGlFuncs->initializeOpenGLFunctions();
}
painter->beginNativePainting();
verify_initialized();
draw_open_gl();
painter->endNativePainting();
if (mDebug) qDebug("Simple3d finished rendering.");
}
GLuint Simple3d::load_shader(GLenum type, const char *shaderSrc)
{
GLuint shader;
GLint compiled;
// Create the shader object
shader = mGlFuncs->glCreateShader(type);
if(shader == 0)
return 0;
// Load the shader source
mGlFuncs->glShaderSource(shader, 1, &shaderSrc, nullptr);
// Compile the shader
mGlFuncs->glCompileShader(shader);
// Check the compile status
mGlFuncs->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if(!compiled)
{
GLint infoLen = 0;
mGlFuncs->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if(infoLen > 1)
{
char* infoLog = new char[size_t(infoLen)+1];
mGlFuncs->glGetShaderInfoLog(shader, infoLen, nullptr, infoLog);
qDebug("In the shader source:\n%s", shaderSrc);
qDebug("Error compiling shader:\n%s\n", infoLog);
delete [] infoLog;
}
mGlFuncs->glDeleteShader(shader);
return 0;
}
return shader;
}
void Simple3d::verify_initialized()
{
if (mOpenGlInitialized) return;
// This piece is partially taken from:
// https://www.khronos.org/assets/uploads/books/openglr_es_20_programming_guide_sample.pdf
mWidth = 50;
mHeight = 50;
const char vShaderStr[] =
"attribute vec4 vPosition;\n"
"void main()\n"
"{\n"
" gl_Position = vPosition;\n"
"}\n";
const char fShaderStr[] =
"//precision mediump float;\n"
"void main()\n"
"{\n"
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n"
"}\n";
GLuint vertexShader;
GLuint fragmentShader;
GLint linked;
// Load the vertex/fragment shaders
vertexShader = load_shader(GL_VERTEX_SHADER, vShaderStr);
fragmentShader = load_shader(GL_FRAGMENT_SHADER, fShaderStr);
// Create the program object
mProgramId = mGlFuncs->glCreateProgram();
if(mProgramId == 0) {
qDebug("Failed to create the OpenGL program.");
return;
}
mGlFuncs->glAttachShader(mProgramId, vertexShader);
mGlFuncs->glAttachShader(mProgramId, fragmentShader);
// Bind vPosition to attribute 0
mGlFuncs->glBindAttribLocation(mProgramId, mVertexArrayID, "vPosition");
// Link the program
mGlFuncs->glLinkProgram(mProgramId);
// Check the link status
mGlFuncs->glGetProgramiv(mProgramId, GL_LINK_STATUS, &linked);
if(!linked)
{
qDebug("Failed to link OpenGL program.");
GLint infoLen = 0;
mGlFuncs->glGetProgramiv(mProgramId, GL_INFO_LOG_LENGTH, &infoLen);
if(infoLen > 1)
{
char* infoLog = new char[size_t(infoLen)];
mGlFuncs->glGetProgramInfoLog(mProgramId, infoLen, nullptr, infoLog);
qDebug("Error linking program:\n%s\n", infoLog);
delete [] infoLog;
}
mGlFuncs->glDeleteProgram(mProgramId);
return;
}
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
mOpenGlInitialized = true;
qDebug("OpenGL was initialized successfully.");
return;
}
void Simple3d::draw_open_gl()
{
if (!mOpenGlInitialized){
QGuiApplication::exit(1);
return;
}
mGlFuncs->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
mGlFuncs->glEnable(GL_DEPTH_TEST);
mGlFuncs->glUseProgram(mProgramId);
GLfloat vVertices[] =
{0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f};
// Set the viewport
mGlFuncs->glViewport(0, 0, mWidth, mHeight);
// Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
// Load the vertex data
mGlFuncs->glVertexAttribPointer(mVertexArrayID, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
mGlFuncs->glEnableVertexAttribArray(0);
mGlFuncs->glDrawArrays(GL_TRIANGLES, 0, 3);
mGlFuncs->glFlush();
mGlFuncs->glFinish();
mGlFuncs->glUseProgram(0); // release the program.
}
Simple3d::~Simple3d()
{
}
void Simple3d::register_qml_item()
{
qmlRegisterType<Simple3d>("CustomGraphicsItems", 1, 0, "Simple3d");
}
/// main.cpp
#include "simple3d.h"
#include <stdexcept>
using namespace std;
int main(int argc, char *argv[]){
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
Simple3d::register_qml_item();
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/example_Simple3d.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl)
{
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
int num_root_objects = engine.rootObjects().size();
qDebug("The number of root objects: %i", num_root_objects);
if (engine.rootObjects().size()!=1){
throw logic_error("Expected exactly one root object"
"in the qml file");
}
QObject * root = engine.rootObjects()[0];
Simple3d * draw_3d_item = root->findChild<Simple3d*>("draw_3d_item");
if (!draw_3d_item){
throw logic_error("Did not find item 'draw_3d_item'.");
}
int result = app.exec();
qDebug("The app is exiting.");
return result;
}
/// example_Simple3d.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import CustomGraphicsItems 1.0
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
Window {
visible: true
Simple3d{
id: draw_3d_item
width: 50
height: 50
anchors.fill: parent
objectName: "draw_3d_item"
}
}
The code should display a red triangle on black background, but nothing is being drawn.
This question is similar to this: Rendering custom opengl in qt5's qtquick 2.0
I liked the answer by Gunnar Sletta. The trick is to use QQuickFramebufferObject, not QtQuickPaintedItem.
The first lines of QQuickFramebufferObject reference page say that it is an auxiliary class, but in fact it is the key class if you want to create a custom OpenGL within QtQuick framework. They should have called it
QQuickOpenGLItem.
Just read this is the example of using QQuickFramebufferObject:
https://doc.qt.io/qt-5/qtquick-scenegraph-textureinsgnode-example.html (Click on link "Example project # code.qt.io".)
I have been struggling to correctly render any geometry using OpenGLES 1.0, 2.0, or 3.0 techniques. My testing device is a Samsung Galaxy S7 Edge (running Android 7.0). I have implemented numerous guides for both OpenGLES and EGL such as:
https://www.khronos.org/registry/EGL/sdk/docs/man/html/
https://www.khronos.org/assets/uploads/books/openglr_es_20_programming_guide_sample.pdf
http://www.ikerhurtado.com/egl-use-android-native-opengles-applications
The code was initially adapted from the "native-activity" example:
https://github.com/googlesamples/android-ndk/tree/master/native-activity
Android Manifest: (mostly from native-activity example)
...
<!-- Tell the system this app requires OpenGL ES 3.0. -->
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
...
CMAKE: (mostly from native-activity example)
...
add_library(
native-activity
SHARED
main.cpp)
target_include_directories(
native-activity
PRIVATE
${ANDROID_NDK}/sources/android/native_app_glue)
# add lib dependencies
target_link_libraries(
native-activity
android
native_app_glue
EGL
GLESv3
log)
...
main.cpp: (mostly from native-activity example, cropped for relevance)
...
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#include <android_native_app_glue.h>
...
/*
*Test GLES 1.0, should draw a white triangle on red background
*/
class TestGLES10 {
public:
GLuint program;
void create() {
char vssource[] =
"attribute vec4 vertexPosition; \n"
"void main(){ \n"
" gl_Position = vertexPosition; \n"
"} \n";
char fssource[] =
"precision mediump float; \n"
"void main(){ \n"
" gl_FragColor = vec4(1.0); "
"} \n";
auto compile = [](const char *source, GLenum type){
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
return shader;
};
GLuint vs = compile(string(vssource).c_str(), GL_VERTEX_SHADER);
GLuint fs = compile(string(fssource).c_str(), GL_FRAGMENT_SHADER);
program = glCreateProgram();
glAttachShader(program, vs);
glAttachShader(program, fs);
glBindAttribLocation(program, 0, "vertexPosition");
glLinkProgram(program);
glDisable(GL_DEPTH_TEST);
}
void update() {
glClearColor(1,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
float positions[]{
0.0f, 0.1f,
-0.1f, -0.1f,
0.1f, -0.1f
};
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, positions);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
};
/*
*Test GLES 3.0, should draw a white triangle on blue background
*/
class TestGLES30 {
public:
GLuint vbo, vao, program;
void create() {
char vssource[] =
"#version 300 es\n"
"layout(location=0) in vec4 vertexPosition; \n"
"void main(){ \n"
" gl_Position = vertexPosition; \n"
"} \n";
char fssource[] =
"#version 300 es\n"
"precision mediump float; \n"
"out vec4 fragment; \n"
"void main(){ \n"
" fragment = vec4(1.0); "
"} \n";
auto compile = [](const char *source, GLenum type){
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
return shader;
};
GLuint vs = compile(string(vssource).c_str(), GL_VERTEX_SHADER);
GLuint fs = compile(string(fssource).c_str(), GL_FRAGMENT_SHADER);
program = glCreateProgram();
glAttachShader(program, vs);
glAttachShader(program, fs);
glLinkProgram(program);
float positions[]{
0.0f, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f
};
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, 2*4, positions, GL_STATIC_DRAW);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*4, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void update() {
glClearColor(0,0,1,1);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}
};
...
void android_main(android_app* appInterface) {
appInterface->onAppCmd = commandRelay;
appInterface->onInputEvent = inputRelay;
while(appInterface->window == nullptr){
pollEvents(appInterface);
}
EGLDisplay display;
EGLSurface surface;
EGLContext context;
EGLint majorVersion, minorVersion;
EGLConfig config;
EGLint width, height;
EGLint nativeVisualID;
EGLint configCount;
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
assert(display != EGL_NO_DISPLAY);
eglInitialize(display, &majorVersion, &minorVersion);
//https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglChooseConfig.xhtml
const EGLint displayAttrib[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,//egl 1.3 req
EGL_NATIVE_RENDERABLE, EGL_TRUE,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_NONE
};
//retreive configs
eglChooseConfig(display, displayAttrib, 0, 0, &configCount);
std::unique_ptr<EGLConfig[]> supported(new EGLConfig[configCount]);
eglChooseConfig(display, displayAttrib, supported.get(), configCount, &configCount);
EGLint i = 0, v;
for (; i < configCount; i++) {
auto get = [&](EGLint attrib){
eglGetConfigAttrib(display, supported[i], attrib, &v);
return v;
};
if (Math2::equal(8,
get(EGL_RED_SIZE),
get(EGL_GREEN_SIZE),
get(EGL_BLUE_SIZE),
get(EGL_DEPTH_SIZE))) {//expands to 8 == get(EGL_RED_SIZE) == ...
config = supported[i];
break;
}
}
if (i == configCount) {//default to first
config = supported[0];
}
const EGLint surfaceAttrib[] = {
EGL_RENDER_BUFFER, EGL_BACK_BUFFER,// render to the back buffer, egl 1.2 req
EGL_NONE
};
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &nativeVisualID);
surface = eglCreateWindowSurface(display, config, appInterface->window, surfaceAttrib);
const EGLint contextAttrib[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,// request a context using Open GL ES 2.0
EGL_NONE
};
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttrib);
assert(context != EGL_NO_CONTEXT);
//sets initial viewport and scissor dimensions to draw surface size
eglMakeCurrent(display, surface, surface, context);
eglQuerySurface(display, surface, EGL_WIDTH, &width);
eglQuerySurface(display, surface, EGL_HEIGHT, &height);
//this call was mentioned, but missing from the example (removing has same effect)
ANativeWindow_setBuffersGeometry(appInterface->window, width, height, nativeVisualID);
TestGLES10 test;
test.create();
while (true){
test.update();
eglSwapBuffers(display, surface);
}
}
Results:
Logged Info:
Vendor : Qualcomm
Renderer : Adreno (TM) 530
Version : OpenGL ES 3.2 V#145.0 (GIT#I86b60582e4)
EGL version : 1.4
Window dimensions : 1080, 1920
Additionally, the device has resolution scaling set to 1080x1920.
Test 1.0: https://i.imgur.com/dmofGYw.png
Test 3.0: https://i.imgur.com/uoghOZc.png
According to:
https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglMakeCurrent.xhtml
the glViewPort should have been initialized to the surface size (1080x1920), so I am at a loss for how the white rectangle could occur.
The shader code for both tests compile without errors from Adreno.
Why wont it render geometry correctly?
According to :
https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglCreateContext.xhtml
there is no way to specifically request a 3.0 or higher context, why does the created context have version 3.2 when I can only request 2.0?
Why wont it render geometry correctly?
Your position arrays are three vertices of two compoenents each (x, y), but you're telling GL they have three components each:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, positions);
why does the created context have version 3.2 when I can only request 2.0?
Versions 3.x are entirely forward compatible with code written for version 2.0, so it's legal for the drivers just to up-convert a context to the newest version they support.
Using GLFW, the C++ OpenGL ES code below calculates the square roots of five numbers and outputs them on the command line. The code makes use of Transform Feedback. When I compile on Ubuntu 17.10, using the following command, I get the result I expect:
$ g++ tf.cpp -lGL -lglfw
If I use Emscripten, however, an exception is thrown, which indicates that
glMapBufferRange is only supported when access is MAP_WRITE|INVALIDATE_BUFFER. I do want to read rather than write, so perhaps I shouldn't use glMapBufferRange, but what can I use instead? I have tried on both Firefox and Chromium. The command I use to compile with Emscripten is:
$ em++ -std=c++11 tf.cpp -s USE_GLFW=3 -s USE_WEBGL2=1 -s FULL_ES3=1 -o a.out.html
The code follows:
#include <iostream>
#define GLFW_INCLUDE_ES3
#include <GLFW/glfw3.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
static const GLuint WIDTH = 800;
static const GLuint HEIGHT = 600;
static const GLchar *vertex_shader_src =
"#version 300 es\n"
"precision mediump float;\n"
"in float inValue;\n"
"out float outValue;\n"
"void main() {\n"
" outValue = sqrt(inValue);\n"
"}\n";
// Emscripten complains if there's no fragment shader
static const GLchar *fragment_shader_src =
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 colour;\n"
"void main() {\n"
" colour = vec4(1.0, 1.0, 0.0, 1.0);\n"
"}\n";
static const GLfloat vertices[] = {
0.0f, 0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
};
GLint get_shader_prog(const char *vert_src, const char *frag_src = "")
{
enum Consts { INFOLOG_LEN = 512 };
GLchar infoLog[INFOLOG_LEN];
GLint fragment_shader, vertex_shader, shader_program, success;
shader_program = glCreateProgram();
auto mk_shader = [&](GLint &shader, const GLchar **str, GLenum shader_type) {
shader = glCreateShader(shader_type);
glShaderSource(shader, 1, str, NULL);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, INFOLOG_LEN, NULL, infoLog);
std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << '\n';
}
glAttachShader(shader_program, shader);
};
mk_shader(vertex_shader, &vert_src, GL_VERTEX_SHADER);
mk_shader(fragment_shader, &frag_src, GL_FRAGMENT_SHADER);
const GLchar* feedbackVaryings[] = { "outValue" };
glTransformFeedbackVaryings(shader_program, 1, feedbackVaryings,
GL_INTERLEAVED_ATTRIBS);
glLinkProgram(shader_program);
glGetProgramiv(shader_program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shader_program, INFOLOG_LEN, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << '\n';
}
glUseProgram(shader_program);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
return shader_program;
}
int main(int argc, char *argv[])
{
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
GLFWwindow *window = glfwCreateWindow(WIDTH, HEIGHT, __FILE__, NULL, NULL);
glfwMakeContextCurrent(window);
GLuint shader_prog = get_shader_prog(vertex_shader_src, fragment_shader_src);
GLint inputAttrib = glGetAttribLocation(shader_prog, "inValue");
glViewport(0, 0, WIDTH, HEIGHT);
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(inputAttrib);
GLuint tbo;
glGenBuffers(1, &tbo);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tbo);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(data),
nullptr, GL_STATIC_READ);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);
glEnable(GL_RASTERIZER_DISCARD);
glBeginTransformFeedback(GL_POINTS);
glDrawArrays(GL_POINTS, 0, 5);
glEndTransformFeedback();
glDisable(GL_RASTERIZER_DISCARD);
glFlush();
GLfloat feedback[5]{1,2,3,4,5};
void *void_buf = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER,0,
sizeof(feedback), GL_MAP_READ_BIT);
GLfloat *buf = static_cast<GLfloat *>(void_buf);
for (int i = 0; i < 5; i++)
feedback[i] = buf[i];
glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
for (int i = 0; i < 5; i++)
std::cout << feedback[i] << ' ';
std::cout << std::endl;
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &tbo);
glDeleteVertexArrays(1, &vao);
glfwTerminate();
return 0;
}
As has been mentioned by another answer, WebGL 2.0 is close to OpenGL ES 3.0, but confusingly does not define glMapBufferRange() function, so that Emscripten tries emulating only part of functionality of this function.
Real WebGL 2.0, however, exposes glGetBufferSubData() analog, which does not exist in OpenGL ES, but exist in desktop OpenGL. The method can be wrapped in code via EM_ASM_:
void myGetBufferSubData (GLenum theTarget, GLintptr theOffset, GLsizeiptr theSize, void* theData)
{
#ifdef __EMSCRIPTEN__
EM_ASM_(
{
Module.ctx.getBufferSubData($0, $1, HEAPU8.subarray($2, $2 + $3));
}, theTarget, theOffset, theData, theSize);
#else
glGetBufferSubData (theTarget, theOffset, theSize, theData);
#endif
}
Fetching VBO data back is troublesome in multi-platform code:
OpenGL 1.5+
glGetBufferSubData(): YES
glMapBufferRange(): YES
OpenGL ES 2.0
glGetBufferSubData(): NO
glMapBufferRange(): NO
OpenGL ES 3.0+
glGetBufferSubData(): NO
glMapBufferRange(): YES
WebGL 1.0
glGetBufferSubData(): NO
glMapBufferRange(): NO
WebGL 2.0
glGetBufferSubData(): YES
glMapBufferRange(): NO
So that:
Desktop OpenGL gives maximum flexibility.
OpenGL ES 2.0 and WebGL 1.0 give no chance to retrieve data back.
OpenGL ES 3.0+ gives only mapping buffer.
WebGL 2.0 gives only getBufferSubData().
WebGL2 does not support MapBufferRange because it would be a security nightmare. Instead it supports getBufferSubData. I have no idea if that is exposed to WebAssembly in emscripten. Emscripten is emulating MapBufferRange for the cases you mentioned by using bufferData. See here and here
If getBufferSubData is not supported you can add it. See the code in library_gl.js for how readPixels is implemented and use that as inspiration for how to expose getBufferSubData to WebAssembly. Either that or add some inline JavaScript with the _EM_ASM. I haven't done it but googling for "getBufferSubData emscripten" bought up this gist
I am writing a openGL program(C++) which draws a ground with two 3D objects above it. The programming tool I use is Xcode version 8.0(8A218a)(OSX 10.11.6).
my code(main.cpp):
#include <GL/glew.h>
#include <GL/freeglut.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
#include <fstream>
using namespace std;
using glm::vec3;
using glm::mat4;
GLint programID;
//initialize all OpenGL objects
GLuint groundVAO, groundVBO, groundEBO; //ground
bool checkStatus( //OK
GLuint objectID,
PFNGLGETSHADERIVPROC objectPropertyGetterFunc,
PFNGLGETSHADERINFOLOGPROC getInfoLogFunc,
GLenum statusType)
{
GLint status;
objectPropertyGetterFunc(objectID, statusType, &status);
if (status != GL_TRUE)
{
GLint infoLogLength;
objectPropertyGetterFunc(objectID, GL_INFO_LOG_LENGTH, &infoLogLength);
GLchar* buffer = new GLchar[infoLogLength];
GLsizei bufferSize;
getInfoLogFunc(objectID, infoLogLength, &bufferSize, buffer);
cout << buffer << endl;
delete[] buffer;
return false;
}
return true;
}
bool checkShaderStatus(GLuint shaderID) //OK
{
return checkStatus(shaderID, glGetShaderiv, glGetShaderInfoLog, GL_COMPILE_STATUS);
}
bool checkProgramStatus(GLuint programID) //OK
{
return checkStatus(programID, glGetProgramiv, glGetProgramInfoLog, GL_LINK_STATUS);
}
string readShaderCode(const char* fileName) //OK
{
ifstream meInput(fileName);
if (!meInput.good())
{
cout << "File failed to load..." << fileName;
exit(1);
}
return std::string(
std::istreambuf_iterator<char>(meInput),
std::istreambuf_iterator<char>()
);
}
void installShaders() //OK
{
GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
const GLchar* adapter[1];
//adapter[0] = vertexShaderCode;
string temp = readShaderCode("VertexShaderCode.glsl");
adapter[0] = temp.c_str();
glShaderSource(vertexShaderID, 1, adapter, 0);
//adapter[0] = fragmentShaderCode;
temp = readShaderCode("FragmentShaderCode.glsl");
adapter[0] = temp.c_str();
glShaderSource(fragmentShaderID, 1, adapter, 0);
glCompileShader(vertexShaderID);
glCompileShader(fragmentShaderID);
if (!checkShaderStatus(vertexShaderID) ||
!checkShaderStatus(fragmentShaderID))
return;
programID = glCreateProgram();
glAttachShader(programID, vertexShaderID);
glAttachShader(programID, fragmentShaderID);
glLinkProgram(programID);
if (!checkProgramStatus(programID))
return;
glDeleteShader(vertexShaderID);
glDeleteShader(fragmentShaderID);
glUseProgram(programID);
}
void keyboard(unsigned char key, int x, int y)
{
//TODO:
}
void sendDataToOpenGL()
{
//TODO:
//create solid objects here and bind to VAO & VBO
//Ground, vertices info
const GLfloat Ground[]
{
-5.0f, +0.0f, -5.0f, //0
+0.498f, +0.898, +0.0f, //grass color
+5.0f, +0.0f, -5.0f, //1
+0.498f, +0.898, +0.0f,
+5.0f, +0.0f, +5.0f, //2
+0.498f, +0.898, +0.0f,
-5.0f, +0.0f, +5.0f
};
GLushort groundIndex[] = {1,2,3, 1,0,3};
//Pass ground to vertexShader
//VAO
glGenVertexArrays(1, &groundVAO);
glBindVertexArray(groundVAO);
//VBO
glGenBuffers(1, &groundVBO);
glBindBuffer(GL_ARRAY_BUFFER, groundVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(Ground), Ground, GL_STATIC_DRAW);
//EBO
glGenBuffers(1, &groundEBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, groundEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(groundIndex), groundIndex, GL_STATIC_DRAW);
//connectToVertexShader
glEnableVertexAttribArray(0); //position
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, 0);
glEnableVertexAttribArray(1); //color
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (char*)(sizeof(float)*3));
}
void paintGL(void)
{
//TODO:
//render your objects and control the transformation here
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//translate model
glm::mat4 modelTransformMatrix = glm::translate(glm::mat4(), vec3(+0.0f, +0.0f, -3.0f));
//perspective view
glm::mat4 projectionMatrix = glm::perspective(+40.0f, +1.0f, +1.0f, +60.0f);
//ultimate matrix
glm::mat4 ultimateMatrix;
//register location on the graphics cards
GLint ultimateMatrixUniformLocation = glGetUniformLocation(programID, "ultimateMatrix");
/*GLint modelTransformMatrixUniformLocation = glGetUniformLocation(programID, "modelTransformMatrix");
GLint projectionMatrixUniformLocation = glGetUniformLocation(programID, "projectionMatrix");*/
//drawing the ground
/*glUniformMatrix4fv(modelTransformMatrixUniformLocation, 1, GL_FALSE, &modelTransformMatrix[0][0]);
glUniformMatrix4fv(projectionMatrixUniformLocation, 1, GL_FALSE, &projectionMatrix[0][0]);*/
glBindVertexArray(groundVAO);
ultimateMatrix = projectionMatrix * modelTransformMatrix;
glUniformMatrix4fv(ultimateMatrixUniformLocation, 1, GL_FALSE, &ultimateMatrix[0][0]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
glFlush();
glutPostRedisplay();
}
void initializedGL(void) //run only once
{
glewInit();
glEnable(GL_DEPTH_TEST);
sendDataToOpenGL();
installShaders();
}
int main(int argc, char *argv[])
{
/*Initialization*/
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutCreateWindow("Try");
glutInitWindowSize(700, 700);
//const GLubyte* glversion = glGetString(GL_VERSION);
/*Register different CALLBACK function for GLUT to response
with different events, e.g. window sizing, mouse click or
keyboard stroke */
initializedGL();
//glewExperimental = GL_TRUE;
glutDisplayFunc(paintGL);
glutKeyboardFunc(keyboard);
/*Enter the GLUT event processing loop which never returns.
it will call different registered CALLBACK according
to different events. */
//printf("OpenGL ver: %s\n", glversion);
glutMainLoop();
return 0;
}
VertexShaderCode.glsl:
#version 430 // GLSL version your computer supports
in layout(location=0) vec3 position;
in layout(location=1) vec3 vertexColor;
uniform mat4 ultimateMatrix;
out vec3 theColor;
void main()
{
vec4 v = vec4(position, 1.0);
gl_Position = ultimateMatrix * v;
theColor = vertexColor;
}
FragmentShaderCode.glsl:
#version 430 //GLSL version your computer supports
out vec4 theColor2;
in vec3 theColor;
void main()
{
theColor2 = vec4(theColor, 1.0);
}
Functions: checkStatus, checkShaderStatus, checkProgramStatus, readShaderCode, installShaders should be all fine.
void keyboard() can be ignored since I havent implemented it yet(just for keyboard control).
I implemented the object "Ground" in sendDataToOpenGL(). But when I compiled and ran the program, "thread 1: exc_bad_access (code =1, address=0x0)" occured in the line of VAO:
And the pop-out window is just a white screen instead of a green grass(3d).
I have tried a method which was provided in other stackoverflow post: using glewExperimental = GL_TRUE;. I didnt see any errors by using that, but the popout screen vanished immediately just after it appeared. It seems that it couldnt help the problem.
Can someone give me a help? Thank you!
glGenVertexArrays is available in since OpenGL version 3.0. If vertex array objects are supported can be checked by glewGetExtension("GL_ARB_vertex_array_object").
Glew can enable additional extensions by glewExperimental = GL_TRUE;. See the GLEW documantation which says:
GLEW obtains information on the supported extensions from the graphics driver. Experimental or pre-release drivers, however, might not report every available extension through the standard mechanism, in which case GLEW will report it unsupported. To circumvent this situation, the glewExperimental global switch can be turned on by setting it to GL_TRUE before calling glewInit(), which ensures that all extensions with valid entry points will be exposed.
Add this to your code:
glewExperimental = GL_TRUE;
glewInit();
I am following the 8Th edition and below is the code I modified from book. it does not give me what I want, but a blank window instead. I wonder what mistake(s) did I make during modifications?
below is the code I modified, hope anyone can point out the mistake.
#include <iostream>
#include <OpenGL/glu.h>
#include <OpenGL/gl3.h>
#include <GLUT/GLUT.h>
GLuint vaoNames[1];
GLuint bufferNames[1];
int vPosition = 0;
GLuint createProgram();
GLuint createShader(GLenum type, const char* src);
GLuint program;
void init()
{
glGenVertexArrays(1, vaoNames);
glBindVertexArray(vaoNames[0]);
GLfloat vertices[6][2] = {
{ -0.90, -0.90 }, // Triangle 1
{ 0.85, -0.90 },
{ -0.90, 0.85 },
{ 0.90, -0.85 }, // Triangle 2
{ 0.90, 0.90 },
{ -0.85, 0.90 }
};
glGenBuffers(1, bufferNames);
glBindBuffer(GL_ARRAY_BUFFER, bufferNames[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glUseProgram(createProgram());
glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(vPosition);
}
void render()
{
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(vaoNames[0]);
glDrawArrays(GL_TRIANGLES, 0, 6);
glFlush();
}
GLuint createProgram()
{
GLuint program = glCreateProgram();
const char* vertexShaderSrc =
"#version 400 core \n"
"layout( location = 0 ) in vec4 vPosition; \n"
"void main() \n"
"{ \n"
"gl_Position = vPosition; \n"
"}";
GLuint vertexShader = createShader(GL_VERTEX_SHADER, vertexShaderSrc);
const char* fragmentShaderSrc =
"#version 400 core \n"
"out vec4 fColor; \n"
"void main() \n"
"{ \n"
"fColor = vec4( 0.0, 0.0, 1.0, 1.0 ); \n"
"}";
GLuint fragmentShader = createShader(GL_FRAGMENT_SHADER, fragmentShaderSrc);
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
return program;
}
GLuint createShader(GLenum type, const char* src){
GLuint shaderID = glCreateShader(type);
glShaderSource(shaderID, 1, &src, 0);
glCompileShader(shaderID);
return shaderID;
}
int main(int argc, char ** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA);
glutInitWindowSize(512, 512);
glutCreateWindow("8877");
init();
glutDisplayFunc(render);
glutMainLoop();
}
When you say 'there is no error during shader compilation and program linking', do you mean that the the program doesn't stop, or do you mean that you actually ran a different program and checked the results of glGetError() at various points, or fetched the compilation or linking log? Your program as is does no actual error checking, and OpenGL doesn't throw exceptions or produce error output as a matter of course. You have to actually request these things.
Simply because glCompileShader() doesn't generate an error doesn't mean your shader actually compiled. You need to be checking the compilation status of shaders and the link status of programs with code similar to this:
GLint compiled;
glGetShaderiv(newShader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
std::string log = getLog(newShader);
SAY(log.c_str());
throw std::runtime_error("Failed to compile shader " + log);
}
where getLog is
static std::string getLog(GLuint shader) {
std::string log;
GLint infoLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1) {
char* infoLog = new char[infoLen];
glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
log = std::string(infoLog);
delete[] infoLog;
}
return log;
}
This is for checking shaders, and there are equivalent functions for checking the results and getting the logs for program linking. Unless you're calling them you have no guarantee that the shaders are compiling. Assuming you haven't made some simple mistake in the shader syntax, the most likely problem you're facing is a lack of support for OpenGL 4.x, which would cause the shaders to fail right off.