Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to releaseContext after morph::Visual construction #294

Merged
merged 4 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions docs/ref/visual/visual.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ event queues (for mouse and keyboard input) are managed. This differs
between Mac, Windows and Linux/X windows. On some platforms there is a
common event processing queue, on others there is per-window event
processing. Calling `Visual::poll()`, `Visual::wait()` and
`Visual::waitevents() only on the main thread will ensure
`Visual::waitevents()` only on the main thread will ensure
cross-platform compatibility for your code. There's a useful
discussion on this subject
[here](https://discourse.glfw.org/t/multithreading-glfw/573) (see
Expand All @@ -425,12 +425,37 @@ There are no issues if you want to multi-thread the rest of your
program - the parts that compute the values that will be visualized
with `morph::Visual`.

# OpenGL version and `OWNED_MODE`
# OpenGL context, version and `OWNED_MODE`

`morph::Visual` and `morph::VisualModel` conspire to hide most of the
OpenGL internals away from you, the client coder. However, there *is*
some background knowledge that it's useful to understand.

## OpenGL context

OpenGL has a concept called the 'context'. This refers to the memory
structures created on the CPU and GPU for a given window of your
program that make it possible to draw content. There's generally one
context per window (though it is also possible to create a context
without a window or any graphics).

You program must always 'obtain the correct context' when drawing to a
window. This is essential if your program has two or more windows,
each with its own context.

You won't generally have to worry about the OpenGL context when
working with morphologica, because the functions in `morph::Visual`
and `morph::VisualModel` automatically obtain the correct context
whenever they require it.

Note that the `morph::Visual` constructors will set the OpenGL
context, and then release it when they complete. VisualModel 'Setup'
code such as `VisualModel::finalize` and `VisualModel::addLabel` will
also obtain and then release the context. Other calls (such as the
render calls) that require the context may acquire it when called and
may not release it when they return. It is usually unnecessary to
release the context for one window before setting it for another.

## OpenGL Version

When you program with OpenGL, you have to choose which of the many versions of the library you want to use. morphologica uses 'modern OpenGL' which essentially means that we draw with *GLSL shader programs*. These are C-like programs which are executed by the graphics processing unit with many parallel threads (you don't need to learn GLSL; morphologica provides [default shader programs](https://github.com/ABRG-Models/morphologica/tree/main/shaders)). Different versions of OpenGL provide different supported features in the GLSL and the C function calls that support it. 'Modern OpenGL' started with OpenGL version 3.3, but version 4.1 was chosen for morphologica's default as it is well supported across the Linux, Mac and Windows platforms.
Expand Down
19 changes: 19 additions & 0 deletions examples/hexgrid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,28 @@ int main()
hgv->setScalarData (&data);
hgv->hexVisMode = morph::HexVisMode::HexInterp; // Or morph::HexVisMode::Triangles for a smoother surface plot
hgv->finalize();

if (v.checkContext() == true) {
std::cout << "I have the context after hgv->finalize()\n";
} else {
std::cout << "I don't have the context after hgv->finalize()\n";
}

v.addVisualModel (hgv);

if (v.checkContext() == true) {
std::cout << "I have the context after addVisualModel\n";
} else {
std::cout << "I don't have the context after addVisualModel()\n";
}

v.keepOpen();

if (v.checkContext() == true) {
std::cout << "I have the context after user requested exit\n";
} else {
std::cout << "I don't have the context after user requested exit\n";
}

return 0;
}
44 changes: 42 additions & 2 deletions morph/Visual.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,14 @@ namespace morph {
// but this has to happen BEFORE the call to VisualResources::freetype_init()
this->init_window();

#ifndef OWNED_MODE
this->setContext(); // For freetype_init
#endif
// Now make sure that Freetype is set up
morph::VisualResources<glver>::i().freetype_init (this);
#ifndef OWNED_MODE
this->releaseContext();
#endif
}

//! Take a screenshot of the window. Return vec containing width * height or {-1, -1} on
Expand Down Expand Up @@ -257,6 +263,21 @@ namespace morph {

//! Release the OpenGL context
void releaseContext() { glfwMakeContextCurrent (nullptr); }
// A callback friendly wrapper
static void release_context (morph::Visual<glver>* _v) { _v->releaseContext(); };

/*!
* \brief OpenGL context check
*
* You can see if the OpenGL context is held at any time in your program. This function
* returns true if there is a non-null window and we currently 'have that context'. This
* should return true after a call to Visual::setContext and false after a call to
* Visual::releaseContext.
*/
bool checkContext()
{
return this->window == nullptr ? false : (glfwGetCurrentContext() == this->window);
}
#endif

/*!
Expand All @@ -271,6 +292,7 @@ namespace morph {
model->get_tprog = &morph::Visual<glver>::get_tprog;
#ifndef OWNED_MODE
model->setContext = &morph::Visual<glver>::set_context;
model->releaseContext = &morph::Visual<glver>::release_context;
#endif
}

Expand Down Expand Up @@ -330,6 +352,9 @@ namespace morph {
const morph::vec<float, 3>& _toffset,
const morph::TextFeatures& tfeatures = morph::TextFeatures(0.01f))
{
#ifndef OWNED_MODE
this->setContext(); // For VisualTextModel
#endif
if (this->shaders.tprog == 0) { throw std::runtime_error ("No text shader prog."); }
auto tmup = std::make_unique<morph::VisualTextModel<glver>> (this, this->shaders.tprog, tfeatures);
if (tfeatures.centre_horz == true) {
Expand All @@ -342,6 +367,9 @@ namespace morph {
}
morph::VisualTextModel<glver>* tm = tmup.get();
this->texts.push_back (std::move(tmup));
#ifndef OWNED_MODE
this->releaseContext();
#endif
return tm->getTextGeometry();
}

Expand All @@ -353,6 +381,9 @@ namespace morph {
morph::VisualTextModel<glver>*& tm,
const morph::TextFeatures& tfeatures = morph::TextFeatures(0.01f))
{
#ifndef OWNED_MODE
this->setContext(); // For VisualTextModel
#endif
if (this->shaders.tprog == 0) { throw std::runtime_error ("No text shader prog."); }
auto tmup = std::make_unique<morph::VisualTextModel<glver>> (this, this->shaders.tprog, tfeatures);
if (tfeatures.centre_horz == true) {
Expand All @@ -365,6 +396,9 @@ namespace morph {
}
tm = tmup.get();
this->texts.push_back (std::move(tmup));
#ifndef OWNED_MODE
this->releaseContext();
#endif
return tm->getTextGeometry();
}

Expand Down Expand Up @@ -928,8 +962,6 @@ namespace morph {
glfwSetWindowSizeCallback (this->window, window_size_callback_dispatch);
glfwSetWindowCloseCallback (this->window, window_close_callback_dispatch);
glfwSetScrollCallback (this->window, scroll_callback_dispatch);

glfwMakeContextCurrent (this->window);
#endif
}

Expand All @@ -938,6 +970,10 @@ namespace morph {
// required to render the Visual.
void init_gl()
{
#ifndef OWNED_MODE
this->setContext();
#endif

#ifdef USE_GLEW
glewExperimental = GL_FALSE;
GLenum error = glGetError();
Expand Down Expand Up @@ -1024,6 +1060,10 @@ namespace morph {
morph::VisualFont::DVSans,
0.035f, 64, morph::vec<float, 3>({0.0f, 0.0f, 0.0f}),
this->title);
#ifndef OWNED_MODE
// Release context after init_gl, meaning after constructor context is released.
this->releaseContext();
#endif
}

private:
Expand Down
20 changes: 19 additions & 1 deletion morph/VisualModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,13 @@ namespace morph {
if (this->setContext != nullptr) { this->setContext (this->parentVis); }
this->initializeVertices();
this->postVertexInitRequired = true;
// Release context after creating and finalizing this VisualModel. On Visual::render(),
// context will be re-acquired.
if (this->releaseContext != nullptr) { this->releaseContext (this->parentVis); }
}

//! Render the VisualModel
//! Render the VisualModel. Note that it is assumed that the OpenGL context has been
//! obtained by the parent Visual::render call.
virtual void render()
{
if (this->hide == true) { return; }
Expand Down Expand Up @@ -331,6 +335,8 @@ namespace morph {
throw std::runtime_error ("No text shader prog. Did your VisualModel-derived class set it up?");
}

if (this->setContext != nullptr) { this->setContext (this->parentVis); } // For VisualTextModel

auto tmup = std::make_unique<morph::VisualTextModel<glver>> (this->parentVis, this->get_shaderprogs(this->parentVis).tprog, tfeatures);

if (tfeatures.centre_horz == true) {
Expand All @@ -343,6 +349,10 @@ namespace morph {
}

this->texts.push_back (std::move(tmup));

// As this is a setup function, release the context
if (this->releaseContext != nullptr) { this->releaseContext (this->parentVis); }

return this->texts.back()->getTextGeometry();
}

Expand All @@ -360,6 +370,8 @@ namespace morph {
throw std::runtime_error ("No text shader prog. Did your VisualModel-derived class set it up?");
}

if (this->setContext != nullptr) { this->setContext (this->parentVis); } // For VisualTextModel

auto tmup = std::make_unique<morph::VisualTextModel<glver>> (this->parentVis, this->get_shaderprogs(this->parentVis).tprog, tfeatures);

if (tfeatures.centre_horz == true) {
Expand All @@ -373,6 +385,10 @@ namespace morph {

this->texts.push_back (std::move(tmup));
tm = this->texts.back().get();

// As this is a setup function, release the context
if (this->releaseContext != nullptr) { this->releaseContext (this->parentVis); }

return this->texts.back()->getTextGeometry();
}

Expand Down Expand Up @@ -671,6 +687,8 @@ namespace morph {
std::function<GLuint(morph::Visual<glver>*)> get_tprog;
//! Set OpenGL context. Should call parentVis->setContext(). Can be nullptr (if in OWNED_MODE).
std::function<void(morph::Visual<glver>*)> setContext;
//! Release OpenGL context. Should call parentVis->releaseContext(). Can be nullptr (if in OWNED_MODE).
std::function<void(morph::Visual<glver>*)> releaseContext;

//! Setter for the parent pointer, parentVis
void set_parent (morph::Visual<glver>* _vis)
Expand Down
Loading