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