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