I have a class that represents an gui-element, that has method to set an image on it's background:
class Element
{
public:
ID2D1Bitmap *image;
ID2D1DeviceContext *target;
int x, y, width, height;
Element(ID2D1DeviceContext *target, int x, int y, int width, int height)
{
image = nullptr;
this->target = target;
this->x = x; this->y = y; this->width = width; this->height = height;
}
void Render()
{
if(image)
target->DrawBitmap(image, D2D1::RectF(x, y, x + width, y + height));
}
void setBackgroundImage(const wchar_t* path)
{
if (!path || wcslen(path) == 0)
return;
IWICBitmapFrameDecode* d2dBmpSrc = nullptr;
IWICBitmapDecoder* d2dDecoder = nullptr;
d2dWICFactory->CreateDecoderFromFilename(path, NULL, GENERIC_READ,
WICDecodeMetadataCacheOnLoad, &d2dDecoder);
if (d2dDecoder)
{
d2dDecoder->GetFrame(0, &d2dBmpSrc);
if (d2dBmpSrc)
{
d2dWICFactory->CreateFormatConverter(&d2dConverter2);
d2dConverter2->Initialize(d2dBmpSrc, GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut);
ID2D1Bitmap *temp = nullptr;
tar->CreateBitmapFromWicBitmap(d2dConverter2, NULL, &temp);
if (temp)
{
D2D1_SIZE_F si = temp->GetSize();
tar->CreateBitmap(D2D1::SizeU(si.width, si.height), 0, 0, D2D1::BitmapProperties(
D2D1::PixelFormat(DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE::D2D1_ALPHA_MODE_PREMULTIPLIED)
), &image);
image->CopyFromBitmap(0, temp, 0);
SafeRelease(&temp);
}
}
}
SafeRelease(&d2dDecoder);
SafeRelease(&d2dBmpSrc);
SafeRelease(&d2dConverter2);
}
~Element(){SafeRelease(&image);}
}*object[100] = {NULL};
int main()
{
ID2D1Factory *factory = nullptr;
D2D1CreateFactory(D2D1_FACTORY_TYPE::D2D1_FACTORY_TYPE_MULTI_THREADED, &factory);
ID2D1DeviceContext *target = ObtainDeviceContext(factory);
object[0] = new Element(target, 0, 0, 100, 100);
object[0]->setBackgroundImage(L"img.png");
for(;;) // But in reality here also is Windows message loop with Dispatch message
{
target->BeginDraw();
target->Clear(D2D1::ColorF(1, 1, 1));
for(int i = 0; i < 100 && object[i]; i++)
object[i]->Render();
target->EndDraw();
}
return 0;
}
All works fine, but the problem, is that loading an image is obviously hangs the program.
Unfortunately, my asynchronous c++ skills are almost empty. I tried to just change method to this:
void Element::setBackgroundImage(const wchar_t*path)
{
thread th(LoadImage(this, path)); th.detach();
}
And jsut bring all code from the method to global function, with additional first argument - LoadImage(Object*,const wchar_t*);
Unfortunately, it immediately crashes. Then I created global variable mutex mu and placed mu.lock() and mu.unlock() as first and last line in LoadImage correspond. Still crashes.
Maybe I also need to lock in Render, and probably on destructor? By the way, what will happen if destructor will try to release image variable at time when it is locked by another thread? It will not be free, and hence memory leak?
Can someone please explain at least general conception of using asynchronous c++ programming for my case? Not thread::join, I need the main thread be going.
Also I would appreciate if You explain how to properly make render loop running in asynchronous thread.
First off, your code is far from a minimal reproducible example. But then again, I've never touched Direct2D before, and I still managed to get it working.
Anyway, it doesn't matter what you do, you'll have to load the image before you're able to render it. Since you used browsers as an example in the comments, what they do is indeed fetch and load files on separate threads, but before they're loaded and in memory, they're rendering placeholders or nothing at all.
Loading the image in a separate thread and locking a mutex in the render function is the same as not using any threads at all (functionally, at least. The main thread will still block in render while the image is being loaded). Really, what you tried before, with this:
should work, (in my testing even with a
D2D1_FACTORY_TYPE_SINGLE_THREADEDfactory), since you're not rendering the element ifimage == nullptr, which acts a sort of lock. Using a properbool loadedstate variable would be even better, but it still works.You just misused the
std::threadconstructor syntax, since you're handing it the return value ofLoadImagewith the argumentsthisandpath, which is almost certainly not a function pointer or lambda, given that your program is crashing (In simple words: You're calling the function instead of passing it in as an argument).Creating a
std::threadcallingElement::setBackgroundImageproperly:It's really as simple as handling the Win32 window message loop on the main thread, while doing the rendering on a separate thread.
This is where you might want to use a mutex, to synchronize the start and stop of the loop, but aside from that, this is enough.
Also, a (mostly) minimally reproducible example; all that's missing is the WINDOW::Window class, which can create a Win32 window among other things: