0

I wrote the following class which is supposed to handle swapping of two buffers used for drawing on another thread.

IE: This class will render to the back-buffer while some other thread renders the front-buffer to the screen and queue the next buffer for rendering when ready (otherwise it should block - Ideally, I don't want it to block though)..

To simulate this, I came up with the following code:

#include <iostream>
#include <queue>
#include <vector>
#include <memory>
#include <atomic>
#include <thread>

//Handles swapping buffers (multiple buffers - Ideally more than just 2 but for now, we use 2).
class NativeBuffer
{
private:
    std::mutex mutex;
    std::thread pool;
    std::vector<std::uint8_t> back_buffer;
    std::vector<std::uint8_t> front_buffer;
    std::deque<std::uint8_t*> buffers;
    std::queue<std::uint8_t*> used;

    std::atomic_int32_t width;
    std::atomic_int32_t height;
    std::atomic_int32_t stride;

    void update()
    {
        //Simulate image that will be drawn to the back-buffer (fixed size)

        std::uint8_t* src = new std::uint8_t[1024 * 1024 * 4]();

        while(true)
        {
            std::unique_lock<std::mutex> lock{mutex};
            if (buffers.empty())
            {
                //printf("No Buffers\n");
                continue;
            }

            std::uint8_t* next = buffers.back();
            buffers.pop_back();
            lock.unlock();

            if (src)
            {
                //printf("Back Buffer Rendering\n");
                lock.lock();
                std::memcpy(next, src, width * height * stride);
                buffers.push_back(next);
                lock.unlock();
            }
        }
    }

public:
    NativeBuffer() : mutex(), pool(), back_buffer(), front_buffer(), width(0), height(0), stride(0)
    {
        this->pool = std::thread([this]{
            this->update();
        });
    }

    ~NativeBuffer()
    {
        this->pool.join();
    }

    //Resize the buffer whenever the UI size changes..
    void resize_if_needed(std::int32_t width, std::int32_t height, std::int32_t stride)
    {
        if (this->width * this->height * this->stride != width * height * stride)
        {
            std::lock_guard<std::mutex>{mutex};
            back_buffer.resize(width * height * stride);
            front_buffer.resize(width * height * stride);

            this->width = width;
            this->height = height;
            this->stride = stride;

            while(buffers.size())
            {
                buffers.pop_front();
            }

            while(used.size())
            {
                used.pop();
            }

            buffers.push_back(&front_buffer[0]);
            buffers.push_back(&back_buffer[0]);

            printf("Resizing\n");
        }
    }

    void* lock()
    {
        std::unique_lock<std::mutex> lock{mutex};
        if (buffers.empty())
        {
            return nullptr;
        }

        std::uint8_t* current = buffers.front();
        used.push(current);
        buffers.pop_front();
        lock.unlock();

        return current;
    }

    void unlock()
    {
        std::unique_lock<std::mutex> lock{mutex};
        if (used.empty())
        {
            return;
        }

        std::uint8_t* current = used.front();
        used.pop();
        buffers.push_back(current);
        lock.unlock();
    }
};

//Simulate drawing to the screen..
void draw(void* screen, std::int32_t w, std::int32_t h, std::int32_t stride)
{
    int i = 0;
    while(true)
    {
        static NativeBuffer native;

        //Simulate splash screen going away.
        //After splash screen.. size changes.. so we also update..
        if (i > 1000)
        {
            w = 512;
            h = 512;
        }

        ++i;
        native.resize_if_needed(w, h, stride);

        void* buffer = native.lock();
        if (buffer)
        {
            //printf("Drawing\n");
            std::memcpy(screen, buffer, w * h * stride);
            native.unlock();
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
}

int main(int argc, const char * argv[]) {

    //Simulate screen buffer..
    std::uint8_t* screen = new std::uint8_t[1024 * 1024 * 4]();

    //Screen original size is 304 x 30 on launch..
    draw(screen, 304, 30, 4);

    return 0;
}

However, the application crashes on line std::memcpy(next, src, width * height * stride);. It seems to be crashing after calling resize_if_needed if the buffer size changes (if I never change the size, everything works).

Any ideas why? I have locked the buffers before resizing. I have locked them right before using them.. I can't see why it crashes :S

Brandon
  • 22,723
  • 11
  • 93
  • 186
  • "the application crashes" Be more specific. What sort of crash? What does the debugger say? What is the stack trace? – Lightness Races in Orbit Jan 12 '20 at 19:29
  • It doesn't give a stack trace but it gives `ACCESS_VIOLATION` on the `memcpy` inside the `update` function. – Brandon Jan 12 '20 at 19:32
  • It will give you a stack trace. Which debugger is it? – Lightness Races in Orbit Jan 12 '20 at 19:33
  • It's `XCode` it really doesn't give a stack-trace. It just jumps straight to line 52 `memcpy` inside the `update` function. I believe it's because `update` is being called on a separate thread it might have issues with. It shows exactly: `NativeBuffer::update()` -> `std::memcpy(next, src, width * height * stride);` – Brandon Jan 12 '20 at 19:34
  • Xcode gives stack traces on access violations. You should review how to use your debugger - once you've done that you'll be able to use it to find clues as to the cause of your crash, including stepping through and examining the values of your variables. Unfortunately we can't do that remotely. Good luck! – Lightness Races in Orbit Jan 12 '20 at 19:35
  • When I say it really gives nothing, you don't believe me.. https://i.imgur.com/b2KmQoW.png and https://i.imgur.com/2nJAVZ5.png It only shows the memcpy. Everything else is either assembly or internal to `std::thread`.. – Brandon Jan 12 '20 at 19:38

0 Answers0