#pragma once #include "AftrConfig.h" #ifdef AFTR_CONFIG_USE_IMGUI #include "Vector.h" #include "Tex.h" #include struct ImDrawList; //ImGui struct forward declarations struct ImDrawCmd; //ImGui struct forward declarations namespace Aftr { class GLSLShader; class IndexedGeometryQuad; /// Written by Scott Nykl. /// This is a simple class that will draw a Dear ImGui Window that displays a Texture*. The user may /// set the this->dock to any of the following locations and the window will remain docked there upon /// any resize in the main window or window showing the texture. A slider bar will let the user quickly /// adjust the size of the texture for easy viewing. Calling setTexDesiredWidth_maintainAspectRatio() /// will change the texture's size (scale) instantly. /// This class does *not* take ownership of the in passed texture. using shdr_bind_functor = std::function; //Returns the ModelViewProjection matrix for ImGui 2D ortho overlay. This is used by the //AftrImGuiTextureViewerWindow inside the custom renderer //The input param sets the display matrix for the quad's geometry on which the texture is placed. //Defaults to identity, but is helpful if the user wants to "flip the Y" axis by rotating 180 deg //about the Y. Aftr::Mat4 create_ortho_MVP_Matrix_for_ImGui2DOverlay( Mat4 const& displayMatrix_for_quadGeo = Mat4() ); class AftrImGui_TextureViewerWindow { public: /// Setting the dock member to one of the enums will cause the window/texture to remain /// docked to the Upper Left, Upper Right, Lower Left, Lower Right, or Center portion of the screen, respectively. /// None will not perform any manipulation of the image. enum class DOCKING_BEHAVIOR { UL, UR, LL, LR, C, NONE }; AftrImGui_TextureViewerWindow(); AftrImGui_TextureViewerWindow( Tex const& tex, std::string const& label ); ~AftrImGui_TextureViewerWindow(); void drawTex( bool* set_to_false_on_close = nullptr ) const; void drawTex( std::string const& label, bool* set_to_false_on_close = nullptr ) const; void setDock_Behavior( AftrImGui_TextureViewerWindow::DOCKING_BEHAVIOR dock_behavior ) { this->dock = dock_behavior; this->onLayoutChange = true; } void setLabel( std::string const& label ) { this->label = label; } void setFlipYAxis( bool flipY ) { this->flipYAxis = flipY; } /// When a user sets this method, AftrBurner takes over rendering this texture. Internally, /// a simple IndexedGeometryQuad is created, the specified texture is bound upon the quad, /// and the quad is rendered with this shader bound. However, the uniforms, attributes, etc /// for this shader may need to be updated each frame. Meaning the bind() method for the /// shader should be invoked to ensure the state is correct. Since this draw call is buried /// deep inside ImGui and not directly in the normal AftrBurner render loop, we cannot pass /// the bind(...) params normally, so we must type erase the bind behavior via an std::function /// that must be copy-able. void setGLSLShader( std::shared_ptr shdr, shdr_bind_functor const& bindFunc ); //Defaults to using the GLSLShaderDefaultOrthoWOGUI_GL32_GL_REDtoLuminance with a bind //functor appropriate for ImGui texture viewing -- this is the *easiest* way to show grayscale images //instead of pairing an orthographic shader and bind func together. void setGLSLShader_toGrayscaleSingleChannelImage(); // Assumes a typical OpenGL Depth buffer reside in the bound texture. Uses the // GLSLShaderDefaultOrthoWOGUIDepthMapGL32 with a corresponding functor and ImGui ortho Projection settings void setGLSLShader_toGrayscaleSingleChannelDepthBufferImage( float nearPlaneDist, float farPlaneDist ); void setGLSLShader_toRGB8Image(); //Default behavior const GLSLShader* getShader() const noexcept; GLSLShader* getShader() noexcept; void setTex( Tex const& tex ); float get_desired_width() const noexcept { return this->desired_width; } Tex const& getTex() const noexcept { return this->tex; } /// Length is normalized from [0,1]. Upper Left is 0,0. void setTexDesiredWidth_maintainAspectRatio( float normalizedX ); void onResizeWindow( int newWidthPix, int newHeightPix ) { this->onLayoutChange = true; } //Uses the current size of the content of this window (from the last frame) as well as the docking information //to compute the upper left corner for setting the windows location so it will align to the specified DOCK //behavior. May return a std::nullopt if docking behavior is NONE since no new pixel location is required std::optional computePixel_Loc_UL_Corner_from_current_Layout( float window_width_pix, float window_height_pix ) const; //Stores ImGui window data where the window corner's upper left is saved. //To conver to OpenGL coords, use convert_ImGui_WinPos_to_OpenGL_2D_Pixel_coords(...) struct ImGui_WinPos { VectorI lower_left{ 0,0 }; //only uses .x and .y to indicate lower left corner of window for the OpenGL Viewport VectorI size_x_y{ 0,0 }; //only uses .x and .y to indicate horizontal x-length and vertical y-length }; //sample usage: //auto [upper_left, lxly] = convert_ImGui_WinPos_to_OpenGL_2D_Pixel_coords( imgui_win ); //Converts the upper_left corner of ImGui to OpenGL's desired lower_left corner static std::tuple< VectorI, VectorI > convert_ImGui_WinPos_to_OpenGL_2D_Pixel_coords( const ImGui_WinPos& p ) noexcept; //Some textures may not be a simple RGBA8 texture and therefore require their own GLSLShader to be //bound when this image is drawn. In order to do this, we render the texture using AftrBurner and //not ImGui's vertex buffers. As a result, we also create an IndexedGeometry quad that the corresponding //texture is bound upon. struct custom_render_to_quad { std::shared_ptr shdr; std::shared_ptr quad; ImGui_WinPos winPos; }; /// Execution of this is invoked from within ImGui's ImDrawList::AddCallback(...) mechanism. /// This implies some ImGui functions are currently unavailable, for example, ImGui::GetCursorScreenPos(...) /// is invalid and will cause a crash. Instead of querying ImGui state, all needed state should be saved /// inside this instances custom_render_to_quad struct. A user should not ever directly call this method, /// they just say: /// ImGui::GetWindowDrawList()->AddCallback( custom_render_dispatch_func, (void*)this ); /// ... and the custom_render_dispatch_func() will invoke this render method. void renderGL32_IGQuad_with_GLSLShader_while_inside_ImGui_Callback( custom_render_to_quad ) const noexcept; AftrImGui_TextureViewerWindow( const AftrImGui_TextureViewerWindow& toCopy ); AftrImGui_TextureViewerWindow( AftrImGui_TextureViewerWindow&& toMove ) noexcept; AftrImGui_TextureViewerWindow& operator=( const AftrImGui_TextureViewerWindow& toAssign ); AftrImGui_TextureViewerWindow& operator=( AftrImGui_TextureViewerWindow&& toMoveAssign ) noexcept; private: Tex tex; std::string label; mutable bool flipYAxis = false; //turn upside down images rightside up - checkbox state to flip the V (vertical) UV coords mutable DOCKING_BEHAVIOR dock = DOCKING_BEHAVIOR::NONE; //When a custom shader / renderer is used and the tex viewer window is dragged outside of the main //ImGui window, it frequently turns black. The same is true if the window starts outside the main window //(is working), and then is docked inside the main window. Once turned black, the image no longer renders properly. //To work around this issue, sln decided to force the TexViewer window to be its own platform window that //is *not* dockable when a custom shader is being used. That is, when the window is created w/ a custom renderer, //it is created as ImGuiViewportFlags_NoAutoMerge bool drawTex_customRendererInsideMainWindow( std::string const& label, bool* set_to_false_on_close ) const; bool drawTex_customRendererOutsideMainWindow( std::string const& label, bool* set_to_false_on_close ) const; //drawTex() calls either this drawTex func or drawTex_customRenderer(). The key is that the proper flags //are passed to the first invocation of the ImGui Window's ImGui::Begin(...). void drawTex_defaultRenderer( std::string const& label, bool* set_to_false_on_close ) const; //These ImGui widget details are inconsequential w.r.t the texture they visualize, but we want the user //to be able to resize the view of this texture while still being able to draw from a const method. There //these internal variables are qualified as mutable to present the const qualifier of drawTex(). //As long as the Texture (which is const) is not mutated, we should expect good behavior from this class. //In the future if multiple threads start touching these mutable variables (which would be a bad, evil thing //to do) this would not be thread safe. mutable float desired_width = 0.25f; ///< user desired width of the screen [0,1] this texture will occupy mutable float desired_width_cache = desired_width; mutable bool onLayoutChange{ true }; mutable Vector lastFrame_sizeXY_px; mutable VectorI winPos_px; ///< ImGui's pixel location of the window, set immediately before the //custom rendering draw back is called. //eventually right click to context menu to dock to a corner and set horz size shdr_bind_functor custom_renderer_shdr_bind_func; mutable std::optional< custom_render_to_quad > custom_renderer_inMainWindow; //starts as nullopt, members, such as winPos, are updated //prior to scheduling the C-style callback. This could be more elegant, but a lambda or // std::function cannot be stored in a C function pointer, so a capture is not viable here. mutable std::optional< custom_render_to_quad > custom_renderer_outsideMainWindow; //starts as nullopt, members, such as winPos, are updated //Encapsulates the custom render callbacks. This way the callbacks can access private data of the TextureViewerWindow struct detail { static void custom_render_dispatch_func_inMainWindow( const ImDrawList* parent_list, const ImDrawCmd* pcmd ); static void custom_render_dispatch_func_outsideMainWindow( const ImDrawList* parent_list, const ImDrawCmd* pcmd ); }; }; } //namespace Aftr #endif