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