Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I render an RGB bitmap into a GLKView using 2D textures?

I have an in-memory "bitmap" which is just a malloced Byte * array that contains pixel data in a simple RGB format (so the size of the byte array is 3 * the number of pixels).

My app is just a view controller with an instance of GLKView. I have implemented its delegate like so:

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
}

and as expected this sets the background color of the GLKView.

What I would now like to do is add code to this implementation of glkView:drawInRect: so that my "bitmap" is rendered into this GLKView. But I can't seem to find any way of doing that simply; I'm kind of overwhelmed by all the different things OpenGL can do, all of which are much more complex than what I'm trying to do here.

glReadPixels seems to sort of be what I'm after here as it seems to provide a pointer to the buffer data.

Edit: apparently this can only be accomplished via the use of textures. I have attempted to implement this with this sample code (note that my "bitmap" here is 4 bytes per sample, matching the format parameters):

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {

    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // raw data
    int width = 30;
    int height = 30;
    int pixelCount = width * height;
    int byteSize = pixelCount * 4;
    GLubyte *textureData = (GLubyte *)malloc(byteSize);

    for (int i = 0; i < byteSize; i++) {
        textureData[i] = 255; // white
    }

    glEnable(GL_TEXTURE_2D);

    GLuint textureID;
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, 
        GL_RGBA, GL_UNSIGNED_BYTE, textureData);

    free(textureData);

}

... but it is not working. The glClear() call works as expected and sets the entire background red; but if I understand the texture samples correctly, the code below that should be drawing a 30x30 white square in the corner, but all I'm getting is the solid red background.

Can anybody spot what I'm doing wrong here?

like image 664
MusiGenesis Avatar asked Oct 08 '15 01:10

MusiGenesis


1 Answers

Sorry about pushing into OpenGL :)

I would create CGImage and then UIImage

void *baseAddress = & textureData;
size_t bytesPerRow = width * 4;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
CGImageRef cgImage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
UIImage *image = [UIImage imageWithCGImage:cgImage];

and then draw it with drawInRect:

Edit:

Here is code I've written. This is not production code: it lacks checking for openGL errors, some minor leaks and have global variables but it's good place to enhance it.

#import "ViewController.h"
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>

@interface ViewController ()

@end

@implementation ViewController

GLuint tex;
float vertices[] = {
    //  Position    Texcoords
    -1.0f,  1.0f, 0.0f, 0.0f, // Top-left
    1.0f,  1.0f, 1.0f, 0.0f, // Top-right
    1.0f, -1.0f, 1.0f, 1.0f, // Bottom-right
    -1.0, -1.0f, 0.0f, 1.0f  // Bottom-left
};

const char * vertexShader = "attribute vec2 position;\n"
                            "attribute vec2 TexCoordIn;\n"
                            "varying vec2 TexCoordOut;\n"
                            "void main() {\n"
                                "gl_Position = vec4(position, 0.0, 1.0);\n"
                                "TexCoordOut = TexCoordIn;\n"
                            "}\n";

const char * fragmentShader =   "precision mediump float;\n"
                                "varying lowp vec2 TexCoordOut;\n"
                                "uniform sampler2D Texture;\n"
                                "void main() {\n"
                                    "gl_FragColor = texture2D(Texture, TexCoordOut);\n"
                                "}\n";

GLuint shaderProgram;
GLuint vao;
GLuint vbo;

-(void) initOpenGLObjects {
    glGenVertexArraysOES(1, &vao);
    glBindVertexArrayOES(vao);
    
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);
    
    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vs, 1, &vertexShader, NULL);
    glCompileShader(vs);
    
    GLint status;
    glGetShaderiv(vs, GL_COMPILE_STATUS, &status);
    
    char buffer[512];
    glGetShaderInfoLog(vs, 512, NULL, buffer);
    
    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fs, 1, &fragmentShader, NULL);
    glCompileShader(fs);
    
    glGetShaderiv(fs, GL_COMPILE_STATUS, &status);
    
    glGetShaderInfoLog(fs, 512, NULL, buffer);
    
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vs);
    glAttachShader(shaderProgram, fs);
    glLinkProgram(shaderProgram);
    
    GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
    GLint texAttrib = glGetAttribLocation(shaderProgram, "TexCoordIn");
    
    glGenBuffers(1, &vbo); // Generate 1 buffer
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), 0);
    glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), (void*)(2*sizeof(float)));
    
    glEnableVertexAttribArray(posAttrib);
    glEnableVertexAttribArray(texAttrib);
}


-(void) viewDidLoad{
    [super viewDidLoad];
    
    self.glkView.context = [[EAGLContext alloc] initWithAPI:
                           kEAGLRenderingAPIOpenGLES2];
    
    [EAGLContext setCurrentContext:self.glkView.context];
    
    CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
    [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

    self.glkView.delegate = self;
    [self initOpenGLObjects];
}

- (void)update
{
    [self.glkView setNeedsDisplay];
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    int width = 300;
    int height = 300;
    int pixelCount = width * height;
    int byteSize = pixelCount * 4;
    GLubyte *textureData = (GLubyte *)malloc(byteSize);
    static int time = 0;
    time = (time+1)%256;
    
    for (int i = 0; i < byteSize; i+=4) {
        textureData[i] = 255;
        textureData[i+1] = time;
        textureData[i+2] = 255;
        textureData[i+3] = 255;
    }
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, textureData);
    
    glUseProgram(shaderProgram);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    
    free(textureData);
}

@end

I don't know how do you want to update data, maybe you don't have to malloc/free it every time and just do small changes and update it with glTexSubImage2D. Most of the time is spend on filling data.

This code worked on my MacBook with Xcode 7 in simulators and iPhone 6 with iOS 9.0.2.

like image 167
Adrian Krupa Avatar answered Nov 11 '22 17:11

Adrian Krupa