1 module memory; 2 3 /** 4 This example demonstrates how to properly handle memory management 5 for displaying things such as text. 6 */ 7 8 import std.exception; 9 import std.file; 10 import std.path; 11 import std.stdio; 12 import std.string; 13 14 import deimos.glfw.glfw3; 15 16 import glad.gl.enums; 17 import glad.gl.ext; 18 import glad.gl.funcs; 19 import glad.gl.loader; 20 import glad.gl.types; 21 22 import glwtf.input; 23 import glwtf.window; 24 25 import imgui; 26 27 import window; 28 29 struct GUI 30 { 31 this(Window window) 32 { 33 this.window = window; 34 35 window.on_scroll.strongConnect(&onScroll); 36 37 int width; 38 int height; 39 glfwGetWindowSize(window.window, &width, &height); 40 41 // trigger initial viewport transform. 42 onWindowResize(width, height); 43 44 window.on_resize.strongConnect(&onWindowResize); 45 } 46 47 void render() 48 { 49 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 50 51 // Mouse states 52 ubyte mousebutton = 0; 53 double mouseX; 54 double mouseY; 55 glfwGetCursorPos(window.window, &mouseX, &mouseY); 56 57 const scrollAreaWidth = (windowWidth / 4) - 10; // -10 to allow room for the scrollbar 58 const scrollAreaHeight = windowHeight - 20; 59 60 int mousex = cast(int)mouseX; 61 int mousey = cast(int)mouseY; 62 63 mousey = windowHeight - mousey; 64 int leftButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_LEFT); 65 int rightButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_RIGHT); 66 int middleButton = glfwGetMouseButton(window.window, GLFW_MOUSE_BUTTON_MIDDLE); 67 68 if (leftButton == GLFW_PRESS) 69 mousebutton |= MouseButton.left; 70 71 imguiBeginFrame(mousex, mousey, mousebutton, mouseScroll); 72 73 if (mouseScroll != 0) 74 mouseScroll = 0; 75 76 /// Improper memory management. 77 displayArea1(scrollAreaWidth, scrollAreaHeight); 78 79 /// Attempted workaround, but still improper memory management. 80 char[128] buffer; 81 displayArea2(scrollAreaWidth, scrollAreaHeight, buffer); 82 83 /// Proper memory management. 84 char[128][100] buffers; 85 displayArea3(scrollAreaWidth, scrollAreaHeight, buffers); 86 87 /// Alternatively you may use 'string', which is guaranteed to be immutable 88 /// and will outlive any stack scope since the garbage collector will keep 89 /// a reference to it. 90 displayArea4(scrollAreaWidth, scrollAreaHeight); 91 92 imguiEndFrame(); 93 94 imguiRender(windowWidth, windowHeight); 95 } 96 97 void displayArea1(int scrollAreaWidth, int scrollAreaHeight) 98 { 99 imguiBeginScrollArea("Improper memory management 1", 10, 10, scrollAreaWidth, scrollAreaHeight, &scrollArea1); 100 101 imguiSeparatorLine(); 102 imguiSeparator(); 103 104 /// Note: improper memory management: 'buffer' is scoped to this function, 105 /// but imguiLabel will keep a reference to the 'buffer' until 'imguiRender' 106 /// is called. 'imguiRender' is only called after 'displayArea1' returns, 107 /// after which 'buffer' will not be usable (it's memory allocated on the stack!). 108 /// Result: Random text being displayed or even crashes are possible. 109 char[128] buffer; 110 auto text = buffer.sformat("This is my text: %s", "more text"); 111 imguiLabel(text); 112 113 imguiEndScrollArea(); 114 } 115 116 void displayArea2(int scrollAreaWidth, int scrollAreaHeight, ref char[128] buffer) 117 { 118 imguiBeginScrollArea("Improper memory management 2", 20 + (1 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea2); 119 120 imguiSeparatorLine(); 121 imguiSeparator(); 122 123 foreach (idx; 0 .. 100) 124 { 125 /// Note: improper memory management: 'buffer' will be re-used in each 126 /// iteration of this loop, but imguiLabel will just keep a reference 127 /// to the same memory location on each call. 128 /// Result: Typically the same bit of text is displayed 100 times. 129 auto text = buffer.sformat("Item number %s", idx); 130 imguiLabel(text); 131 } 132 133 imguiEndScrollArea(); 134 } 135 136 void displayArea3(int scrollAreaWidth, int scrollAreaHeight, ref char[128][100] buffers) 137 { 138 imguiBeginScrollArea("Proper memory management 1", 30 + (2 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea3); 139 140 imguiSeparatorLine(); 141 imguiSeparator(); 142 143 foreach (idx, ref buffer; buffers) 144 { 145 /// Note: Proper memory management: 'buffer' is unique for all the items, 146 /// and imguiLabel can safely store a reference to each string since each 147 /// buffer will be valid until the exit of the scope where the 'imguiRender' 148 /// call is emitted. 149 auto text = buffer.sformat("Item number %s", idx); 150 imguiLabel(text); 151 } 152 153 imguiEndScrollArea(); 154 } 155 156 void displayArea4(int scrollAreaWidth, int scrollAreaHeight) 157 { 158 imguiBeginScrollArea("Proper memory management 2", 40 + (3 * scrollAreaWidth), 10, scrollAreaWidth, scrollAreaHeight, &scrollArea4); 159 160 imguiSeparatorLine(); 161 imguiSeparator(); 162 163 foreach (idx; 0 .. 100) 164 { 165 /// Note: Proper memory management: the string will not be prematurely 166 /// garbage-collected since the GC will know that 'imguiLabel' will store 167 /// a refererence to this string for use in a later 'imguiRender call. 168 string str = "This is just some text"; 169 imguiLabel(str); 170 } 171 172 imguiEndScrollArea(); 173 } 174 175 /** 176 This tells OpenGL what area of the available area we are 177 rendering to. In this case, we change it to match the 178 full available area. Without this function call resizing 179 the window would have no effect on the rendering. 180 */ 181 void onWindowResize(int width, int height) 182 { 183 // bottom-left position. 184 enum int x = 0; 185 enum int y = 0; 186 187 /** 188 This function defines the current viewport transform. 189 It defines as a region of the window, specified by the 190 bottom-left position and a width/height. 191 192 Note about the viewport transform: 193 It is the process of transforming vertex data from normalized 194 device coordinate space to window space. It specifies the 195 viewable region of a window. 196 */ 197 glViewport(x, y, width, height); 198 199 windowWidth = width; 200 windowHeight = height; 201 } 202 203 void onScroll(double hOffset, double vOffset) 204 { 205 mouseScroll = -cast(int)vOffset; 206 } 207 208 private: 209 Window window; 210 int windowWidth; 211 int windowHeight; 212 213 int scrollArea1 = 0; 214 int scrollArea2 = 0; 215 int scrollArea3 = 0; 216 int scrollArea4 = 0; 217 int mouseScroll = 0; 218 } 219 220 int main(string[] args) 221 { 222 int width = 1024, height = 768; 223 224 auto window = createWindow("imgui", WindowMode.windowed, width, height); 225 226 GUI gui = GUI(window); 227 228 glfwSwapInterval(1); 229 230 string fontPath = thisExePath().dirName().buildPath("../").buildPath("DroidSans.ttf"); 231 232 enforce(imguiInit(fontPath)); 233 234 glClearColor(0.8f, 0.8f, 0.8f, 1.0f); 235 glEnable(GL_BLEND); 236 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 237 glDisable(GL_DEPTH_TEST); 238 239 while (!glfwWindowShouldClose(window.window)) 240 { 241 gui.render(); 242 243 /* Swap front and back buffers. */ 244 window.swap_buffers(); 245 246 /* Poll for and process events. */ 247 glfwPollEvents(); 248 249 if (window.is_key_down(GLFW_KEY_ESCAPE)) 250 glfwSetWindowShouldClose(window.window, true); 251 } 252 253 // Clean UI 254 imguiDestroy(); 255 256 return 0; 257 }