1 // i could add a "time" uniform for the shaders automatically. unity does a float4 i think with ticks in it
2 // register cheat code? or even a fighting game combo..
3 /++
4 	An add-on for simpledisplay.d, joystick.d, and simpleaudio.d
5 	that includes helper functions for writing simple games (and perhaps
6 	other multimedia programs). Whereas simpledisplay works with
7 	an event-driven framework, arsd.game always uses a consistent
8 	timer for updates.
9 
10 	Usage example:
11 
12 	---
13 	final class MyGame : GameHelperBase {
14 		/// Called when it is time to redraw the frame
15 		/// it will try for a particular FPS
16 		override void drawFrame() {
17 			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ACCUM_BUFFER_BIT);
18 
19 			glLoadIdentity();
20 
21 			glColor3f(1.0, 1.0, 1.0);
22 			glTranslatef(x, y, 0);
23 			glBegin(GL_QUADS);
24 
25 			glVertex2i(0, 0);
26 			glVertex2i(16, 0);
27 			glVertex2i(16, 16);
28 			glVertex2i(0, 16);
29 
30 			glEnd();
31 		}
32 
33 		int x, y;
34 		override bool update(Duration deltaTime) {
35 			x += 1;
36 			y += 1;
37 			return true;
38 		}
39 
40 		override SimpleWindow getWindow() {
41 			auto window = create2dWindow("My game");
42 			// load textures and such here
43 			return window;
44 		}
45 
46 		final void fillAudioBuffer(short[] buffer) {
47 
48 		}
49 	}
50 
51 	void main() {
52 		auto game = new MyGame();
53 
54 		runGame(game, maxRedrawRate, maxUpdateRate);
55 	}
56 	---
57 
58 	It provides an audio thread, input scaffold, and helper functions.
59 
60 
61 	The MyGame handler is actually a template, so you don't have virtual
62 	function indirection and not all functions are required. The interfaces
63 	are just to help you get the signatures right, they don't force virtual
64 	dispatch at runtime.
65 
66 	See_Also:
67 		[arsd.ttf.OpenGlLimitedFont]
68 +/
69 module arsd.game;
70 
71 /+
72 	Networking helper: just send/receive messages and manage some connections
73 
74 	It might offer a controller queue you can put local and network events in to get fair lag and transparent ultiplayer
75 
76 	split screen?!?!
77 
78 +/
79 
80 /+
81 	ADD ME:
82 	Animation helper like audio style. Your game object
83 	has a particular image attached as primary.
84 
85 	You can be like `animate once` or `animate indefinitely`
86 	and it takes care of it, then set new things and it does that too.
87 +/
88 
89 public import arsd.gamehelpers;
90 public import arsd.color;
91 public import arsd.simpledisplay;
92 public import arsd.simpleaudio;
93 
94 import std.math;
95 public import core.time;
96 
97 public import arsd.joystick;
98 
99 /++
100 	Creates a simple 2d opengl simpledisplay window. It sets the matrix for pixel coordinates and enables alpha blending and textures.
101 +/
102 SimpleWindow create2dWindow(string title, int width = 512, int height = 512) {
103 	auto window = new SimpleWindow(width, height, title, OpenGlOptions.yes);
104 
105 	window.setAsCurrentOpenGlContext();
106 
107 	glEnable(GL_BLEND);
108 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
109 	glClearColor(0,0,0,0);
110 	glDepthFunc(GL_LEQUAL);
111 
112 	glMatrixMode(GL_PROJECTION);
113 	glLoadIdentity();
114 	glOrtho(0, width, height, 0, 0, 1);
115 
116 	glMatrixMode(GL_MODELVIEW);
117 	glLoadIdentity();
118 	glDisable(GL_DEPTH_TEST);
119 	glEnable(GL_TEXTURE_2D);
120 
121 	window.windowResized = (newWidth, newHeight) {
122 		int x, y, w, h;
123 
124 		// FIXME: this works for only square original sizes
125 		if(newWidth < newHeight) {
126 			w = newWidth;
127 			h = newWidth * height / width;
128 			x = 0;
129 			y = (newHeight - h) / 2;
130 		} else {
131 			w = newHeight * width / height;
132 			h = newHeight;
133 			x = (newWidth - w) / 2;
134 			y = 0;
135 		}
136 
137 		glViewport(x, y, w, h);
138 		window.redrawOpenGlSceneNow();
139 	};
140 
141 	return window;
142 }
143 
144 /++
145 	This is the base class for your game.
146 
147 	You should destroy this explicitly. Easiest
148 	way is to do this in your `main` function:
149 
150 	---
151 		auto game = new MyGameSubclass();
152 		scope(exit) .destroy(game);
153 
154 		runGame(game);
155 	---
156 +/
157 abstract class GameHelperBase {
158 	/// Implement this to draw.
159 	abstract void drawFrame();
160 
161 	ushort snesRepeatRate() { return ushort.max; }
162 	ushort snesRepeatDelay() { return snesRepeatRate(); }
163 
164 	/// Implement this to update. The deltaTime tells how much real time has passed since the last update.
165 	/// Returns true if anything changed, which will queue up a redraw
166 	abstract bool update(Duration deltaTime);
167 	//abstract void fillAudioBuffer(short[] buffer);
168 
169 	/// Returns the main game window. This function will only be
170 	/// called once if you use runGame. You should return a window
171 	/// here like one created with `create2dWindow`.
172 	abstract SimpleWindow getWindow();
173 
174 	/// Override this and return true to initialize the audio system.
175 	/// Note that trying to use the [audio] member without this will segfault!
176 	bool wantAudio() { return false; }
177 
178 	/// You must override [wantAudio] and return true for this to be valid;
179 	AudioOutputThread audio;
180 
181 	this() {
182 		audio = AudioOutputThread(wantAudio());
183 	}
184 
185 	protected bool redrawForced;
186 
187 	/// Forces a redraw even if update returns false
188 	final public void forceRedraw() {
189 		redrawForced = true;
190 	}
191 
192 	/// These functions help you handle user input. It offers polling functions for
193 	/// keyboard, mouse, joystick, and virtual controller input.
194 	///
195 	/// The virtual digital controllers are best to use if that model fits you because it
196 	/// works with several kinds of controllers as well as keyboards.
197 
198 	JoystickUpdate[4] joysticks;
199 	ref JoystickUpdate joystick1() { return joysticks[0]; }
200 
201 	bool[256] keyboardState;
202 
203 	// FIXME: add a mouse position and delta thing too.
204 
205 	/++
206 
207 	+/
208 	VirtualController snes;
209 }
210 
211 /++
212 	The virtual controller is based on the SNES. If you need more detail, try using
213 	the joystick or keyboard and mouse members directly.
214 
215 	```
216 	 l          r
217 
218 	 U          X
219 	L R  s  S  Y A
220 	 D          B
221 	```
222 
223 	For Playstation and XBox controllers plugged into the computer,
224 	it picks those buttons based on similar layout on the physical device.
225 
226 	For keyboard control, arrows and WASD are mapped to the d-pad (ULRD in the diagram),
227 	Q and E are mapped to the shoulder buttons (l and r in the diagram).So are U and P.
228 
229 	Z, X, C, V (for when right hand is on arrows) and K,L,I,O (for left hand on WASD) are mapped to B,A,Y,X buttons.
230 
231 	G is mapped to select (s), and H is mapped to start (S).
232 
233 	The space bar and enter keys are also set to button A, with shift mapped to button B.
234 
235 
236 	Only player 1 is mapped to the keyboard.
237 +/
238 struct VirtualController {
239 	ushort previousState;
240 	ushort state;
241 
242 	// for key repeat
243 	ushort truePreviousState;
244 	ushort lastStateChange;
245 	bool repeating;
246 
247 	///
248 	enum Button {
249 		Up, Left, Right, Down,
250 		X, A, B, Y,
251 		Select, Start, L, R
252 	}
253 
254 	@nogc pure nothrow @safe:
255 
256 	/++
257 		History: Added April 30, 2020
258 	+/
259 	bool justPressed(Button idx) const {
260 		auto before = (previousState & (1 << (cast(int) idx))) ? true : false;
261 		auto after = (state & (1 << (cast(int) idx))) ? true : false;
262 		return !before && after;
263 	}
264 	/++
265 		History: Added April 30, 2020
266 	+/
267 	bool justReleased(Button idx) const {
268 		auto before = (previousState & (1 << (cast(int) idx))) ? true : false;
269 		auto after = (state & (1 << (cast(int) idx))) ? true : false;
270 		return before && !after;
271 	}
272 
273 	///
274 	bool opIndex(Button idx) const {
275 		return (state & (1 << (cast(int) idx))) ? true : false;
276 	}
277 	private void opIndexAssign(bool value, Button idx) {
278 		if(value)
279 			state |= (1 << (cast(int) idx));
280 		else
281 			state &= ~(1 << (cast(int) idx));
282 	}
283 }
284 
285 /++
286 	Deprecated, use the other overload instead.
287 
288 	History:
289 		Deprecated on May 9, 2020. Instead of calling
290 		`runGame(your_instance);` run `runGame!YourClass();`
291 		instead. If you needed to change something in the game
292 		ctor, make a default constructor in your class to do that
293 		instead.
294 +/
295 deprecated("Use runGame!YourGameType(updateRate, redrawRate); instead now.")
296 void runGame()(GameHelperBase game, int maxUpdateRate = 20, int maxRedrawRate = 0) { assert(0, "this overload is deprecated, use runGame!YourClass instead"); }
297 
298 /++
299 	Runs your game. It will construct the given class and destroy it at end of scope.
300 	Your class must have a default constructor and must implement [GameHelperBase].
301 	Your class should also probably be `final` for performance reasons.
302 
303 	$(TIP
304 		If you need to pass parameters to your game class, you can define
305 		it as a nested class in your `main` function and access the local
306 		variables that way instead of passing them explicitly through the
307 		constructor.
308 	)
309 
310 	Params:
311 	maxUpdateRate = The max rates are given in executions per second
312 	maxRedrawRate = Redraw will never be called unless there has been at least one update
313 +/
314 void runGame(T : GameHelperBase)(int maxUpdateRate = 20, int maxRedrawRate = 0) {
315 
316 
317 	auto game = new T();
318 	scope(exit) .destroy(game);
319 
320 	// this is a template btw because then it can statically dispatch
321 	// the members instead of going through the virtual interface.
322 
323 	int joystickPlayers = enableJoystickInput();
324 	scope(exit) closeJoysticks();
325 
326 	auto window = game.getWindow();
327 
328 	window.redrawOpenGlScene = &game.drawFrame;
329 
330 	auto lastUpdate = MonoTime.currTime;
331 
332 	window.eventLoop(1000 / maxUpdateRate,
333 		delegate() {
334 			foreach(p; 0 .. joystickPlayers) {
335 				version(linux)
336 					readJoystickEvents(joystickFds[p]);
337 				auto update = getJoystickUpdate(p);
338 
339 				if(p == 0) {
340 					static if(__traits(isSame, Button, PS1Buttons)) {
341 						// PS1 style joystick mapping compiled in
342 						with(Button) with(VirtualController.Button) {
343 							// so I did the "wasJustPressed thing because it interplays
344 							// better with the keyboard as well which works on events...
345 							if(update.buttonWasJustPressed(square)) game.snes[Y] = true;
346 							if(update.buttonWasJustPressed(triangle)) game.snes[X] = true;
347 							if(update.buttonWasJustPressed(cross)) game.snes[B] = true;
348 							if(update.buttonWasJustPressed(circle)) game.snes[A] = true;
349 							if(update.buttonWasJustPressed(select)) game.snes[Select] = true;
350 							if(update.buttonWasJustPressed(start)) game.snes[Start] = true;
351 							if(update.buttonWasJustPressed(l1)) game.snes[L] = true;
352 							if(update.buttonWasJustPressed(r1)) game.snes[R] = true;
353 							// note: no need to check analog stick here cuz joystick.d already does it for us (per old playstation tradition)
354 							if(update.axisChange(Axis.horizontalDpad) < 0 && update.axisPosition(Axis.horizontalDpad) < -8) game.snes[Left] = true;
355 							if(update.axisChange(Axis.horizontalDpad) > 0 && update.axisPosition(Axis.horizontalDpad) > 8) game.snes[Right] = true;
356 							if(update.axisChange(Axis.verticalDpad) < 0 && update.axisPosition(Axis.verticalDpad) < -8) game.snes[Up] = true;
357 							if(update.axisChange(Axis.verticalDpad) > 0 && update.axisPosition(Axis.verticalDpad) > 8) game.snes[Down] = true;
358 
359 							if(update.buttonWasJustReleased(square)) game.snes[Y] = false;
360 							if(update.buttonWasJustReleased(triangle)) game.snes[X] = false;
361 							if(update.buttonWasJustReleased(cross)) game.snes[B] = false;
362 							if(update.buttonWasJustReleased(circle)) game.snes[A] = false;
363 							if(update.buttonWasJustReleased(select)) game.snes[Select] = false;
364 							if(update.buttonWasJustReleased(start)) game.snes[Start] = false;
365 							if(update.buttonWasJustReleased(l1)) game.snes[L] = false;
366 							if(update.buttonWasJustReleased(r1)) game.snes[R] = false;
367 							if(update.axisChange(Axis.horizontalDpad) > 0 && update.axisPosition(Axis.horizontalDpad) > -8) game.snes[Left] = false;
368 							if(update.axisChange(Axis.horizontalDpad) < 0 && update.axisPosition(Axis.horizontalDpad) < 8) game.snes[Right] = false;
369 							if(update.axisChange(Axis.verticalDpad) > 0 && update.axisPosition(Axis.verticalDpad) > -8) game.snes[Up] = false;
370 							if(update.axisChange(Axis.verticalDpad) < 0 && update.axisPosition(Axis.verticalDpad) < 8) game.snes[Down] = false;
371 						}
372 
373 					} else static if(__traits(isSame, Button, XBox360Buttons)) {
374 					static assert(0);
375 						// XBox style mapping
376 						// the reason this exists is if the programmer wants to use the xbox details, but
377 						// might also want the basic controller in here. joystick.d already does translations
378 						// so an xbox controller with the default build actually uses the PS1 branch above.
379 						/+
380 						case XBox360Buttons.a: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_A) ? true : false;
381 						case XBox360Buttons.b: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_B) ? true : false;
382 						case XBox360Buttons.x: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_X) ? true : false;
383 						case XBox360Buttons.y: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_Y) ? true : false;
384 
385 						case XBox360Buttons.lb: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? true : false;
386 						case XBox360Buttons.rb: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? true : false;
387 
388 						case XBox360Buttons.back: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) ? true : false;
389 						case XBox360Buttons.start: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_START) ? true : false;
390 						+/
391 					}
392 				}
393 
394 				game.joysticks[p] = update;
395 			}
396 
397 			auto now = MonoTime.currTime;
398 			bool changed = game.update(now - lastUpdate);
399 			auto stateChange = game.snes.truePreviousState ^ game.snes.state;
400 			game.snes.previousState = game.snes.state;
401 			game.snes.truePreviousState = game.snes.state;
402 
403 			if(stateChange == 0) {
404 				game.snes.lastStateChange++;
405 				auto r = game.snesRepeatRate();
406 				if(r != typeof(r).max && !game.snes.repeating && game.snes.lastStateChange == game.snesRepeatDelay()) {
407 					game.snes.lastStateChange = 0;
408 					game.snes.repeating = true;
409 				} else if(r != typeof(r).max && game.snes.repeating && game.snes.lastStateChange == r) {
410 					game.snes.lastStateChange = 0;
411 					game.snes.previousState = 0;
412 				}
413 			} else {
414 				game.snes.repeating = false;
415 			}
416 			lastUpdate = now;
417 
418 			if(game.redrawForced) {
419 				changed = true;
420 				game.redrawForced = false;
421 			}
422 
423 			// FIXME: rate limiting
424 			if(changed)
425 				window.redrawOpenGlSceneNow();
426 		},
427 
428 		delegate (KeyEvent ke) {
429 			game.keyboardState[ke.hardwareCode] = ke.pressed;
430 
431 			with(VirtualController.Button)
432 			switch(ke.key) {
433 				case Key.Up, Key.W: game.snes[Up] = ke.pressed; break;
434 				case Key.Down, Key.S: game.snes[Down] = ke.pressed; break;
435 				case Key.Left, Key.A: game.snes[Left] = ke.pressed; break;
436 				case Key.Right, Key.D: game.snes[Right] = ke.pressed; break;
437 				case Key.Q, Key.U: game.snes[L] = ke.pressed; break;
438 				case Key.E, Key.P: game.snes[R] = ke.pressed; break;
439 				case Key.Z, Key.K: game.snes[B] = ke.pressed; break;
440 				case Key.Space, Key.Enter, Key.X, Key.L: game.snes[A] = ke.pressed; break;
441 				case Key.C, Key.I: game.snes[Y] = ke.pressed; break;
442 				case Key.V, Key.O: game.snes[X] = ke.pressed; break;
443 				case Key.G: game.snes[Select] = ke.pressed; break;
444 				case Key.H: game.snes[Start] = ke.pressed; break;
445 				case Key.Shift, Key.Shift_r: game.snes[B] = ke.pressed; break;
446 				default:
447 			}
448 		}
449 	);
450 }
451 
452 /++
453 	Simple class for putting a TrueColorImage in as an OpenGL texture.
454 
455 	Doesn't do mipmapping btw.
456 +/
457 final class OpenGlTexture {
458 	private uint _tex;
459 	private int _width;
460 	private int _height;
461 	private float _texCoordWidth;
462 	private float _texCoordHeight;
463 
464 	/// Calls glBindTexture
465 	void bind() {
466 		glBindTexture(GL_TEXTURE_2D, _tex);
467 	}
468 
469 	/// For easy 2d drawing of it
470 	void draw(Point where, int width = 0, int height = 0, float rotation = 0.0, Color bg = Color.white) {
471 		draw(where.x, where.y, width, height, rotation, bg);
472 	}
473 
474 	///
475 	void draw(float x, float y, int width = 0, int height = 0, float rotation = 0.0, Color bg = Color.white) {
476 		glPushMatrix();
477 		glTranslatef(x, y, 0);
478 
479 		if(width == 0)
480 			width = this.originalImageWidth;
481 		if(height == 0)
482 			height = this.originalImageHeight;
483 
484 		glTranslatef(cast(float) width / 2, cast(float) height / 2, 0);
485 		glRotatef(rotation, 0, 0, 1);
486 		glTranslatef(cast(float) -width / 2, cast(float) -height / 2, 0);
487 
488 		glColor4f(cast(float)bg.r/255.0, cast(float)bg.g/255.0, cast(float)bg.b/255.0, cast(float)bg.a / 255.0);
489 		glBindTexture(GL_TEXTURE_2D, _tex);
490 		glBegin(GL_QUADS); 
491 			glTexCoord2f(0, 0); 				glVertex2i(0, 0);
492 			glTexCoord2f(texCoordWidth, 0); 		glVertex2i(width, 0); 
493 			glTexCoord2f(texCoordWidth, texCoordHeight); 	glVertex2i(width, height); 
494 			glTexCoord2f(0, texCoordHeight); 		glVertex2i(0, height); 
495 		glEnd();
496 
497 		glBindTexture(GL_TEXTURE_2D, 0); // unbind the texture
498 
499 		glPopMatrix();
500 	}
501 
502 	/// Use for glTexCoord2f
503 	float texCoordWidth() { return _texCoordWidth; }
504 	float texCoordHeight() { return _texCoordHeight; } /// ditto
505 
506 	/// Returns the texture ID
507 	uint tex() { return _tex; }
508 
509 	/// Returns the size of the image
510 	int originalImageWidth() { return _width; }
511 	int originalImageHeight() { return _height; } /// ditto
512 
513 	// explicitly undocumented, i might remove this
514 	TrueColorImage from;
515 
516 	/// Make a texture from an image.
517 	this(TrueColorImage from) {
518 		bindFrom(from);
519 	}
520 
521 	/// Generates from text. Requires ttf.d
522 	/// pass a pointer to the TtfFont as the first arg (it is template cuz of lazy importing, not because it actually works with different types)
523 	this(T, FONT)(FONT* font, int size, in T[] text) if(is(T == char)) {
524 		bindFrom(font, size, text);
525 	}
526 
527 	/// Creates an empty texture class for you to use with [bindFrom] later
528 	/// Using it when not bound is undefined behavior.
529 	this() {}
530 
531 
532 
533 	/// After you delete it with dispose, you may rebind it to something else with this.
534 	void bindFrom(TrueColorImage from) {
535 		assert(from !is null);
536 		assert(from.width > 0 && from.height > 0);
537 
538 		import core.stdc.stdlib;
539 
540 		_width = from.width;
541 		_height = from.height;
542 
543 		this.from = from;
544 
545 		auto _texWidth = _width;
546 		auto _texHeight = _height;
547 
548 		const(ubyte)* data = from.imageData.bytes.ptr;
549 		bool freeRequired = false;
550 
551 		// gotta round them to the nearest power of two which means padding the image
552 		if((_texWidth & (_texWidth - 1)) || (_texHeight & (_texHeight - 1))) {
553 			_texWidth = nextPowerOfTwo(_texWidth);
554 			_texHeight = nextPowerOfTwo(_texHeight);
555 
556 			auto n = cast(ubyte*) malloc(_texWidth * _texHeight * 4);
557 			if(n is null) assert(0);
558 			scope(failure) free(n);
559 
560 			auto size = from.width * 4;
561 			auto advance = _texWidth * 4;
562 			int at = 0;
563 			int at2 = 0;
564 			foreach(y; 0 .. from.height) {
565 				n[at .. at + size] = from.imageData.bytes[at2 .. at2+ size];
566 				at += advance;
567 				at2 += size;
568 			}
569 
570 			data = n;
571 			freeRequired = true;
572 
573 			// the rest of data will be initialized to zeros automatically which is fine.
574 		}
575 
576 		glGenTextures(1, &_tex);
577 		glBindTexture(GL_TEXTURE_2D, tex);
578 
579 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
580 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
581 		
582 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
583 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
584 		
585 		glTexImage2D(
586 			GL_TEXTURE_2D,
587 			0,
588 			GL_RGBA,
589 			_texWidth, // needs to be power of 2
590 			_texHeight,
591 			0,
592 			GL_RGBA,
593 			GL_UNSIGNED_BYTE,
594 			data);
595 
596 		assert(!glGetError());
597 
598 		_texCoordWidth = cast(float) _width / _texWidth;
599 		_texCoordHeight = cast(float) _height / _texHeight;
600 
601 		if(freeRequired)
602 			free(cast(void*) data);
603 		glBindTexture(GL_TEXTURE_2D, 0);
604 	}
605 
606 	/// ditto
607 	void bindFrom(T, FONT)(FONT* font, int size, in T[] text) if(is(T == char)) {
608 		assert(font !is null);
609 		int width, height;
610 		auto data = font.renderString(text, size, width, height);
611 		auto image = new TrueColorImage(width, height);
612 		int pos = 0;
613 		foreach(y; 0 .. height)
614 		foreach(x; 0 .. width) {
615 			image.imageData.bytes[pos++] = 255;
616 			image.imageData.bytes[pos++] = 255;
617 			image.imageData.bytes[pos++] = 255;
618 			image.imageData.bytes[pos++] = data[0];
619 			data = data[1 .. $];
620 		}
621 		assert(data.length == 0);
622 
623 		bindFrom(image);
624 	}
625 
626 	/// Deletes the texture. Using it after calling this is undefined behavior
627 	void dispose() {
628 		glDeleteTextures(1, &_tex);
629 		_tex = 0;
630 	}
631 
632 	~this() {
633 		if(_tex > 0)
634 			dispose();
635 	}
636 }
637 
638 /+
639 	FIXME: i want to do stbtt_GetBakedQuad for ASCII and use that
640 	for simple cases especially numbers. for other stuff you can
641 	create the texture for the text above.
642 +/
643 
644 ///
645 void clearOpenGlScreen(SimpleWindow window) {
646 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ACCUM_BUFFER_BIT);
647 }
648 
649