【问题标题】:Poor opengl image processing performanceopengl图像处理性能差
【发布时间】:2010-09-20 22:23:07
【问题描述】:

我正在尝试使用 opengl 进行一些简单的图像处理。由于我找不到任何可以做到这一点的好图书馆,我一直在尝试自己解决方案。

我只是想在 gpu 上合成一些图像,然后将它们读回。然而,我的实现的性能似乎几乎等同于它在 cpu 上所做的事情......出了点问题......

我已尝试遵循我在网上找到的最佳做法。但它仍然做错了什么。

我已经尝试删除所有不相关的代码。

关于为什么此实现性能不佳的任何想法?

int image_width = 1280;
int image_height = 720;
int image_size = image_width * image_height;

class texture
{
public:
    texture()
    {   
        glGenTextures(1, &texture_);    
        bind();
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image_width, image_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
    }

    ~texture(){ glDeleteTextures(1, &texture_); }
    void bind(){ glBindTexture(GL_TEXTURE_2D, texture_); }
    GLuint handle() { return texture_; }

private:
    GLuint texture_;
};
typedef std::shared_ptr<texture> texture_ptr;

class pixel_buffer // pixel buffer with associated texture
{
public:
    pixel_buffer()
    {
        glGenBuffersARB(1, &pbo_);
        bind_pbo();
        glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, image_size, 0, GL_STREAM_DRAW);
    }

    ~pixel_buffer(){ glDeleteBuffers(1, &pbo_); }

    void begin_write(void* src)
    {
        texture_.bind();
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo_);
        glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, image_size, 0, GL_STREAM_DRAW);
        void* ptr = glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY);
        assert(ptr); 
        memcpy(ptr, src, image_size);
        glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
    }

    void end_write()
    {
        bind_texture();
        bind_pbo();
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image_width, image_height, GL_BGRA, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
        unbind_pbo();
    }

    void begin_read(GLuint buffer)
    {
        glReadBuffer(buffer);
        glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo_);
        glBufferData(GL_PIXEL_PACK_BUFFER_ARB, image_size, NULL, GL_STREAM_READ);
        glReadPixels(0, 0, image_width, image_height, GL_BGRA, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
    }

    void end_read(void* dest)
    {
        void* ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);   
        memcpy(dest, ptr, image_size);
        glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB);
        unbind_pbo();
    }

    void bind_pbo(){ glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo_); }
    void unbind_pbo(){ glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo_); }
    void bind_texture() { texture_.bind(); }

    GLuint texture_handle() { return texture_.handle(); }

private:
    texture texture_;
    GLuint pbo_;
};
typedef std::shared_ptr<pixel_buffer> pixel_buffer_ptr;

class frame_buffer// frame buffer with associated pixel buffer
{
public:
    frame_buffer()
    {
        glGenFramebuffersEXT(1, &fbo_);

        bind();
        pbo_.bind_texture();
        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, pbo_.texture_handle(), 0);
    }

    ~frame_buffer() { glDeleteFramebuffersEXT(1, &fbo_); }

    void bind() { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); }
    void unbind() { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);    }

    void begin_read()
    {
        bind();
        pbo_.begin_read(GL_COLOR_ATTACHMENT0_EXT);
    }

    void end_read(void* dest)
    {
        pbo_.end_read(dest);
        unbind();
    }

private:
    pixel_buffer pbo_;
    GLuint fbo_;
};
typedef std::shared_ptr<frame_buffer> frame_buffer_ptr;

struct image_processor::implementation
{   
    void compose(const std::vector<image_ptr>& images)
    {                   
        // END PREVIOUS READ
        if(reading_fbo_)
        {
            image_ptr result_image = std::make_shared<image>(image_size);
            reading_fbo_->end_read(result_image->data());
            output_.push(reading_result_image_);

            reading_fbo_ = nullptr;
        }

        // END PREVIOUS WRITE
        frame_buffer_ptr written_fbo;
        if(!writing_pbo_group_.empty())
        {
            // END
            written_fbo = get_fbo();
            written_fbo->bind();
            glClear(GL_COLOR_BUFFER_BIT);   

            for(size_t n = 0; n < writing_pbo_group_.size(); ++n)
            {
                writing_pbo_group_[n]->end_write();
                writing_pbo_group_[n]->bind_texture();
                quad_->draw(); // DRAW FULLSCREEN QUAD
            }
            written_fbo->unbind();

            writing_pbo_group_.clear();
        }

        // BEGIN NEW WRITE
        if(!images.empty())
        {           
            for(size_t n = 0; n < images.size(); ++n)
            {
                auto pbo = get_pbo();
                pbo->begin_write(images[n]->data());

                writing_pbo_group_.push_back(pbo);
            }
        }

        // BEGIN NEW READ       
        if(written_fbo)
        {   
            written_fbo->begin_read();

            reading_fbo_ = written_fbo; 
        }
    }

    pixel_buffer_ptr get_pbo()
    {
        if(pbo_pool_.empty())
            pbo_pool_.push_back(std::make_shared<pixel_buffer>());

        auto pbo = pbo_pool_.front();
        pbo_pool_.pop_front();

        return pixel_buffer_ptr(pbo.get(), [=](pixel_buffer*){pbo_pool_.push_back(pbo);});
    }

    frame_buffer_ptr get_fbo()
    {
        if(fbo_pool_.empty())
            fbo_pool_.push_back(std::make_shared<frame_buffer>());

        auto fbo = fbo_pool_.front();
        fbo_pool_.pop_front();

        return frame_buffer_ptr(fbo.get(), [=](frame_buffer*){fbo_pool_.push_back(fbo);});
    }

    std::vector<pixel_buffer_ptr>   writing_pbo_group_;
    frame_buffer_ptr                reading_fbo_;

    std::deque<pixel_buffer_ptr> pbo_pool_;
    std::deque<frame_buffer_ptr> fbo_pool_;
};

编辑:

做了一些分析。大多数 cpu 时间似乎都花在了 begin_write();

虽然看不出有什么问题...

void begin_write(void* src)
{
    texture_.bind();
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo_);
    glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, image_size, 0, GL_STREAM_DRAW);
    void* ptr = glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY);
    assert(ptr); 
    memcpy(ptr, src, image_size);
    glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}

【问题讨论】:

  • 您使用的是什么 GPU?在处理小图像时,CPU 可以非常快速地计算这一点。您应该使用更大的图像测试您的实现,看看哪个具有更好的性能。
  • Quadro FX 1700M,英特尔酷睿 2 双核。我会用不同的尺寸做更多的测试。
  • 增加图片大小确实更快。尽管 CPU 开销,我仍然有点惊讶。它基本上只是在做 N+1 个内存副本,其余的是异步 gpu 操作……尤其是当我看到 CUDA 性能图几乎没有任何开销时。

标签: c++ opengl gpgpu


【解决方案1】:

您的瓶颈是从 CPU 到 GPU 的内存传输。当您需要对存储在 GPU 内存中的相同少量数据执行许多并行计算时,GPU 允许您提高性能(例如,以每秒 30 帧的速度绘制相同的场景)。当您需要在主内存和 GPU 内存之间来回传输数据时,大部分时间都浪费在这上面。

【讨论】:

  • 我相信你的分析是正确的。那么我的问题是如何减少 cpu -> gpu 传输开销。
  • 通过存储更多数据并直接在 GPU 上生成任何可能的内容。这取决于您的应用程序。并非所有算法都适合在 GPU 上运行。如果您只想混合 N 个大图像一次并忘记它们,那么 GPU 不适合您。如果您有 N 张图像,然后在用户不断更改参数(例如图像编辑软件)的同时应用一些复杂的合成,那么您可以将它们加载到 GPU 一次并多次使用。
  • 如果我立即使用 pbo 作为图像的后台存储怎么办?从而消除了对额外副本的需要。写入 glMapped 内存是否比普通内存慢?
  • 这里没有魔法。 gl*Buffer API 只允许您在 GPU 上分配内存。通过调用 glMapBuffer,您只需请求驱动程序在您的进程地址空间中为该内存分配一个地址。当您在那里写入时,操作系统/驱动程序会将其传输到 GPU。其性能取决于 PCI 总线的带宽(或 GPU 的任何连接方式)。
【解决方案2】:

尝试更改像素格式(即 BGRA 而不是 ARGB)。

我不确定具体细节,但卡实际使用的内容与您想要的内容不匹配可能会产生灾难性的性能。

(如果没有关于您的数据集、您正在运行的算法和一些硬基准数据的信息,很难提供更多帮助......)

【讨论】:

  • 我正在使用 BGRA。如果尝试谷歌搜索 opengl 原生纹理格式但没有成功。什么是原生格式?
  • 哦。您的代码在一个地方说 GL_RGBA8 。本机格式取决于您使用的卡(OpenGL 本身没有,它取决于实现)。最好的方法是进行基准测试,看看是否有任何不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-11
  • 2011-05-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多