Mugichoko's blog

Mugichoko’s blog

プログラミングを中心としたメモ書き.

GLSL #12: Pixel Buffer Object (PBO)

目標

Pixel Buffer Object (PBO) を使って,GPUメモリにあるテクスチャ(画像)をメインメモリに読み込む.「glReadPixels()やglGetTexImage()は遅い!」でも「PBOを使うと速い!」という話をよく聞くので,実装してみた.

前回記事は,以下のものですが,今回とは特に関係ありません. mugichoko.hatenablog.com

参考にしたサイトは以下の通り. www.songho.ca

実装環境

サンプルプログラム

pbo.h

#pragma once

// PBO: http://www.songho.ca/opengl/gl_pbo.html#create
#include <iostream>
using namespace std;
#include <GL/glew.h>
#include <GLFW/glfw3.h>

namespace gl
{
    static size_t typeID(
        GLenum type
    );

    class pbo
    {
    private:
        const int width;
        const int height;
        const int channel;
        GLenum format;
        GLenum type;
        
        GLuint pboBuff;
        const int buffSize;

    public:
        pbo(
            int w,
            int h,
            int ch,
            GLenum format,  // e.g., GL_RGBA
            GLenum type     // e.g., GL_FLOAT
        );
        ~pbo();

        void readPixelsFromTex(
            GLuint texID,
            void *data
        );

        void readPixelsFromBuff(
            GLuint buffID,  // Set fbo ID or zero to read back buffer
            void *data
        );

        int getWidth();
        int getHeight();
        int getChannel();
        GLenum getFormat();
        GLenum getType();
    };
}

pbo.cpp

#include "pbo.h"

namespace gl
{
    static size_t typeID(
        GLenum type
    )
    {
        if (type == GL_FLOAT)
            return sizeof(float);
        else if (type == GL_UNSIGNED_BYTE)
            return sizeof(unsigned char);
        else
            return sizeof(int);
    }

    pbo::pbo(
        int w,
        int h,
        int ch,
        GLenum format,
        GLenum type
    ) : width(w), height(h), channel(ch), format(format), type(type),
        buffSize(w * h * ch * typeID(type))
    {
        glGenBuffers(1, &pboBuff);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboBuff);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, buffSize, NULL, GL_STREAM_READ);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
    }

    pbo::~pbo()
    {
        // PBO
        glBindBuffer(GL_ARRAY_BUFFER, pboBuff);
        glDeleteBuffers(1, &pboBuff);
        pboBuff = 0;
    }

    void pbo::readPixelsFromTex(
        GLuint texID,
        void *data
    )
    {
        // Send normal texture data to the PBO
        glBindBuffer(GL_PIXEL_PACK_BUFFER, pboBuff);
        glBindTexture(GL_TEXTURE_2D, texID);
        glGetTexImage(GL_TEXTURE_2D, 0, format, type, NULL);
        // Send PBO to main memory
        GLubyte *pboPtr = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
        if (pboPtr)
        {
            memcpy(data, pboPtr, buffSize);
            glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
        }
        glBindTexture(GL_TEXTURE_2D, 0);
        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    }

    void pbo::readPixelsFromBuff(
        GLuint buffID,
        void *data
    )
    {
        // Send normal texture data to the PBO
        glBindBuffer(GL_PIXEL_PACK_BUFFER, pboBuff);
        glBindFramebuffer(GL_READ_FRAMEBUFFER, buffID);
        glReadPixels(0, 0, width, height, format, type, data);
        // Send PBO to main memory
        GLubyte *pboPtr = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
        if (pboPtr)
        {
            memcpy(data, pboPtr, buffSize);
            glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
        }
        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    }

    int pbo::getWidth()
    {
        return width;
    }
    int pbo::getHeight()
    {
        return height;
    }
    int pbo::getChannel()
    {
        return channel;
    }
    GLenum pbo::getFormat()
    {
        return format;
    }
    GLenum pbo::getType()
    {
        return type;
    }
}

結果

OpenCVのcv::imshow()を使って,読み込んだ画像を表示したところできました.ちなみに,読み込み先のOpenGLの座標系とOpenCVの座標系の関係から上下逆さまになっています.ということで,画像は上手く読み込めたけれど合っているのかな?特段,速くなったようにも思えないのだが… おそらく,GPUアーキテクチャに依存しているのだろう.

f:id:Mugichoko:20170901233206j:plain