This seems like some locking mechanism is failing. The assert is clearly happening on the render thread and the render thread shouldn't even enter the render methods if a major delete operations are happening on the message thread.
Yes, the assert is happening on the OpenGL render thread. Below is the code where i delete the DocumentWindows in case I am doing something wrong. The "mQAWindows" member variable is of datatype "std::map<int, ScopedPointer<QAWindow>>", where "QAWindow" is the custom "DocumentWindow". This map just keeps pointers to the windows so that they may be deleted properly.
void QAHandler::destroyAllQAWindows()
{
const ScopedLock sl(mLock);
std::function<void(void)> destroyAllQAWindowsLambda = [this]() { this->destroyAllQAWindowsOnJuceMessageThread(); };
juce::MessageManager::callAsync(destroyAllQAWindowsLambda);
}
// Must be called on the Juce message thread.
void QAHandler::destroyAllQAWindowsOnJuceMessageThread()
{
const ScopedLock sl(mLock);
for (std::map<int, ScopedPointer<QAWindow>>::iterator it = mQAWindows.begin(); it != mQAWindows.end(); ++it)
{
it->second = nullptr;
}
mQAWindows.clear();
startNextQARun(); // Starts the next test case, if any, thereby creating new DocumentWindows.
}
And just to show the whole picture. The windows are created like this on the message thread:
for (int i = 0; i < qaRun.QAComposites.size(); i++)
{
juce::OptionalScopedPointer<QAWindow> window = juce::OptionalScopedPointer<QAWindow>(new QAWindow(1000, 800, this, qaRun, i), false);
}
At OpenGL context creation, the window calls back to add a reference. It is done like this because our SDK creates a unique ID (compositeId) for each OpenGL context to know where to render to. Our rendering composite is created in the window just after the OpenGL context is created (in OpenGLAppComponent::initialise()).
void QAHandler::onCreateComposite(const int& compositeId, const std::string& compositeName, juce::OptionalScopedPointer<QAWindow> window)
{
const ScopedLock sl(mLock);
mCompositeIdFinishedMap[compositeId] = false;
mQAWindows[compositeId] = window; // <--------------- Here is the reference to the window kept until deletion.
mCompositeIdNameMap[compositeId] = compositeName;
}
You say that the assertion always happens during the deletion of the DocumentWindow. Do you mean that the main thread's call stack is inside the delete code of the DocumentWindow? Can you send me a stack trace of that thread as well?
Yes, the Juce Message Thread calls the destructor of the DocumentWindow (customly named QAWindow in my code). The DocumentWindow further has a "ScopedPointer<QAView>" member, where the QAView class inherits from "OpenGLAppComponent" to do the actual rendering.
Here is the destructors and the corresponding stack trace of the when deleting the DocumentWindow on the Juce Message Thread. The destructor of the OpenGLAppComponent/QAView is called from the destructor of the DocumentWindow/QAWindow.
QAView::~QAView()
{
shutdownOpenGL();
}
QAWindow::~QAWindow()
{
mQAView = nullptr;
}
mQATool.exe!QA::QAView::~QAView() Line 22
[External Code]
mQATool.exe!juce::ContainerDeletePolicy<QA::QAView>::destroy(QA::QAView * object) Line 48
mQATool.exe!juce::ScopedPointer<QA::QAView>::operator=(QA::QAView * const newObjectToTakePossessionOf) Line 141
mQATool.exe!QA::QAWindow::~QAWindow() Line 24
[External Code]
mQATool.exe!juce::ContainerDeletePolicy<QA::QAWindow>::destroy(QA::QAWindow * object) Line 48
mQATool.exe!juce::ScopedPointer<QA::QAWindow>::operator=(QA::QAWindow * const newObjectToTakePossessionOf) Line 141
mQATool.exe!QA::QAHandler::destroyAllQAWindowsOnJuceMessageThread() Line 347
mQATool.exe!QA::QAHandler::destroyAllQAWindows::__l3::<lambda>() Line 336
[External Code]
mQATool.exe!juce::AsyncFunction::messageCallback() Line 141
mQATool.exe!juce::WindowsMessageHelpers::dispatchMessageFromLParam(long lParam) Line 49
mQATool.exe!juce::MessageManager::dispatchNextMessageOnSystemQueue(bool returnIfNoPendingMessages) Line 110
mQATool.exe!juce::MessageManager::runDispatchLoopUntil(int millisecondsToRunFor) Line 99
mQATool.exe!juce::MessageManager::runDispatchLoop() Line 87
mQATool.exe!juce::JUCEApplicationBase::main() Line 244
mQATool.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) Line 58
mQATool.exe!main(int argc, char * * argv, char * * envp) Line 71
[External Code]
[Frames below may be incorrect and/or missing, no symbols loaded for kernel32.dll]
In your second comment you speak of another assertion: jassert (attachment == nullptr); When and where is this happening? I think it is probably unwise to continue to run your app after you have hit an assertion in the OpenGL render stuff, so which one are you hitting first?
This is an assert at the way bottom of "juce_OpenGLContext.cpp", in the function "void OpenGLContext::copyTexture". I am not really sure why I end up in a function copying a texture, as the textures I am rendering is solely handled by our SDK. Maybe Juce uses something in addition for the rendering (?). Anyway, the code just asserts that the attachment is a nullptr, which it is, and continues running.
I copied the main parts of the function in below. It is "areShadersAvailable()" that returns false, thus I end up in the assert where it says "Running on an old graphics card!", which is clearly not the case (I am running a GeForce GTX 670). The reason why "areShadersAvailable()" returns false is because "getCachedImage()" returns a nullptr inside that function. (As a sidenote I am setting a flag "minGLVersion = 2" in our SDK to enable shaders, but that should not matter. The rendering is in itself ok.)
To be as clear as possible:
a) The assert for old graphics cards is sometimes hit when deleting the window (I said always previously, but that is actually not the case). This assert does not fail and the window is deleted thereafter.
b) When the deletion fails, i.e. when "getCachedImage()" returns a nullptr in "OpenGLContext::getAssociatedObject()", this assertion fails before the old graphics card assertion is hit, so it is here the problem seemingly lies. (I am pretty sure that it hits before the other assert, but that assert isn't always hit, so I am not 100 % certain.)
void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
const Rectangle<int>& anchorPosAndTextureSize,
const int contextWidth, const int contextHeight,
bool flippedVertically)
{
// <snip>
if (areShadersAvailable())
{
// <snip>
}
else
{
jassert(attachment == nullptr); // Running on an old graphics card!
}
JUCE_CHECK_OPENGL_ERROR
}
Additional issue (probably related):
I also randomly ran into another issue which is probably related. When starting a new test run, during the creation of the DocumentWindows, I hit an assert in "juce_OpenGL_win32.h", see below. This assert just checks if the context is active, which it was not at this point. I also copied in the stack trace for this. I have only hit this assert once.
bool setSwapInterval (int numFramesPerSwap)
{
jassert (isActive()); // this can only be called when the context is active..
return wglSwapIntervalEXT != nullptr && wglSwapIntervalEXT (numFramesPerSwap) != FALSE;
}
Stack trace for this assert:
mQATool.exe!juce::OpenGLContext::NativeContext::setSwapInterval(int numFramesPerSwap) Line 97
mQATool.exe!juce::OpenGLContext::CachedImage::initialiseOnThread() Line 392
mQATool.exe!juce::OpenGLContext::CachedImage::run() Line 345
mQATool.exe!juce::Thread::threadEntryPoint() Line 106
mQATool.exe!juce::juce_threadEntryPoint(void * userData) Line 114
mQATool.exe!juce::threadEntryProc(void * userData) Line 103
[External Code]
[Frames below may be incorrect and/or missing, no symbols loaded for msvcr120d.dll]