MagnetarX10 Posted January 14, 2023 Share Posted January 14, 2023 I am new to Ultra Engine and never used Leadworks and have only a little bit of 3D programming experience. I looked as some youtube examples and read what I think are the relevant portions of the programming reference, but I am not 100% sure what to do. I am trying to build something similar to the Editor interface I saw in the Gallery. I want a main window that has a left panel and a right panel with UI controls and a center panel that renders a 3D viewport. I have created the Window, FrameBuffer, World, and Interface Objects. I have a Left and Right Panel on the Interface. I setup 2 cameras, one for the 2D and one for the 3D Objects. I turned off the real-time rendering for the 2D camera. I am sure this is simple, I don't know how to setup the 3D world for only that middle space inside the main window. Currently, the 3D world draws over the 2D UI and I get a lot of flashing. Quote Link to comment Share on other sites More sharing options...
Josh Posted January 14, 2023 Share Posted January 14, 2023 This is definitely doable. Basically, you create a child window with no titlebar and manually set its shape any time the parent window moves or resizes. To make a resizable window with a framebuffer, you also need to call AsyncRender(false) at the very beginning of your program: https://www.ultraengine.com/learn/AsyncRender?lang=cpp This will make it so the rendering all takes place inside the World::Render() call, instead of running on a separate thread. This mode is for event-driven programs instead of games that are constantly rendering. I'll put together an example for you tomorrow, but if you feel like taking a stab at it before then, look at the second example on this page: https://github.com/UltraEngine/Documentation/blob/master/CPP/OpenGL.md That is an old example using OpenGL with Ultra App Kit, but it's very similar to setting up a framebuffer in Ultra Engine. I'll get a new example up tomorrow. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
reepblue Posted January 14, 2023 Share Posted January 14, 2023 Here's an example on how to create a fully functioning application with a 3D viewport. #include "UltraEngine.h" using namespace UltraEngine; const int TOOLBARHEIGHT = 48; const int STATUSBARHEIGHT = 32; const int SIDEPANELWIDTH = 300; const int CONSOLEHEIGHT = 120; int main(int argc, const char* argv[]) { AsyncRender(false); //Get displays auto displays = GetDisplays(); //Create window auto mainwindow = CreateWindow("3D Viewport", 0, 0, 1024, 768, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR | WINDOW_RESIZABLE); mainwindow->SetMinSize(800, 600); //Create user interface auto ui = CreateInterface(mainwindow); iVec2 sz = ui->root->ClientSize(); //------------------------------------------------------- // Create main menu //------------------------------------------------------- auto mainmenu = CreateMenu("", ui->root); //File menu auto menu_file = CreateMenu("File", mainmenu); CreateMenu("New", menu_file); CreateMenu("", menu_file); auto menu_open = CreateMenu("Open", menu_file); auto menu_save = CreateMenu("Save", menu_file); auto menu_saveas = CreateMenu("Save as...", menu_file); CreateMenu("", menu_file); auto menu_recentfiles = CreateMenu("Recent files", menu_file); std::array<shared_ptr<Widget>, 10> menu_recentfile; for (int n = 0; n < menu_recentfile.size(); ++n) { menu_recentfile[n] = CreateMenu("Recent file " + String(n + 1), menu_recentfiles); } CreateMenu("", menu_file); auto menu_exit = CreateMenu("Exit", menu_file); //Edit menu auto menu_edit = CreateMenu("Edit", mainmenu); CreateMenu("Undo", menu_edit); CreateMenu("Redo", menu_edit); CreateMenu("", menu_edit); CreateMenu("Cut", menu_edit); CreateMenu("Copy", menu_edit); CreateMenu("Past", menu_edit); CreateMenu("", menu_edit); CreateMenu("Select all", menu_edit); CreateMenu("Select none", menu_edit); CreateMenu("Invert selection", menu_edit); //View menu auto menu_view = CreateMenu("View", mainmenu); auto menu_perspective = CreateMenu("Perspective", menu_view); auto menu_top = CreateMenu("XZ - Top", menu_view); auto menu_side = CreateMenu("XZ - Side", menu_view); auto menu_front = CreateMenu("XY - Front", menu_view); menu_perspective->SetState(true); //Tools menu auto menu_tools = CreateMenu("Tools", mainmenu); auto menu_options = CreateMenu("Options", menu_tools); //Help menu auto menu_help = CreateMenu("Help", mainmenu); auto menu_helpcontents = CreateMenu("Help Contents", menu_help); auto menu_about = CreateMenu("About", menu_help); //------------------------------------------------------- // Create toolbar //------------------------------------------------------- auto toolbar = CreatePanel(0, mainmenu->size.y, sz.x, TOOLBARHEIGHT, ui->root); toolbar->SetLayout(1, 1, 1, 0); int x = 4, y = 4; auto toolbarbutton_open = CreateButton("", x, y, TOOLBARHEIGHT - 8, TOOLBARHEIGHT - 8, toolbar, BUTTON_TOOLBAR); toolbarbutton_open->SetFontScale(2); toolbarbutton_open->SetIcon(LoadIcon("https://raw.githubusercontent.com/UltraEngine/Documentation/master/Assets/Icons/open.svg")); x += TOOLBARHEIGHT; auto toolbarbutton_save = CreateButton("", x, y, TOOLBARHEIGHT - 8, TOOLBARHEIGHT - 8, toolbar, BUTTON_TOOLBAR); toolbarbutton_save->SetFontScale(2); toolbarbutton_save->SetIcon(LoadIcon("https://raw.githubusercontent.com/UltraEngine/Documentation/master/Assets/Icons/save.svg")); x += TOOLBARHEIGHT; auto toolbarbutton_options = CreateButton("", x, y, TOOLBARHEIGHT - 8, TOOLBARHEIGHT - 8, toolbar, BUTTON_TOOLBAR); toolbarbutton_options->SetFontScale(2); toolbarbutton_options->SetIcon(LoadIcon("https://raw.githubusercontent.com/UltraEngine/Documentation/master/Assets/Icons/settings.svg")); x += TOOLBARHEIGHT; auto toolbarbutton_help = CreateButton("", x, y, TOOLBARHEIGHT - 8, TOOLBARHEIGHT - 8, toolbar, BUTTON_TOOLBAR); toolbarbutton_help->SetIcon(LoadIcon("https://raw.githubusercontent.com/UltraEngine/Documentation/master/Assets/Icons/help.svg")); toolbarbutton_help->SetFontScale(2); //------------------------------------------------------- // Create status bar //------------------------------------------------------- auto statusbar = CreatePanel(0, sz.y - STATUSBARHEIGHT, sz.x, STATUSBARHEIGHT, ui->root); statusbar->SetLayout(1, 1, 0, 1); auto statusbarlabel_view = CreateLabel("Perspective", 4, 0, 300, statusbar->size.y, statusbar, LABEL_LEFT | LABEL_MIDDLE); //------------------------------------------------------- // Create main panel //------------------------------------------------------- auto mainpanel = CreatePanel(0, toolbar->position.y + toolbar->size.y, sz.x, sz.y - toolbar->size.y - toolbar->position.y - statusbar->size.y, ui->root); mainpanel->SetLayout(1, 1, 1, 1); sz = mainpanel->ClientSize(); //Create console auto console = CreateTextArea(4, mainpanel->size.y - CONSOLEHEIGHT, mainpanel->size.x - SIDEPANELWIDTH - 8, CONSOLEHEIGHT - 28 - 4, mainpanel); console->SetLayout(1, 1, 0, 1); auto widget_input = CreateTextField(4, mainpanel->size.y - 28, mainpanel->size.x - SIDEPANELWIDTH - 8, 28, mainpanel, TEXTFIELD_ENTERKEYACTIONEVENT); widget_input->SetLayout(1, 1, 0, 1); //Main viewport auto mainviewport = CreatePanel(4, 4, mainpanel->size.x - SIDEPANELWIDTH - 8, mainpanel->size.y - 8 - CONSOLEHEIGHT, mainpanel, PANEL_BORDER); mainviewport->SetLayout(1, 1, 1, 1); mainviewport->SetColor(0, 0, 0); auto viewport = CreateWindow("", mainviewport->GetPosition(true).x, mainviewport->GetPosition(true).y, mainviewport->GetSize().x, mainviewport->GetSize().y, mainwindow, WINDOW_CHILD | WINDOW_CLIENTCOORDS | WINDOW_RESIZABLE); //Create a world auto world = CreateWorld(); //Create a framebuffer auto framebuffer = CreateFramebuffer(viewport); //Create a camera auto camera = CreateCamera(world); camera->SetClearColor(0.125); camera->SetFov(70); camera->SetPosition(0, 0, -3); //Create a light auto light = CreateDirectionalLight(world); light->SetRotation(35, 45, 0); //Create a box auto box = CreateBox(world); //------------------------------------------------------- // Create side panel //------------------------------------------------------- auto sidepanel = CreatePanel(sz.x - SIDEPANELWIDTH, 0, SIDEPANELWIDTH, sz.y, mainpanel); sidepanel->SetLayout(0, 1, 1, 1); auto tabber = CreateTabber(0, 0, SIDEPANELWIDTH, sz.y, sidepanel); tabber->SetLayout(1, 1, 1, 1); tabber->AddItem("Objects", true); tabber->AddItem("Scene"); //Object panel sz = tabber->ClientSize(); auto objectpanel = CreatePanel(0, 0, sz.x, sz.y, tabber); objectpanel->SetLayout(1, 1, 1, 1); //tabber->items[0].extra = objectpanel; //Scene panel auto scenepanel = CreatePanel(0, 0, sz.x, sz.y, tabber); scenepanel->SetHidden(true); scenepanel->SetLayout(1, 1, 1, 1); //tabber->items[1].extra = scenepanel; x = 8; y = 12; CreateLabel("Category:", x, y, 200, 30, objectpanel); y += 24; auto objectcategorybox = CreateComboBox(x, y, sz.x - x * 2, 30, objectpanel); objectcategorybox->SetLayout(1, 1, 1, 0); objectcategorybox->AddItem("Primitives", true); objectcategorybox->AddItem("Extended primitives"); objectcategorybox->AddItem("Cameras"); objectcategorybox->AddItem("Lights"); objectcategorybox->AddItem("Splines"); y += 44; CreateLabel("Object:", x, y, 200, 30, objectpanel); y += 24; auto objectbox = CreateComboBox(x, y, sz.x - x * 2, 30, objectpanel); objectbox->SetLayout(1, 1, 1, 0); objectbox->AddItem("Box", true); objectbox->AddItem("Wedge"); objectbox->AddItem("Cylinder"); objectbox->AddItem("Sphere"); y += 44; x = 80; CreateButton("Create", x, y, sz.x - 2 * x, 28, objectpanel); x = 8; y = 12; auto scenebrowser = CreateTreeView(x, y, sz.x - 2 * x, 400 - y, scenepanel); scenebrowser->SetLayout(1, 1, 1, 1); auto node = scenebrowser->root->AddNode("Scene"); node->Expand(); node->AddNode("Box 1"); node->AddNode("Box 2"); node->AddNode("Box 3"); y += scenebrowser->size.y + x; auto propertiespanel = CreatePanel(x, y, sz.x, sz.y - y, scenepanel); propertiespanel->SetLayout(1, 1, 0, 1); y = 8; CreateLabel("Name:", x, y + 4, 60, 30, propertiespanel); auto widget_name = CreateTextField(x * 2 + 60, y, sz.x - 4 * x - 60, 30, propertiespanel); widget_name->SetText("Box 1"); y += 40; CreateLabel("Value:", x, y + 4, 60, 30, propertiespanel); CreateSlider(x * 2 + 60, y, sz.x - 4 * x - 60, 30, propertiespanel, SLIDER_HORIZONTAL | SLIDER_TRACKBAR); y += 40; //------------------------------------------------------- // Options window //------------------------------------------------------- auto optionswindow = CreateWindow("Options", 0, 0, 400, 500, mainwindow, WINDOW_HIDDEN | WINDOW_TITLEBAR | WINDOW_CENTER); auto optionsui = CreateInterface(optionswindow); sz = optionsui->root->ClientSize(); auto button_option1 = CreateButton("Option 1", 12, 12, 300, 30, optionsui->root, BUTTON_CHECKBOX); button_option1->SetState(WIDGETSTATE_SELECTED); auto button_option2 = CreateButton("Option 2", 12, 12 + 32, 300, 30, optionsui->root, BUTTON_RADIO); button_option2->SetState(WIDGETSTATE_SELECTED); auto button_option3 = CreateButton("Option 3", 12, 12 + 32 * 2, 300, 30, optionsui->root, BUTTON_RADIO); auto button_applyoptions = CreateButton("OK", sz.x - 2 * (8 + 80), sz.y - 8 - 30, 80, 30, optionsui->root, BUTTON_OK); auto button_closeoptions = CreateButton("Cancel", sz.x - 8 - 80, sz.y - 8 - 30, 80, 30, optionsui->root, BUTTON_CANCEL); //------------------------------------------------------- // Main loop //------------------------------------------------------- while (true) { while (PeekEvent()) { const Event event = WaitEvent(); switch (event.id) { case EVENT_WINDOWSIZE: if (event.source == mainwindow) { // If the window resize event is captured auto window = event.source->As<UltraEngine::Window>(); // Get the new size of the applications window UltraEngine::iVec2 sz = mainwindow->ClientSize(); // Set the position and size of the viewport window viewport->SetShape(mainviewport->GetPosition(true).x, mainviewport->GetPosition(true).y, mainviewport->GetSize().x, mainviewport->GetSize().y); } break; case EVENT_PRINT: console->AddText(event.text + "\n"); break; case EVENT_WIDGETACTION: if (event.source == menu_exit) { EmitEvent(EVENT_WINDOWCLOSE, mainwindow); } else if (event.source == menu_open) { RequestFile("Open File"); } else if (event.source == menu_save or event.source == menu_saveas) { RequestFile("Save File", "", "All Files", 0, true); } else if (event.source == menu_helpcontents) { RunFile("https://www.ultraengine.com/learn"); } else if (event.source == menu_about) { Notify("3D Viewport"); } else if (event.source == menu_perspective or event.source == menu_top or event.source == menu_side or event.source == menu_front) { menu_perspective->SetState(WIDGETSTATE_UNSELECTED); menu_top->SetState(WIDGETSTATE_UNSELECTED); menu_side->SetState(WIDGETSTATE_UNSELECTED); menu_front->SetState(WIDGETSTATE_UNSELECTED); auto menuitem = event.source->As<Widget>(); menuitem->SetState(WIDGETSTATE_SELECTED); statusbarlabel_view->SetText(menuitem->text); } else if (event.source == toolbarbutton_open) { EmitEvent(EVENT_WIDGETACTION, menu_open); } else if (event.source == toolbarbutton_save) { EmitEvent(EVENT_WIDGETACTION, menu_save); } else if (event.source == toolbarbutton_options) { EmitEvent(EVENT_WIDGETACTION, menu_options); } else if (event.source == toolbarbutton_help) { EmitEvent(EVENT_WIDGETACTION, menu_helpcontents); } else if (event.source == widget_input) { if (!widget_input->text.empty()) { console->AddText(widget_input->text + "\n"); widget_input->SetText(""); } widget_input->Activate(); } else if (event.source == menu_options) { optionswindow->SetHidden(false); optionswindow->Activate(); mainwindow->Disable(); } else if (event.source == button_applyoptions or event.source == button_closeoptions) { EmitEvent(EVENT_WINDOWCLOSE, optionswindow); } break; case EVENT_WIDGETSELECT: if (event.source == tabber) { for (int n = 0; n < tabber->items.size(); ++n) { if (n == event.data) { objectpanel->SetHidden(true); scenepanel->SetHidden(false); } else { objectpanel->SetHidden(false); scenepanel->SetHidden(true); } } } break; case EVENT_WINDOWCLOSE: if (event.source == mainwindow) { if (Confirm("Are you sure you want to quit?")) { return 0; } } else if (event.source == optionswindow) { mainwindow->Enable(); mainwindow->Activate(); optionswindow->SetHidden(true); } } } box->Turn(0, 45 / 60.0f, 0); world->Update(); world->Render(framebuffer); } return 0; } 1 1 Quote Cyclone - Ultra Game System - Component Preprocessor - Tex2TGA - Darkness Awaits Template (Leadwerks) If you like my work, consider supporting me on Patreon! Link to comment Share on other sites More sharing options...
MagnetarX10 Posted January 14, 2023 Author Share Posted January 14, 2023 @reepblue Thanks! This is very, very helpful. I had worked on this a bit and got it largely working based on @Josh's info. The nice thing about using the ListenEvent() callback is that the 3D Scene will remain visible while the Application Window is resized. I also called Update() to keep it rotating, but I am not sure if that is a bad idea. I added some buttons to change the rotation of the cube, but I do not understand is why it can take a few clicks to get the buttons to work. I assume there is a better way to get the button click or I need to select it based on mouse over? Also, it has been a while since I have programmed C++ ( over a decade), so I don't remember if there is a way to cast the "shared_ptr<Object> extra" parameter in the ListenEvent() callback to a struct or class of objects instead of the single pointer so I don't need to setup the panels and world objects as globals. #include "UltraEngine.h" #include "ComponentSystem.h" using namespace UltraEngine; const int WIN_WIDTH = 1000; const int WIN_HEIGHT = 1000; const int PANEL_WIDTH = 200; const int PADDING = 10; //global "cheats" to pass more data into the resize callback shared_ptr<Window> viewport; shared_ptr<World> world; shared_ptr<Framebuffer> framebuffer; shared_ptr<Widget> left_panel; shared_ptr<Widget> right_panel; //------------------------------------------------------- // Callback to handle dynamic resizing of the Viewport // and panels //------------------------------------------------------- bool ResizeViewport(const Event& ev, shared_ptr<Object> extra) { // If the window resize event is captured auto window = ev.source->As<Window>(); // Get the new size of the applications window iVec2 win_sz = window->ClientSize(); auto viewport = extra->As<Window>(); // Set the position and size of the viewport window AND the panels viewport->SetShape(PANEL_WIDTH + PADDING, PADDING, win_sz.x - (PANEL_WIDTH*2) - (PADDING * 2), win_sz.y - PADDING); if (left_panel != NULL) left_panel->SetShape(0, 0, PANEL_WIDTH, win_sz.y - PADDING); if (right_panel != NULL) right_panel->SetShape(win_sz.x - PANEL_WIDTH, 0, PANEL_WIDTH, win_sz.y - PADDING); //Render the Viewport to keep the 3D scene drawing during the resize world->Update(); world->Render(framebuffer); return true; } int main(int argc, const char* argv[]) { //Disable multithreaded rendering for RESIZABLE Windows AsyncRender(false); auto displays = GetDisplays(); auto window = CreateWindow("Ultra Engine Test", 0, 0, WIN_WIDTH, WIN_HEIGHT, displays[0], WINDOW_TITLEBAR | WINDOW_RESIZABLE); auto font = LoadFont("Fonts/Arial.ttf"); //------------------------------------------------------- // Create 2D User Interface //------------------------------------------------------- auto ui = CreateInterface(window); iVec2 win_sz = ui->root->ClientSize(); left_panel = CreatePanel(0, 0, PANEL_WIDTH, win_sz.y - PADDING, ui->root); left_panel->SetColor(0.20f, 0.20f, 0.20f, 1); left_panel->SetLayout(1, 1, 1, 1); right_panel = CreatePanel(win_sz.x - PANEL_WIDTH, 0, PANEL_WIDTH, win_sz.y - PADDING, ui->root); right_panel->SetColor(0.20f, 0.20f, 0.20f, 1); right_panel->SetLayout(1, 1, 1, 1); //Add Buttons iVec2 left_sz = left_panel->ClientSize(); iVec2 right_sz = right_panel->ClientSize(); auto rotate_left_btn = CreateButton("Rotate Left", left_sz.x / 2 - 75, left_sz.y / 2 - 15, 150, 30, left_panel); auto rotate_right_btn = CreateButton("Rotate Right", right_sz.x / 2 - 75, right_sz.y / 2 - 15, 150, 30, right_panel); //------------------------------------------------------- // Create 3D Viewport //------------------------------------------------------- viewport = CreateWindow("", PANEL_WIDTH + PADDING, PADDING, win_sz.x - (PANEL_WIDTH*2) - (PADDING*2), win_sz.y - PADDING, window, WINDOW_CHILD); framebuffer = CreateFramebuffer(viewport); world = CreateWorld(); // Adjust the size of the viewport when the applications window is resized (this will callback to the ResizeViewport() function) ListenEvent(EVENT_WINDOWSIZE, window, ResizeViewport, viewport); //------------------------------------------------------- // Setup 3D Scene //------------------------------------------------------- //Setup Camera auto camera_3D = CreateCamera(world); camera_3D->SetClearColor(0.125); camera_3D->SetFov(50); camera_3D->SetPosition(0, 0, -3); //Create a light auto light = CreateBoxLight(world); light->SetRotation(35, 45, 0); light->SetRange(-10, 10); //Create a box auto box = CreateBox(world); box->SetColor(0, 0, 1); //Entity component system auto actor = CreateActor(box); auto component = actor->AddComponent<Mover>(); //Rotate the Box component->rotation.y = 45; //------------------------------------------------------- // Main Loop //------------------------------------------------------- while (true) { while (PeekEvent()) { const Event ev = WaitEvent(); switch (ev.id) { case EVENT_WIDGETACTION: //Set the rotation based on the button click if (ev.source == rotate_right_btn) component->rotation.y = -45; else if (ev.source == rotate_left_btn) component->rotation.y = +45; break; case EVENT_WINDOWCLOSE: if (ev.source == window) return 0; break; default: ui->ProcessEvent(ev); break; } } world->Update(); world->Render(framebuffer); } return 0; } Quote Link to comment Share on other sites More sharing options...
MagnetarX10 Posted January 15, 2023 Author Share Posted January 15, 2023 @reepblue I looked at your code again. I am trying to figure out why it does not work the same as the ListenEvent() callback version I have. I assume that WaitEvent() returns an EVENT_WINDOWSIZE event on a different frequency than the ListenEvent() gets called. Your loop drops down to the Update() and Render() calls which seems like it should be functionally equivalent, but I see a dark window during the resize in your version. This means there is a difference. It has been a very long time since I did Windows programming but maybe the WaitEvent() triggers in the messages that signal a begin and end to the sizing rather than getting message for WM_SIZING which (I think) happens continuously as the user is resizing the window. Quote Link to comment Share on other sites More sharing options...
Josh Posted January 15, 2023 Share Posted January 15, 2023 Okee-dokee, I have updated the documentation with another example that shows how to create an event-driven desktop application with a 3D viewport embedded in the interface:. See the third example here: https://www.ultraengine.com/learn/CreateInterface?lang=cpp Modal Loops ➰ When a window is resized, the system goes into a loop and doesn't come out of that loop until the mouse is released. This is a "blocking" or modal loop. Windows, Linux, and Mac all work like this. It's an OS-level thing, and I think it must be done to make window resizing as snappy and responsive as possible. If you are using ListenEvent you will intercept WINDOWSIZE events during the loop, but if you are using WaitEvent() you won't see anything until the mouse is let go. Therefore, in order to update the viewport window as the window is being resized, you must use an event listener. When to Render ⏰ In my OpenGL example linked to above I redraw the viewport every time a WINDOWSIZE event occurs, inside the system loop. This is probably not a good idea with Vulkan, because every time the viewport changes size it has to allocate a new color and depth buffer. (OpenGL might also do this, but if so it is hidden from the user.) It's better to resize the viewport dynamically, but only re-render the scene once the resize operation is done, the modal loop exits, and the program execution goes back to the main loop (when the WaitEvent call finishes). If you just resize the viewport without rendering again, the appearance will vary depending on the driver / manufacturer of your GPU. Nvidia drivers will stretch the image and Intel drivers will leave the image as-is, with a "hall of mirrors" effect if the window is made bigger. Either of these behaviors is fine. Minimizing Renders 📷 When you do resize a window, you will see a bunch of WINDOWPAINT events coming out of WaitEvent(), one for each mouse movement you made. If you just render the viewport every time a WINDOWPAINT event occurs, it will make your program unresponsive for a moment after you resize the window, because it has to render the viewport 100 times for no good reason. To prevent this from happening, the example keeps track of the viewport state with the "dirty" variable. If a WINDOWPAINT event occurs and the viewport has not been invalidated yet, the viewport is invalidated (the "dirty" variable is set to true) and a custom VIEWPORTRENDER event is emitted. Since this event goes to the end of the event queue, it will be evaluated last, after all the remaining WINDOWPAINT events are skipped. The result is the viewport will only render once each time the window is resized. When you run the example, the printed output will show exactly what is happening. A Bug! 🐛 🤯 Finally, this example is producing a crash on exit, so I will take a closer look today and get that fixed for you. Fix is available on branch 1.0.1! 2 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Josh Posted January 15, 2023 Share Posted January 15, 2023 9 hours ago, MagnetarX10 said: Also, it has been a while since I have programmed C++ ( over a decade), so I don't remember if there is a way to cast the "shared_ptr<Object> extra" parameter in the ListenEvent() callback to a struct or class of objects instead of the single pointer so I don't need to setup the panels and world objects as globals. You can create a new class derived from the object class like this: class Viewport : public Object { public: shared_ptr<Panel> panel; shared_ptr<Window> window; bool dirty; Viewport() { dirty = false; } }; auto viewport = make_shared<Viewport>(); This class can be cast to an Object, so it can be supplied in the extra parameter of ListenEvent(). Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
MagnetarX10 Posted January 16, 2023 Author Share Posted January 16, 2023 @Josh Thanks. On my machine (RTX3080, Windows 11 Pro, AMD CPU), every once in a while the viewport does not paint. I think it is writing a ViewPort Render, but the ViewPort is blank. Tends to happen when I am resizing or moving the window. I have not figure out exactly what causes it. Quote Link to comment Share on other sites More sharing options...
Josh Posted January 16, 2023 Share Posted January 16, 2023 I'm trying to produce an error like this, but it's completely stable on my Nvidia card. Is there any reliable way to make it happen? Do you have version 1.0.1 of the engine installed? It will show the version you're on in the Updates tab in the client app. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
MagnetarX10 Posted January 17, 2023 Author Share Posted January 17, 2023 Here is a video. I think it is when I click outside of the Viewport region. I think I was doing this accidently initially and did not notice. My assumption is that the viewport is getting invalidated but it does not seem to be sent a render message. I added a little bit of code to track which render event was being sent so I could easily tell when a new render message we sent: case EVENT_VIEWPORTRENDER: world->Render(framebuffer); dirty = false; std::stringstream sstm; sstm << "Viewport render (count=" << ++renderCount << ")"; Print(sstm.str()); break; Quote Link to comment Share on other sites More sharing options...
Josh Posted January 17, 2023 Share Posted January 17, 2023 I found an issue related to window refresh and resizing...still working on it... Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Josh Posted January 18, 2023 Share Posted January 18, 2023 An update for 1.0.1 is available now. There were some wrong window size events being emitted that might have caused your problem. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
MagnetarX10 Posted January 18, 2023 Author Share Posted January 18, 2023 I may have done this incorrectly, but I updated my Ultra Engine Client to 1.0.1. Then I created a new project. Then I copied and pasted the code from example 3 in Create Instance into main.cpp and rebuilt the project, but I am getting the same behavior as before. I tried it twice and restarted the Ultra Engine Client in case that is a requirement after an update. Quote Link to comment Share on other sites More sharing options...
Josh Posted January 18, 2023 Share Posted January 18, 2023 You don't need to restart the client. On the Updates panel it shows the version installed. Does yours say 1.0.1?' Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
MagnetarX10 Posted January 18, 2023 Author Share Posted January 18, 2023 Yes. Quote Link to comment Share on other sites More sharing options...
Josh Posted January 18, 2023 Share Posted January 18, 2023 I did the same, and compiled it for you, just to make sure there's no problem with you receiving updates: windowtest.zip The behavior should look like this: 1 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
MagnetarX10 Posted January 18, 2023 Author Share Posted January 18, 2023 I have the same issue with your EXE. Video link: https://drive.google.com/file/d/1VxprAr7jaH9Ne5sWD-Ecmea1TBPF3Rp4/view?usp=share_link Something about my system, I guess. I could have some weird version issue with drivers or libs. I have two monitors. One is a wide screen monitor. I have seen other issues in the past that were particular to a wide monitor, but this happens on both of my screens. Not sure if any of this information is useful, but here it is: Quote Link to comment Share on other sites More sharing options...
Drew_Benton Posted January 19, 2023 Share Posted January 19, 2023 @reepblue's code seems to work great, because it avoids trying to solve the issue of "when to re-render". I think this is the preferred way to go about an event driven system like this on Windows. 7 hours ago, MagnetarX10 said: I may have done this incorrectly, but I updated my Ultra Engine Client to 1.0.1. Then I created a new project. Then I copied and pasted the code from example 3 in Create Instance into main.cpp and rebuilt the project, but I am getting the same behavior as before. I tried it twice and restarted the Ultra Engine Client in case that is a requirement after an update. I feel the Example 3 code is missing a lot of common render invalidation events (conceptually speaking), which are causing the issues you are seeing. I ran the example and can reproduce the exact issues you are having, so I think it's code related and not hardware/driver related. For reference, I have a RTX 3080 TI, also using 527.56 drivers running 2x 4k monitors. For example, on WIndows, I feel you should dirty the render view to cause a redraw when: The app is moved The app is resized When the parent window repaints Consider the following modified Example 3: #include "UltraEngine.h" using namespace UltraEngine; const int SidePanelWidth = 200; const int Indent = 8; // Callback function for resizing the viewport bool ResizeViewport(const Event& ev, shared_ptr<Object> extra) { // If the window resize event is captured auto window = ev.source->As<Window>(); // Get the new size of the applications window iVec2 sz = window->ClientSize(); auto viewport = extra->As<Window>(); // Set the position and size of the viewport window viewport->SetShape(SidePanelWidth, Indent, sz.x - SidePanelWidth - Indent, sz.y - Indent * 2); return true; } //Custom event ID const EventId EVENT_VIEWPORTRENDER = EventId(101); int main(int argc, const char* argv[]) { // Disable asynchronous rendering so window resizing will work with 3D graphics AsyncRender(false); // Get the available displays auto displays = GetDisplays(); // Create a window auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR | WINDOW_RESIZABLE); // Create user interface auto ui = CreateInterface(window); // Get the size of the user interface iVec2 sz = ui->background->ClientSize(); // Create a treeview widget auto treeview = CreateTreeView(Indent, Indent, SidePanelWidth - Indent * 2, sz.y - Indent * 2, ui->root); // Anchor left, top and bottom of treeview widget treeview->SetLayout(1, 0, 1, 1); // Add nodes to the treeview widget treeview->root->AddNode("Object 1"); treeview->root->AddNode("Object 2"); treeview->root->AddNode("Object 3"); // Create a viewport window auto viewport = CreateWindow("", SidePanelWidth, Indent, sz.x - SidePanelWidth - Indent, sz.y - Indent * 2, window, WINDOW_CHILD); // Adjust the size of the viewport when the applications window is resized (this will callback to our ResizeViewport() function) ListenEvent(EVENT_WINDOWSIZE, window, ResizeViewport, viewport); // Create a framebuffer auto framebuffer = CreateFramebuffer(viewport); // Create a world auto world = CreateWorld(); // Create a camera auto camera = CreateCamera(world); camera->SetClearColor(0.125); camera->SetPosition(0, 0, -4); // Create a light auto light = CreateBoxLight(world); light->SetRotation(35, 45, 0); light->SetRange(-10, 10); // Create a model auto model = CreateSphere(world); model->SetColor(0, 0, 1); // This varialble will be used for viewport refreshing bool dirty = false; // Main loop while (true) { // Wait for event const Event ev = WaitEvent(); // Evaluate event switch (ev.id) { case EVENT_WINDOWMOVE: Print("Window move"); if (not dirty) { dirty = true; EmitEvent(EVENT_VIEWPORTRENDER, viewport); Print("viewport refresh"); } break; case EVENT_WINDOWSIZE: Print("Window size"); if (not dirty) { dirty = true; EmitEvent(EVENT_VIEWPORTRENDER, viewport); Print("viewport refresh"); } break; //Close window when escape key is pressed case EVENT_KEYDOWN: if (ev.source == window and ev.data == KEY_ESCAPE) return 0; break; case EVENT_WINDOWCLOSE: if (ev.source == window) return 0; break; case EVENT_WINDOWPAINT: //if (ev.source == viewport) { Print("Window paint"); if (not dirty) { // This prevents excessive paint events from building up, especially during window resizing // This event is added to the end of the event queue, so if a lot of paint events build up, it will // only cause a single render to be performed. dirty = true; EmitEvent(EVENT_VIEWPORTRENDER, viewport); Print("viewport refresh"); } } break; case EVENT_VIEWPORTRENDER: world->Render(framebuffer); dirty = false; Print("Viewport render"); break; } } return 0; } This should solve the recent issues you've posted about because: If you move the Windows offscreen and back on, that counts as a move, which triggers a render, ensuring the viewport doesn't stay invalidated from being offscreen. When you resize the window, the render view gets invalided as expected during the resize itself, but it will redraw once you complete the resize. When you click in the empty space between the two windows, the parent window gets a paint message in such a way the child is not currently invalidated, but should be, which is why the viewport disappears. That behavior is fixed by not checking the message source in the code for the paint message, which should also fix other invalidation issues stemming from the parent. On one hand, maybe the engine could get some event processing changes to specifically address some of these issues, but from my experiences trying to track down and understand obscure Windows behaviors when it comes to events is usually not worth it. I just think it's far easier to model your code like reepblue did, and just avoid most of those issues in the first place by always updating/rendering. The other alternative is to possibly render selectively to an image file, and then use simpler, but more comprehensive Win32 message processing to ensure only the minimal amount of redrawing happens outside of a rendering context where you can manage the HDC of a single HWND and not deal with multiple windows and different behaviors of messages across a parent/child setup. Doesn't sound like that's what you're after here though, but if you wanted to minimize 3d rendering due to needing to limit graphics resource usage, I'd consider something like that maybe. 1 Quote Link to comment Share on other sites More sharing options...
Josh Posted January 19, 2023 Share Posted January 19, 2023 Windows should emit a paint event whenever a window is invalidated, whether that comes from resizing or something else. But if you are saying you were able to produce and fix the problem then I can't argue with that. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Josh Posted January 19, 2023 Share Posted January 19, 2023 I see you guys are both on Windows 11. There must have been some changes to the window compositing system between how it worked in Win10 and earlier. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Drew_Benton Posted January 19, 2023 Share Posted January 19, 2023 6 hours ago, Josh said: Windows should emit a paint event whenever a window is invalidated, whether that comes from resizing or something else. But if you are saying you were able to produce and fix the problem then I can't argue with that. I'm on Windows 10, and the WM_PAINT did happen on invalidation when clicking in the middle of the thin border between windows, but just for the parent window and not the child window. I had some code to PeekMessage and check what was going on before UltraEngine processed the events, so that's how I arrived at the conclusion to not check the window source. I can't attach a MP4 file, but here's an example: https://gyazo.com/71f17118ad6d81f388b0f748257570cc I wonder if making the main window the render window, and a second window the UI window would result in the intended behavior without additional modifications? Quote Link to comment Share on other sites More sharing options...
MagnetarX10 Posted January 19, 2023 Author Share Posted January 19, 2023 @Drew_Benton Thanks. Makes sense. Quote Link to comment Share on other sites More sharing options...
MagnetarX10 Posted January 19, 2023 Author Share Posted January 19, 2023 @Josh Thanks for the help. Here is a version that combines the OpenGL example. This seems to be working fine. This is exactly what I need. #include "UltraEngine.h" #include <GL/GL.h> #pragma comment (lib, "opengl32.lib") using namespace UltraEngine; const int SidePanelWidth = 200; const int Indent = 8; //Custom event ID const EventId EVENT_VIEWPORTRENDER = EventId(101); void GLRender(shared_ptr<Window> viewport) { // Get and set the current size of the viewport iVec2 sz = viewport->ClientSize(); if (sz.x < 1 or sz.y < 1) return; glViewport(0, 0, sz.x, sz.y); // Set clear colour of viewport background glClearColor(0.15f, 0.15f, 0.15f, 1.0f); // Clear colour and depth buffers glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Render our triangle glBegin(GL_TRIANGLES); // Vertex 1 glColor3f(1, 0, 0); glVertex3f(0, 0.5, 0); // Vertex 2 glColor3f(0, 1, 0); glVertex3f(0.5, -0.5, 0); // Vertex 3 glColor3f(0, 0, 1); glVertex3f(-0.5, -0.5, 0); glEnd(); HWND hwnd = viewport->GetHandle(); auto hdc = GetDC(hwnd); SwapBuffers(hdc); ReleaseDC(hwnd, hdc); } // Callback function for resizing the viewport bool ResizeViewport(const Event& ev, shared_ptr<Object> extra) { // If the window resize event is captured auto window = ev.source->As<Window>(); // Get the new size of the applications window iVec2 sz = window->ClientSize(); auto viewport = extra->As<Window>(); // Set the position and size of the viewport window viewport->SetShape(SidePanelWidth, Indent, sz.x - SidePanelWidth - Indent, sz.y - Indent * 2); return true; } int main(int argc, const char* argv[]) { // Disable asynchronous rendering so window resizing will work with 3D graphics AsyncRender(false); // Get the available displays auto displays = GetDisplays(); // Create a window auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR | WINDOW_RESIZABLE); // Create user interface auto ui = CreateInterface(window); // Get the size of the user interface iVec2 sz = ui->background->ClientSize(); // Create a treeview widget auto treeview = CreateTreeView(Indent, Indent, SidePanelWidth - Indent * 2, sz.y - Indent * 2, ui->root); // Anchor left, top and bottom of treeview widget treeview->SetLayout(1, 0, 1, 1); // Add nodes to the treeview widget treeview->root->AddNode("Object 1"); treeview->root->AddNode("Object 2"); treeview->root->AddNode("Object 3"); // Create a viewport window auto viewport = CreateWindow("", SidePanelWidth, Indent, sz.x - SidePanelWidth - Indent, sz.y - Indent * 2, window, WINDOW_CHILD); // Adjust the size of the viewport when the applications window is resized (this will callback to our ResizeViewport() function) ListenEvent(EVENT_WINDOWSIZE, window, ResizeViewport, viewport); // Initialize an OpenGL context (get a hdc) HWND hwnd = (HWND)(viewport->GetHandle()); HDC hdc = GetDC(hwnd); // Specify the format of the default framebuffer PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, // Flags PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, // Framebuffer colour format (R, G, B, A) PFD_TYPE_RGBA, // Framebuffer colour depth (32 bit) 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Number of bits for depth-buffer 24, // Number of bits for stencil-buffer 8, // Number of render-targets in default framebuffer 0, PFD_MAIN_PLANE, 0, 0, 0, 0 }; // Select an appropriate pixel format that is supported by the hdc int format = ChoosePixelFormat(hdc, &pfd); if (SetPixelFormat(hdc, format, &pfd) == 0) { RuntimeError("SetPixelFormat() failed."); } // Create an OpenGL rendering context using our current hdc HGLRC glcontext = wglCreateContext(hdc); if (glcontext == NULL) { RuntimeError("wglCreateContext() failed."); } wglMakeCurrent(hdc, glcontext); // This varialble will be used for viewport refreshing bool dirty = false; // Main loop while (true) { // Wait for event const Event ev = WaitEvent(); // Evaluate event switch (ev.id) { case EVENT_WINDOWMOVE: if (not dirty) { dirty = true; EmitEvent(EVENT_VIEWPORTRENDER, viewport); } break; case EVENT_WINDOWSIZE: if (not dirty) { dirty = true; EmitEvent(EVENT_VIEWPORTRENDER, viewport); } break; //Close window when escape key is pressed case EVENT_KEYDOWN: if (ev.source == window and ev.data == KEY_ESCAPE) return 0; break; case EVENT_WINDOWCLOSE: if (ev.source == window) return 0; break; case EVENT_WINDOWPAINT: { if (not dirty) { // This prevents excessive paint events from building up, especially during window resizing // This event is added to the end of the event queue, so if a lot of paint events build up, it will // only cause a single render to be performed. dirty = true; EmitEvent(EVENT_VIEWPORTRENDER, viewport); } } break; case EVENT_VIEWPORTRENDER: GLRender(viewport); dirty = false; break; } } return 0; } 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.