mirror of
				https://git.shylie.info/shylie/glerminal.git
				synced 2025-10-31 17:20:14 +00:00 
			
		
		
		
	Compare commits
	
		
			44 Commits
		
	
	
		
			96944fcaa9
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fb3eeb6ec9 | |||
| 263cf68ca2 | |||
| b8e616c8d6 | |||
| 1bfbab7a96 | |||
| d156005441 | |||
| 82389b8c12 | |||
| 0a9d211942 | |||
| 937693ce6a | |||
| 365cad551b | |||
| 5fbeb1e4e9 | |||
| 11ffc40bbb | |||
| db46885ee9 | |||
| 20fad92e4f | |||
| 7c0c02b9af | |||
| 1a1f4e8b13 | |||
|   | 6663804c88 | ||
|   | 9d88ebbb3b | ||
|   | 76bfd67f4e | ||
|   | 4811e4d970 | ||
|   | e402b5f5a3 | ||
|   | 139681a3fd | ||
|   | 45ec13db45 | ||
|   | 996d14c93c | ||
|   | d50fefc359 | ||
|   | 6444654f42 | ||
|   | da1f32a014 | ||
|   | 71c052b38f | ||
|   | a39905c6cb | ||
|   | e5b017ad97 | ||
|   | fa8b8bfcf8 | ||
|   | ca0addb54c | ||
|   | 93df224dc8 | ||
|   | 35902c2803 | ||
|   | 4b740e3edd | ||
|   | 10a90a5f6a | ||
|   | 939fea0fa7 | ||
|   | 844e1b6c7a | ||
|   | 7cd81b31eb | ||
|   | 1e0be8eca0 | ||
|   | ffcafb5160 | ||
|   | 06e189963e | ||
|   | 4b13ca2818 | ||
|   | cf9ab6c0a1 | ||
|   | ae2052f8a9 | 
| @@ -5,29 +5,31 @@ on: push | ||||
|  | ||||
| jobs: | ||||
|   build-app: | ||||
|     runs-on: linux_amd64 | ||||
|     runs-on: shy-server | ||||
|     steps: | ||||
|       - name: Checkout current | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           path: curr | ||||
|           submodules: recursive | ||||
|       - name: Checkout previous | ||||
|           lfs: true | ||||
|       - name: Checkout previous known working | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           path: prev | ||||
|           fetch-depth: 2 | ||||
|           fetch-depth: 0 | ||||
|           submodules: recursive | ||||
|       - run: cd prev && git checkout HEAD^ | ||||
|           lfs: true | ||||
|       - run: cd prev && git checkout 263cf68ca2 | ||||
|       - name: Build current | ||||
|         run: cmake -S curr -B curr/build -DGLERMINAL_TEST=ON && cmake --build curr/build | ||||
|       - name: Build previous | ||||
|         run: cmake -S prev -B prev/build -DGLERMINAL_TEST=ON && cmake --build prev/build | ||||
|       - name: Generate PNG file for current | ||||
|         run: cd curr/build/tests && XDG_RUNTIME_DIR=$PWD xvfb-run -a -s="-screen 0 1280x800x24" ./test-basic | ||||
|         run: cd curr/build/tests && ls $PWD/resources && XDG_RUNTIME_DIR=$PWD xvfb-run -a ./test-basic | ||||
|       - name: Generate PNG file for previous | ||||
|         run: cd prev/build/tests && XDG_RUNTIME_DIR=$PWD xvfb-run -a -s="-screen 0 1280x800x24" ./test-basic | ||||
|       - name: Upload current PNG | ||||
|         run: cd prev/build/tests && ls $PWD/resources && XDG_RUNTIME_DIR=$PWD xvfb-run -a ./test-basic | ||||
|       - name: Upload PNG files | ||||
|         uses: actions/upload-artifact@v3 | ||||
|         with: | ||||
|             name: basic | ||||
|   | ||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1,4 @@ | ||||
| build/ | ||||
| cmake-build-*/ | ||||
| .idea/ | ||||
| .cache/ | ||||
|   | ||||
							
								
								
									
										2
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,3 @@ | ||||
| [submodule "glfw"] | ||||
| 	path = glfw | ||||
| 	url = https://git.shylie.info/shylie/glfw.git | ||||
| 	url = https://github.com/glfw/glfw.git | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| cmake_minimum_required(VERSION 3.28) | ||||
|  | ||||
| project(glerminal | ||||
| 	VERSION 0.1.0 | ||||
| 	LANGUAGES C CXX | ||||
|   VERSION 0.1.0 | ||||
|   LANGUAGES C CXX | ||||
| ) | ||||
|  | ||||
| option(GLERMINAL_OPENGL_DEBUG_CONTEXT "" OFF) | ||||
| @@ -11,60 +11,58 @@ set(GLERMINAL_GRID_WIDTH 40 CACHE STRING "") | ||||
| set(GLERMINAL_GRID_HEIGHT 25 CACHE STRING "") | ||||
| set(GLERMINAL_LAYER_COUNT 64 CACHE STRING "") | ||||
| set(GLERMINAL_CELL_SCALE 4 CACHE STRING "") | ||||
| set(GLERMINAL_CELL_SIZE 8 CACHE STRING "") | ||||
|  | ||||
| configure_file(source/glerminal-config.h.in glerminal-config.h @ONLY) | ||||
|  | ||||
| add_subdirectory(glfw) | ||||
|  | ||||
| add_library(glerminal STATIC | ||||
| 	${CMAKE_CURRENT_BINARY_DIR}/glerminal-config.h | ||||
| 	include/glerminal.h | ||||
|   ${CMAKE_CURRENT_BINARY_DIR}/glerminal-config.h | ||||
|   include/glerminal.h | ||||
|  | ||||
| 	source/glerminal-private.h | ||||
| 	source/glerminal.cpp | ||||
|   source/stb_image.h | ||||
|   source/glerminal-private.h | ||||
|   source/glerminal.cpp | ||||
|  | ||||
| 	source/glad/glad.h | ||||
| 	source/KHR/khrplatform.h | ||||
| 	source/glad.c | ||||
|   source/glad/glad.h | ||||
|   source/KHR/khrplatform.h | ||||
|   source/glad.c | ||||
|  | ||||
|   source/miniaudio.h | ||||
|   source/miniaudio.c | ||||
| ) | ||||
|  | ||||
| set_target_properties(glerminal | ||||
| 	PROPERTIES | ||||
| 		CXX_STANDARD 11 | ||||
|   PROPERTIES | ||||
|     CXX_STANDARD 11 | ||||
| ) | ||||
|  | ||||
| target_include_directories(glerminal | ||||
| 	PUBLIC | ||||
| 		include | ||||
| 		${CMAKE_CURRENT_BINARY_DIR} | ||||
| 	PRIVATE | ||||
| 		source | ||||
|   PUBLIC | ||||
|     include | ||||
|     ${CMAKE_CURRENT_BINARY_DIR} | ||||
|   PRIVATE | ||||
|     source | ||||
| ) | ||||
|  | ||||
| target_link_libraries(glerminal | ||||
| 	PRIVATE | ||||
| 		glfw | ||||
|   PUBLIC | ||||
|     glfw | ||||
| ) | ||||
|  | ||||
| target_compile_definitions(glerminal | ||||
| 	PUBLIC | ||||
| 		GLERMINAL_VERSION=${PROJECT_VERSION} | ||||
| 		GLERMINAL_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} | ||||
| 		GLERMINAL_VERSION_MINOR=${PROJECT_VERSION_MINOR} | ||||
| 		GLERMINAL_VERSION_PATCH=${PROJECT_VERSION_PATCH} | ||||
|   PUBLIC | ||||
|     GLERMINAL_VERSION=${PROJECT_VERSION} | ||||
|     GLERMINAL_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} | ||||
|     GLERMINAL_VERSION_MINOR=${PROJECT_VERSION_MINOR} | ||||
|     GLERMINAL_VERSION_PATCH=${PROJECT_VERSION_PATCH} | ||||
| ) | ||||
|  | ||||
| if (MSVC) | ||||
| 	target_link_options(glerminal | ||||
| 		PUBLIC | ||||
| 			"/ENTRY:mainCRTStartup" | ||||
| 	) | ||||
| if(PROJECT_IS_TOP_LEVEL) | ||||
|   add_subdirectory(examples examples) | ||||
| endif() | ||||
|  | ||||
| if (PROJECT_IS_TOP_LEVEL) | ||||
| 	add_subdirectory(examples examples) | ||||
| endif() | ||||
|  | ||||
| if (GLERMINAL_TEST) | ||||
| 	add_subdirectory(tests tests) | ||||
| if(GLERMINAL_TEST) | ||||
|   add_subdirectory(tests tests) | ||||
| endif() | ||||
							
								
								
									
										37
									
								
								PROJECT_RETROSPECTIVE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								PROJECT_RETROSPECTIVE.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| # A look back on what I learned | ||||
|  | ||||
| ## why? | ||||
|  | ||||
| I came across an art/rendering style called "sprite stacking". | ||||
| I found sprite stacking very interesting in comparison to traditional methods, | ||||
| and wanted to experiment with the style. | ||||
|  | ||||
| ## challenge | ||||
|  | ||||
| This was my first big graphics-based project. Computer graphics | ||||
| at the low-level was new to me. I took this as an opportunity to | ||||
| learn the basics of OpenGL. This learning process was the most | ||||
| difficult part of the project, especially the domain-specific | ||||
| rendering techniques used. | ||||
|  | ||||
| ## how? | ||||
|  | ||||
| I found some [great tutorials](https://learnopengl.com) to get started, | ||||
| and though these helped, I also did a lot of individual | ||||
| learning through experimentation and discussion with peers. | ||||
|  | ||||
| ## results | ||||
|  | ||||
| In the end, the library had most of the functionality | ||||
| I originally wanted. The library can draw up to 256 layers of icons in a grid, | ||||
| with optional offsets and tints per icon. Here's an example with only 7 library calls: | ||||
|  | ||||
|  | ||||
|  | ||||
| ## evaluation | ||||
|  | ||||
| Besides learning the basics of OpenGL, I also learned | ||||
| how to use CMake to help build the library. Also, I set up | ||||
| continuous integration for automated testing on a git commit. | ||||
| If I were to rewrite this library, I would definitely invest more | ||||
| into the automated testing process. | ||||
| @@ -1,2 +1,4 @@ | ||||
| # termg | ||||
| # glerminal | ||||
|  | ||||
| glerminal is a tile-based graphics library created | ||||
| with the intent to use a sprite-stacking art style. | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								example.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								example.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 6.1 MiB | 
| @@ -12,10 +12,12 @@ file(GLOB_RECURSE | ||||
|  | ||||
| foreach(RESOURCE_FILE ${EXAMPLE_RESOURCES}) | ||||
| 	add_custom_command( | ||||
| 		OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_FILE} | ||||
| 		COMMAND ${CMAKE_COMMAND} -E copy | ||||
| 			${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_FILE} | ||||
| 		OUTPUT | ||||
| 			${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_FILE} | ||||
| 		COMMAND | ||||
| 			${CMAKE_COMMAND} -E copy | ||||
| 				${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_FILE} | ||||
| 				${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_FILE} | ||||
| 		DEPENDS | ||||
| 			${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_FILE} | ||||
| 	) | ||||
|   | ||||
| @@ -9,9 +9,9 @@ namespace | ||||
| 		glerminal_load_sprites_file("resources/atlas.png"); | ||||
| 	} | ||||
|  | ||||
| 	void mainloop(float dt) | ||||
| 	void mainloop(double dt) | ||||
| 	{ | ||||
| 		static float time = 1; | ||||
| 		static double time = 1; | ||||
|  | ||||
| 		time += dt; | ||||
|  | ||||
| @@ -30,8 +30,8 @@ namespace | ||||
| 			{ | ||||
| 				for (int k = 0; k < LAYER_COUNT; k++) | ||||
| 				{ | ||||
| 					glerminal_set(i, j, k, rand() % 4); | ||||
| 					glerminal_offset(i, j, k, (rand() * rand()) % 64 - 32, (rand() * rand()) % 64 - 32); | ||||
| 					glerminal_set(i + 1, j + 1, k, rand() % 4); | ||||
| 					glerminal_offset(i + 1, j + 1, k, (rand() * rand()) % 64 - 32, (rand() * rand()) % 64 - 32); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| @@ -42,5 +42,5 @@ namespace | ||||
|  | ||||
| int main(int argc, char** argv) | ||||
| { | ||||
| 	glerminal_run(init, mainloop); | ||||
| 	glerminal_run({init, mainloop}); | ||||
| } | ||||
| @@ -9,25 +9,25 @@ namespace | ||||
| 	{ | ||||
| 		glerminal_load_sprites_file("resources/basic.png"); | ||||
|  | ||||
| 		for (int i = 0; i < GRID_WIDTH; i++) | ||||
| 		for (int i = 0; i < GRID_WIDTH + 2; i++) | ||||
| 		{ | ||||
| 			for (int j = 0; j < GRID_HEIGHT; j++) | ||||
| 			for (int j = 0; j < GRID_HEIGHT + 2; j++) | ||||
| 			{ | ||||
| 				glerminal_set(i, j, 0, 1); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void mainloop(float dt) | ||||
| 	void mainloop(double dt) | ||||
| 	{ | ||||
| 		static float time = 0; | ||||
| 		static double time = 0; | ||||
|  | ||||
| 		time += dt; | ||||
| 		time = fmodf(time, 3.1415926f * 2); | ||||
|  | ||||
| 		for (int i = 0; i < GRID_WIDTH; i++) | ||||
| 		for (int i = 0; i < GRID_WIDTH + 2; i++) | ||||
| 		{ | ||||
| 			for (int j = 0; j < GRID_HEIGHT; j++) | ||||
| 			for (int j = 0; j < GRID_HEIGHT + 2; j++) | ||||
| 			{ | ||||
| 				glerminal_offset(i, j, 0, cosf(time - i / 3.1415f), sinf(time - j / 3.1415f)); | ||||
| 			} | ||||
| @@ -39,5 +39,5 @@ namespace | ||||
|  | ||||
| int main(int argc, char** argv) | ||||
| { | ||||
| 	glerminal_run(init, mainloop); | ||||
| 	glerminal_run({init, mainloop}); | ||||
| } | ||||
| @@ -18,22 +18,16 @@ namespace | ||||
| 	{ | ||||
| 		glerminal_load_sprites_file("resources/rogue.png"); | ||||
|  | ||||
| 		for (int i = 0; i < WALL_LAYERS; i++) | ||||
| 		for (int i = 0; i < GRID_WIDTH + 2; i++) | ||||
| 		{ | ||||
| 			const unsigned char v = (i + 1) * (256 / WALL_LAYERS) - 1; | ||||
| 			const unsigned int j = (0xFF << 24) | (v << 16) | (v << 8) | v; | ||||
| 			glerminal_layer_color(i, j); | ||||
|  | ||||
| 			glerminal_layer_scale(i, (i / static_cast<float>(WALL_LAYERS)) + 1); | ||||
| 		} | ||||
|  | ||||
| 		for (int i = 0; i < GRID_WIDTH; i++) | ||||
| 		{ | ||||
| 			for (int j = 0; j < GRID_HEIGHT; j++) | ||||
| 			for (int j = 0; j < GRID_HEIGHT + 2; j++) | ||||
| 			{ | ||||
| 				for (int k = 0; k < WALL_LAYERS; k++) | ||||
| 				{ | ||||
| 					if (i == 0 || j == 0 || i == GRID_WIDTH - 1 || j == GRID_HEIGHT - 1) | ||||
| 					const unsigned char v = (k + 1) * (256 / WALL_LAYERS) - 1; | ||||
| 					const unsigned int c = (0xFF << 24) | (v << 16) | (v << 8) | v; | ||||
|  | ||||
| 					if (i == 1 || j == 1 || i == GRID_WIDTH || j == GRID_HEIGHT) | ||||
| 					{ | ||||
| 						glerminal_set(i, j, k, floor); | ||||
| 					} | ||||
| @@ -41,22 +35,25 @@ namespace | ||||
| 					{ | ||||
| 						glerminal_set(i, j, k, wall); | ||||
| 					} | ||||
|  | ||||
| 					glerminal_color(i, j, k, c); | ||||
| 					glerminal_scale(i, j, k, (k / static_cast<float>(WALL_LAYERS)) + 1); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void mainloop(float dt) | ||||
| 	void mainloop(double dt) | ||||
| 	{ | ||||
| 		static float time = 0; | ||||
| 		static double time = 0; | ||||
| 		time += dt; | ||||
|  | ||||
| 		const float cx = GRID_WIDTH / 2.0f * cosf(time / 2) + GRID_WIDTH / 2.0f; | ||||
| 		const float cy = GRID_HEIGHT / 2.0f * sinf(time / 2) + GRID_HEIGHT / 2.0f; | ||||
| 		const float cx = GRID_WIDTH / 2.0f * cosf(time / 2) + GRID_WIDTH / 2.0f + 0.5f; | ||||
| 		const float cy = GRID_HEIGHT / 2.0f * sinf(time / 2) + GRID_HEIGHT / 2.0f + 0.5f; | ||||
|  | ||||
| 		for (int i = 0; i < GRID_WIDTH; i++) | ||||
| 		for (int i = 0; i < GRID_WIDTH + 2; i++) | ||||
| 		{ | ||||
| 			for (int j = 0; j < GRID_HEIGHT; j++) | ||||
| 			for (int j = 0; j < GRID_HEIGHT + 2; j++) | ||||
| 			{ | ||||
| 				for (int k = 0; k < WALL_LAYERS; k++) | ||||
| 				{ | ||||
| @@ -83,5 +80,5 @@ namespace | ||||
|  | ||||
| int main(int argc, char** argv) | ||||
| { | ||||
| 	glerminal_run(init, mainloop); | ||||
| 	glerminal_run({init, mainloop}); | ||||
| } | ||||
| @@ -11,40 +11,41 @@ namespace | ||||
| 		 | ||||
| 		glerminal_load_sprites_file("resources/towers.png"); | ||||
|  | ||||
| 		for (int i = 0; i < LAYER_COUNT; i++) | ||||
| 		{ | ||||
| 			constexpr unsigned char c = 16; | ||||
| 			const unsigned char v = (255 - c) * powf((256 / LAYER_COUNT * i - 1) / 256.0f, 2.0f) + c; | ||||
| 			const unsigned int j = (0xFF << 24) | (v << 16) | (v << 8) | v; | ||||
| 			glerminal_layer_color(i, j); | ||||
| 			glerminal_layer_scale(i, i / static_cast<float>(LAYER_COUNT) + 1); | ||||
| 		} | ||||
|  | ||||
| 		for (int i = 0; i < GRID_WIDTH; i++) | ||||
| 		for (int i = 0; i < GRID_WIDTH + 2; i++) | ||||
| 		{ | ||||
| 			for (int j = 0; j < GRID_HEIGHT; j++) | ||||
| 			for (int j = 0; j < GRID_HEIGHT + 2; j++) | ||||
| 			{ | ||||
| 				const int c = rand() % (LAYER_COUNT * 3 / 4) + LAYER_COUNT / 4; | ||||
| 				for (int k = 0; k < c; k++) | ||||
| 				{ | ||||
| 					glerminal_set(i, j, k, 1); | ||||
| 				} | ||||
|  | ||||
| 				for (int k = 0; k < LAYER_COUNT; k++) | ||||
| 				{ | ||||
| 					constexpr unsigned char min = 16; | ||||
| 					const unsigned char v = (255 - min) * powf((256 / LAYER_COUNT * k - 1) / 256.0f, 2.0f) + min; | ||||
| 					const unsigned int col = (0xFF << 24) | (v << 16) | (v << 8) | v; | ||||
| 					glerminal_color(i, j, k, col); | ||||
| 					glerminal_scale(i, j, k, k / static_cast<float>(LAYER_COUNT) + 1); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void mainloop(float dt) | ||||
| 	void mainloop(double dt) | ||||
| 	{ | ||||
| 		static float time = 0; | ||||
| 		static double time = 0; | ||||
|  | ||||
| 		time += dt; | ||||
|  | ||||
| 		const float cx = (GRID_WIDTH / 2.0f) * cosf(time / 3.1415f) + (GRID_WIDTH / 2.0f); | ||||
| 		const float cy = (GRID_HEIGHT / 2.0f) * sinf(time / 3.1415f) + (GRID_HEIGHT / 2.0f); | ||||
| 		const float cx = (GRID_WIDTH / 2.0f) * cosf(time / 3.1415f) + (GRID_WIDTH / 2.0f) + 0.5f; | ||||
| 		const float cy = (GRID_HEIGHT / 2.0f) * sinf(time / 3.1415f) + (GRID_HEIGHT / 2.0f) + 0.5f; | ||||
|  | ||||
| 		for (int i = 0; i < GRID_WIDTH; i++) | ||||
| 		for (int i = 0; i < GRID_WIDTH + 2; i++) | ||||
| 		{ | ||||
| 			for (int j = 0; j < GRID_WIDTH; j++) | ||||
| 			for (int j = 0; j < GRID_WIDTH + 2; j++) | ||||
| 			{ | ||||
| 				for (int k = 0; k < LAYER_COUNT; k++) | ||||
| 				{ | ||||
| @@ -63,5 +64,5 @@ namespace | ||||
|  | ||||
| int main(int argc, char** argv) | ||||
| { | ||||
| 	glerminal_run(init, mainloop); | ||||
| 	glerminal_run({init, mainloop}); | ||||
| } | ||||
| @@ -9,14 +9,24 @@ extern "C" | ||||
| #endif | ||||
|  | ||||
| typedef void (*glerminal_init_cb)(); | ||||
| typedef void (*glerminal_main_cb)(float dt); | ||||
| typedef void (*glerminal_main_cb)(double dt); | ||||
| typedef void (*glerminal_keys_cb)(int key); | ||||
| typedef void (*glerminal_mousemoved_cb)(double x, double y); | ||||
| typedef void (*glerminal_mousepress_cb)(int button, double x, double y); | ||||
|  | ||||
| typedef struct { | ||||
|  glerminal_init_cb init; | ||||
|  glerminal_main_cb main; | ||||
|  glerminal_keys_cb keypress, keyrelease; | ||||
|  glerminal_mousemoved_cb moved; | ||||
|  glerminal_mousepress_cb mousepress, mouserelease; | ||||
| } glerminal_init_params; | ||||
|  | ||||
| /** | ||||
|  * @brief Call init once, then run the application's mainloop | ||||
|  * @param init initialization callback | ||||
|  * @param main main calllback | ||||
|  * @param params Initialization parameters | ||||
|  */ | ||||
| void glerminal_run(glerminal_init_cb init, glerminal_main_cb main); | ||||
| void glerminal_run(glerminal_init_params params); | ||||
|  | ||||
| void glerminal_quit(); | ||||
|  | ||||
| @@ -27,48 +37,52 @@ void glerminal_flush(); | ||||
|  | ||||
| /** | ||||
|  * @brief Set a cell's sprite | ||||
|  * @param x position of the cell in the range [0, 40) | ||||
|  * @param y position of the cell in the range [0, 25) | ||||
|  * @param layer layer of the cell in the range [0, 8) | ||||
|  * @param sprite sprite's index in the range [0, 256) | ||||
|  * @param x position of the cell in the range [0, GRID_WIDTH) | ||||
|  * @param y position of the cell in the range [0, GRID_HEIGHT) | ||||
|  * @param layer layer of the cell in the range [0, LAYER_COUNT) | ||||
|  * @param sprite sprite's index in the range [0, 4096) | ||||
|  */ | ||||
| void glerminal_set(unsigned char x, unsigned char y, unsigned char layer, unsigned char sprite); | ||||
| void glerminal_set(int x, int y, int layer, unsigned short sprite); | ||||
| /** | ||||
|  * @brief Get a cell's sprite | ||||
|  * @param x position of the cell in the range [0, 40) | ||||
|  * @param y position of the cell in the range [0, 25) | ||||
|  * @param layer layer of the cell in the range [0, 8) | ||||
|  * @param x position of the cell in the range [0, GRID_WIDTH) | ||||
|  * @param y position of the cell in the range [0, GRID_HEIGHT) | ||||
|  * @param layer layer of the cell in the range [0, LAYER_COUNT) | ||||
|  * @return sprite index currently assigned to the cell | ||||
|  */ | ||||
| unsigned char glerminal_get(unsigned char x, unsigned char y, unsigned char layer); | ||||
| unsigned short glerminal_get(int x, int y, int layer); | ||||
| /** | ||||
|  * @brief Set a cell's offset | ||||
|  * @param x position of the cell in the range [0, 40) | ||||
|  * @param y position of the cell in the range [0, 25) | ||||
|  * @param layer layer of the cell in the range [0, 8) | ||||
|  * @param x_offset offset of the cell on the x axis in the range [-128, 127], where 0 is no offset | ||||
|  * @param y_offset offset of the cell on the y axis in the range [-128, 127], where 0 is no offset | ||||
|  * @param x position of the cell in the range [0, GRID_WIDTH) | ||||
|  * @param y position of the cell in the range [0, GRID_HEIGHT) | ||||
|  * @param layer layer of the cell in the range [0, LAYER_COUNT) | ||||
|  * @param x_offset offset of the cell on the x axis in cells | ||||
|  * @param y_offset offset of the cell on the y axis in cells | ||||
|  */ | ||||
| void glerminal_offset(unsigned char x, unsigned char y, unsigned char layer, float x_offset, float y_offset); | ||||
| void glerminal_offset(int x, int y, int layer, float x_offset, float y_offset); | ||||
|  | ||||
| /** | ||||
|  * @brief Set a layer's color | ||||
|  * @param layer The layer to modify | ||||
|  * @brief Set a cell's color | ||||
|  * @param x position of the cell in the range [0, GRID_WIDTH) | ||||
|  * @param y position of the cell in the range [0, GRID_HEIGHT) | ||||
|  * @param layer layer of the cell in the range [0, LAYER_COUNT) | ||||
|  * @param color The new color | ||||
|  */ | ||||
| void glerminal_layer_color(unsigned char layer, unsigned int color); | ||||
| void glerminal_color(int x, int y, int layer, unsigned int color); | ||||
| /** | ||||
|  * @brief Set a layer's scale | ||||
|  * @param layer The layer to modify | ||||
|  * @brief Set a cell's scale | ||||
|  * @param x position of the cell in the range [0, GRID_WIDTH) | ||||
|  * @param y position of the cell in the range [0, GRID_HEIGHT) | ||||
|  * @param layer layer of the cell in the range [0, LAYER_COUNT) | ||||
|  * @param scale The new scale | ||||
|  */ | ||||
| void glerminal_layer_scale(unsigned char layer, float scale); | ||||
| void glerminal_scale(int x, int y, int layer, float scale); | ||||
|  | ||||
| /** | ||||
|  * @brief Load sprites from a png file | ||||
|  * @param filename Name of the png file | ||||
|  */ | ||||
| void glerminal_load_sprites_file(const char* filename); | ||||
| int glerminal_load_sprites_file(const char* filename); | ||||
|  | ||||
| /** | ||||
|  * @brief Load sprites from memory | ||||
| @@ -76,7 +90,9 @@ void glerminal_load_sprites_file(const char* filename); | ||||
|  * @param height height of the atlas in sprites | ||||
|  * @param buffer the in-memory atlas | ||||
|  */ | ||||
| void glerminal_load_sprites_buffer(unsigned char width, unsigned char height, const unsigned int* buffer); | ||||
| int glerminal_load_sprites_buffer(unsigned char width, unsigned char height, const unsigned int* buffer); | ||||
|  | ||||
| void glerminal_sound(const char* name); | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
|   | ||||
| @@ -16,7 +16,8 @@ enum | ||||
| 	GRID_WIDTH = @GLERMINAL_GRID_WIDTH@, | ||||
| 	GRID_HEIGHT = @GLERMINAL_GRID_HEIGHT@, | ||||
| 	LAYER_COUNT = @GLERMINAL_LAYER_COUNT@, | ||||
| 	CELL_SCALE = @GLERMINAL_CELL_SCALE@ | ||||
| 	CELL_SCALE = @GLERMINAL_CELL_SCALE@, | ||||
| 	CELL_SIZE = @GLERMINAL_CELL_SIZE@ | ||||
| }; | ||||
|  | ||||
| #ifdef __cplusplus | ||||
|   | ||||
| @@ -2,30 +2,36 @@ | ||||
| #define GLERMINAL_PRIVATE_H | ||||
|  | ||||
| #include "glerminal.h" | ||||
| #include "miniaudio.h" | ||||
|  | ||||
| #include <stb_image.h> | ||||
| #include <glad/glad.h> | ||||
| #include <GLFW/glfw3.h> | ||||
|  | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
| #include <stdexcept> | ||||
| #include <map> | ||||
| #include <string> | ||||
|  | ||||
| namespace glerminal | ||||
| { | ||||
| 	constexpr unsigned int CELL_SIZE = 8; | ||||
| 	constexpr unsigned int MAX_SPRITES_ROW = 64; | ||||
| 	constexpr unsigned int MAX_SPRITES = MAX_SPRITES_ROW * MAX_SPRITES_ROW; | ||||
| 	constexpr unsigned int GRID_WIDTH = ::GRID_WIDTH; | ||||
| 	constexpr unsigned int GRID_HEIGHT = ::GRID_HEIGHT; | ||||
| 	constexpr unsigned int LAYER_COUNT = ::LAYER_COUNT; | ||||
| 	constexpr unsigned int CELL_SCALE = ::CELL_SCALE; | ||||
| 	constexpr unsigned int CELL_SIZE = ::CELL_SIZE; | ||||
| 	constexpr unsigned int GRID_AREA = GRID_WIDTH * GRID_HEIGHT; | ||||
| 	constexpr unsigned int SCREEN_WIDTH = GRID_WIDTH * CELL_SIZE * CELL_SCALE; | ||||
| 	constexpr unsigned int SCREEN_HEIGHT = GRID_HEIGHT * CELL_SIZE * CELL_SCALE; | ||||
| 	constexpr unsigned int SOUND_CHANNELS = 8; | ||||
|  | ||||
| 	constexpr unsigned int GRID_AREA_2 = (GRID_WIDTH + 2) * (GRID_HEIGHT + 2); | ||||
|  | ||||
| 	class glerminal | ||||
| 	{ | ||||
| 	public: | ||||
| 		glerminal(glerminal_init_cb init, glerminal_main_cb main); | ||||
| 		explicit glerminal(glerminal_init_params params); | ||||
| 		~glerminal(); | ||||
|  | ||||
| 		glerminal(const glerminal&) = delete; | ||||
| @@ -39,12 +45,14 @@ namespace glerminal | ||||
|  | ||||
| 		void flush(); | ||||
|  | ||||
| 		void set(unsigned char x, unsigned char y, unsigned char layer, unsigned char sprite); | ||||
| 		unsigned char get(unsigned char x, unsigned char y, unsigned char layer) const; | ||||
| 		void offset(unsigned char x, unsigned char y, unsigned char layer, float x_offset, float y_offset); | ||||
| 		void layer_color(unsigned char layer, unsigned int color); | ||||
| 		void layer_scale(unsigned char layer, float scale); | ||||
| 		void set(int x, int y, int layer, unsigned short sprite); | ||||
| 		unsigned short get(int x, int y, int layer) const; | ||||
| 		void offset(int x, int y, int layer, float x_offset, float y_offset); | ||||
| 		void color(int x, int y, int layer, unsigned int color); | ||||
| 		void scale(int x,int y, int layer, float scale); | ||||
| 		void load_atlas(unsigned char w, unsigned char h, const unsigned int* data); | ||||
| 		bool load_sound(const char* name); | ||||
| 		void play_sound(const char* name); | ||||
|  | ||||
| 	private: | ||||
| 		// glfw data | ||||
| @@ -65,25 +73,26 @@ namespace glerminal | ||||
| 		unsigned int m_framebuffer_backing_texture; | ||||
| 		unsigned int m_screen_framebuffer; | ||||
| 		unsigned int m_screen_framebuffer_backing_texture; | ||||
| 		unsigned int m_layer_colors_buffer; | ||||
| 		unsigned int m_layer_scales_buffer; | ||||
| 		unsigned int m_screen_size_uniform_location; | ||||
| 		unsigned int m_palette_uniform_location; | ||||
| 		unsigned int m_colors_instance_vbo; | ||||
| 		unsigned int m_scales_instance_vbo; | ||||
|  | ||||
| 		// per-cell data | ||||
|  | ||||
| 		unsigned char m_cells[GRID_AREA * LAYER_COUNT]; | ||||
| 		float m_offsets[GRID_AREA * LAYER_COUNT * 2]; | ||||
|  | ||||
| 		// per-layer data | ||||
|  | ||||
| 		float m_layer_colors[LAYER_COUNT * 4]; | ||||
| 		float m_layer_scales[LAYER_COUNT]; | ||||
| 		unsigned short m_cells[GRID_AREA_2 * LAYER_COUNT]; | ||||
| 		float m_offsets[GRID_AREA_2 * LAYER_COUNT * 2]; | ||||
| 		unsigned char m_colors[GRID_AREA_2 * LAYER_COUNT * 4]; | ||||
| 		float m_scales[GRID_AREA_2 * LAYER_COUNT]; | ||||
|  | ||||
| 		// library state | ||||
|  | ||||
| 		unsigned int m_sprites[CELL_SIZE * CELL_SIZE * (1 << (8 * sizeof(*m_cells)))]; | ||||
| 		unsigned int m_sprites[(CELL_SIZE + 2) * (CELL_SIZE + 2) * MAX_SPRITES]; | ||||
| 		glerminal_main_cb m_main; | ||||
| 		glerminal_keys_cb m_keypressed, m_keyreleased; | ||||
| 		glerminal_mousemoved_cb m_mousemoved; | ||||
| 		glerminal_mousepress_cb m_mousepressed, m_mousereleased; | ||||
|  | ||||
| 		ma_engine m_audio_engine; | ||||
| 		std::map<std::string, ma_sound> m_sounds; | ||||
|  | ||||
| #ifdef GLERMINAL_OPENGL_DEBUG_CONTEXT | ||||
| 		mutable std::ofstream m_log; | ||||
| @@ -93,12 +102,20 @@ namespace glerminal | ||||
| 		void init_glfw(); | ||||
| 		void init_gl(); | ||||
|  | ||||
| 		void init_audio(); | ||||
|  | ||||
| 		void deinit_glfw(); | ||||
| 		void deinit_gl(); | ||||
|  | ||||
| 		void deinit_audio(); | ||||
|  | ||||
| 		void update_sprites(); | ||||
| 		void update_layer_colors(); | ||||
| 		void update_layer_scales(); | ||||
| 		void update_colors(); | ||||
| 		void update_scales(); | ||||
|  | ||||
| 		static void glfw_key_handler(GLFWwindow* window, int key, int scancode, int action, int mods); | ||||
| 		static void glfw_mousemoved_handler(GLFWwindow* window, double x, double y); | ||||
| 		static void glfw_mousepress_handler(GLFWwindow* window, int button, int action, int mods); | ||||
| 	}; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,14 @@ | ||||
| #define STB_IMAGE_IMPLEMENTATION | ||||
| #define STBI_ONLY_PNG | ||||
| #define STBI_MAX_DIMENSIONS 128 | ||||
| #define STBI_MAX_DIMENSIONS 2048 | ||||
| #include "glerminal-private.h" | ||||
|  | ||||
| #define GRID_SIZE_UNIFORM_NAME "grid_size" | ||||
| #define SPRITES_UNIFORM_NAME "sprites" | ||||
| #define LAYERS_UNIFORM_NAME "layers" | ||||
| #define LAYER_COUNT_UNIFORM_NAME "layer_count" | ||||
| #define ATLAS_WIDTH_UNIFORM_NAME "atlas_width" | ||||
| #define CELL_SIZE_UNIFORM_NAME "cell_size" | ||||
|  | ||||
| namespace | ||||
| { | ||||
| @@ -30,14 +32,15 @@ namespace | ||||
| 		"layout (location = 0) in vec2 position;\n" | ||||
| 		"layout (location = 1) in vec2 offset;\n" | ||||
| 		"layout (location = 2) in int sprite;\n" | ||||
| 		"layout (location = 3) in vec4 color;\n" | ||||
| 		"layout (location = 4) in float scale;\n" | ||||
| 		"uniform float " CELL_SIZE_UNIFORM_NAME ";\n" | ||||
| 		"uniform vec4 " GRID_SIZE_UNIFORM_NAME ";\n" | ||||
| 		"layout (std430, binding = 0) buffer LayerScales" | ||||
| 		"{\n" | ||||
| 		"	float scales[];\n" | ||||
| 		"} lss;\n" | ||||
| 		"uniform int " ATLAS_WIDTH_UNIFORM_NAME ";\n" | ||||
| 		"out VS_OUT {\n" | ||||
| 		"	flat int sprite;\n" | ||||
| 		"	flat int layer;\n" | ||||
| 		"	flat vec4 layer_color;\n" | ||||
| 		"	vec2 texcoord;\n" | ||||
| 		"} vs_out;\n" | ||||
| 		"void main()\n" | ||||
| @@ -46,9 +49,10 @@ namespace | ||||
| 		"	vec2 scaled_offset = 2 * offset * " GRID_SIZE_UNIFORM_NAME ".zw;\n" | ||||
| 		"	vs_out.sprite = sprite;\n" | ||||
| 		"	vs_out.layer = layer;\n" | ||||
| 		"	vs_out.texcoord = vec2(position.x + 1, -position.y);\n" | ||||
| 		"	vec2 cell_position = vec2(lss.scales[layer] + (gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) - " GRID_SIZE_UNIFORM_NAME ".x * floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) * " GRID_SIZE_UNIFORM_NAME ".z), -floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) * " GRID_SIZE_UNIFORM_NAME ".z));\n" | ||||
| 		"	vec2 temp = ((position + vec2(-0.5, 0.5)) * lss.scales[layer] + cell_position + vec2(0.5, -0.5)) * " GRID_SIZE_UNIFORM_NAME ".zw * 2 + vec2(-1, 1);\n" | ||||
| 		"	vs_out.layer_color = color;\n" | ||||
| 		"	vs_out.texcoord = vec2(sprite % " ATLAS_WIDTH_UNIFORM_NAME " + position.x + 1, (sprite / " ATLAS_WIDTH_UNIFORM_NAME ") - position.y) / vec2(" ATLAS_WIDTH_UNIFORM_NAME ") + vec2(-(2 * position.x + 1) * " GRID_SIZE_UNIFORM_NAME ".z / (2 * " CELL_SIZE_UNIFORM_NAME "), (2 * position.y + 1) * " GRID_SIZE_UNIFORM_NAME ".z / (2 * " CELL_SIZE_UNIFORM_NAME "));\n" | ||||
| 		"	vec2 cell_position = vec2(scale + (gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) - " GRID_SIZE_UNIFORM_NAME ".x * floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) / " GRID_SIZE_UNIFORM_NAME ".x), -floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) / " GRID_SIZE_UNIFORM_NAME ".x));\n" | ||||
| 		"	vec2 temp = ((position + vec2(-0.5, 0.5)) * scale + cell_position + vec2(-0.5, 0.5)) * " GRID_SIZE_UNIFORM_NAME ".zw * 2 + vec2(-1, 1);\n" | ||||
| 		"	gl_Position = vec4(scaled_offset.x + temp.x, scaled_offset.y - temp.y, 0, 1);\n" | ||||
| 		"}"; | ||||
|  | ||||
| @@ -60,13 +64,14 @@ namespace | ||||
| 		"layout (location = 0) in vec2 position;\n" | ||||
| 		"layout (location = 1) in vec2 offset;\n" | ||||
| 		"layout (location = 2) in int sprite;\n" | ||||
| 		"layout (location = 3) in vec4 color;\n" | ||||
| 		"layout (location = 4) in float scale;\n" | ||||
| 		"uniform float " CELL_SIZE_UNIFORM_NAME ";\n" | ||||
| 		"uniform vec4 " GRID_SIZE_UNIFORM_NAME ";\n" | ||||
| 		"layout (std430, binding = 0) buffer LayerScales" | ||||
| 		"{\n" | ||||
| 		"	float scales[];\n" | ||||
| 		"} lss;\n" | ||||
| 		"uniform int " ATLAS_WIDTH_UNIFORM_NAME ";\n" | ||||
| 		"out VS_OUT {\n" | ||||
| 		"	flat int sprite;\n" | ||||
| 		"	flat vec4 layer_color;\n" | ||||
| 		"	vec2 texcoord;\n" | ||||
| 		"} vs_out;\n" | ||||
| 		"void main()\n" | ||||
| @@ -75,9 +80,10 @@ namespace | ||||
| 		"	gl_Layer = layer;\n" | ||||
| 		"	vec2 scaled_offset = 2 * offset * " GRID_SIZE_UNIFORM_NAME ".zw;\n" | ||||
| 		"	vs_out.sprite = sprite;\n" | ||||
| 		"	vs_out.texcoord = vec2(position.x + 1, -position.y);\n" | ||||
| 		"	vec2 cell_position = vec2(lss.scales[layer] + (gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) - " GRID_SIZE_UNIFORM_NAME ".x * floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) * " GRID_SIZE_UNIFORM_NAME ".z), -floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) * " GRID_SIZE_UNIFORM_NAME ".z));\n" | ||||
| 		"	vec2 temp = ((position + vec2(-0.5, 0.5)) * lss.scales[layer] + cell_position + vec2(0.5, -0.5)) * " GRID_SIZE_UNIFORM_NAME ".zw * 2 + vec2(-1, 1);\n" | ||||
| 		"	vs_out.texcoord = vec2(sprite % " ATLAS_WIDTH_UNIFORM_NAME " + position.x + 1, (sprite / " ATLAS_WIDTH_UNIFORM_NAME ") - position.y) / vec2(" ATLAS_WIDTH_UNIFORM_NAME ") + vec2(-(2 * position.x + 1) * " GRID_SIZE_UNIFORM_NAME ".z / (2 * " CELL_SIZE_UNIFORM_NAME "), (2 * position.y + 1) * " GRID_SIZE_UNIFORM_NAME ".z / (2 * " CELL_SIZE_UNIFORM_NAME "));\n" | ||||
| 		"	vs_out.layer_color = color;\n" | ||||
| 		"	vec2 cell_position = vec2(scale + (gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) - " GRID_SIZE_UNIFORM_NAME ".x * floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) / " GRID_SIZE_UNIFORM_NAME ".x), -floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) / " GRID_SIZE_UNIFORM_NAME ".x));\n" | ||||
| 		"	vec2 temp = ((position + vec2(-0.5, 0.5)) * scale + cell_position + vec2(-0.5, 0.5)) * " GRID_SIZE_UNIFORM_NAME ".zw * 2 + vec2(-1, 1);\n" | ||||
| 		"	gl_Position = vec4(scaled_offset.x + temp.x, scaled_offset.y - temp.y, 0, 1);\n" | ||||
| 		"}"; | ||||
|  | ||||
| @@ -91,25 +97,30 @@ namespace | ||||
| 		"in VS_OUT {\n" | ||||
| 		"	flat int sprite;\n" | ||||
| 		"	flat int layer;\n" | ||||
| 		"	flat vec4 layer_color;\n" | ||||
| 		"	vec2 texcoord;\n" | ||||
| 		"} gs_in[];\n" | ||||
| 		"flat out int sprite;\n" | ||||
| 		"flat out vec4 layer_color;\n" | ||||
| 		"out vec2 texcoord;\n" | ||||
| 		"void main()\n" | ||||
| 		"{\n" | ||||
| 		"	gl_Layer = gs_in[0].layer;\n" | ||||
| 		"	gl_Position = gl_in[0].gl_Position;\n" | ||||
| 		"	sprite = gs_in[0].sprite;\n" | ||||
| 		"	layer_color = gs_in[0].layer_color;\n" | ||||
| 		"	texcoord = gs_in[0].texcoord;\n" | ||||
| 		"	EmitVertex();\n" | ||||
| 		"	gl_Layer = gs_in[1].layer;\n" | ||||
| 		"	gl_Position = gl_in[1].gl_Position;\n" | ||||
| 		"	sprite = gs_in[1].sprite;\n" | ||||
| 		"	layer_color = gs_in[1].layer_color;\n" | ||||
| 		"	texcoord = gs_in[1].texcoord;\n" | ||||
| 		"	EmitVertex();\n" | ||||
| 		"	gl_Layer = gs_in[2].layer;\n" | ||||
| 		"	gl_Position = gl_in[2].gl_Position;\n" | ||||
| 		"	sprite = gs_in[2].sprite;\n" | ||||
| 		"	layer_color = gs_in[2].layer_color;\n" | ||||
| 		"	texcoord = gs_in[2].texcoord;\n" | ||||
| 		"	EmitVertex();\n" | ||||
| 		"	EndPrimitive();\n" | ||||
| @@ -119,13 +130,14 @@ namespace | ||||
|  | ||||
| 	constexpr char FRAGMENT_SHADER_SOURCE[] = | ||||
| 		"#version 450 core\n" | ||||
| 		"in vec2 texcoord;\n" | ||||
| 		"flat in int sprite;\n" | ||||
| 		"layout (binding = 0) uniform sampler2DArray " SPRITES_UNIFORM_NAME ";\n" | ||||
| 		"flat vec4 layer_color;\n" | ||||
| 		"in vec2 texcoord;\n" | ||||
| 		"layout (binding = 2) uniform sampler2D " SPRITES_UNIFORM_NAME ";\n" | ||||
| 		"out vec4 FragColor;\n" | ||||
| 		"void main()\n" | ||||
| 		"{\n" | ||||
| 		"	FragColor = texture(" SPRITES_UNIFORM_NAME ", vec3(texcoord, sprite));\n" | ||||
| 		"	FragColor = layer_color * texture(" SPRITES_UNIFORM_NAME ", texcoord);\n" | ||||
| 		"}"; | ||||
|  | ||||
| 	// note: AMD_vertex_shader_layer support required | ||||
| @@ -133,13 +145,14 @@ namespace | ||||
| 		"#version 450 core\n" | ||||
| 		"in VS_OUT {\n" | ||||
| 		"	flat int sprite;\n" | ||||
| 		"	flat vec4 layer_color;\n" | ||||
| 		"	vec2 texcoord;\n" | ||||
| 		"} fs_in;\n" | ||||
| 		"layout (binding = 0) uniform sampler2DArray " SPRITES_UNIFORM_NAME ";\n" | ||||
| 		"layout (binding = 2) uniform sampler2D " SPRITES_UNIFORM_NAME ";\n" | ||||
| 		"out vec4 FragColor;\n" | ||||
| 		"void main()\n" | ||||
| 		"{\n" | ||||
| 		"	FragColor = texture(" SPRITES_UNIFORM_NAME ", vec3(fs_in.texcoord, fs_in.sprite));\n" | ||||
| 		"	FragColor = fs_in.layer_color * texture(" SPRITES_UNIFORM_NAME ", fs_in.texcoord);\n" | ||||
| 		"}"; | ||||
|  | ||||
| 	constexpr const char* FRAGMENT_SHADER_SOURCE_PTR = FRAGMENT_SHADER_SOURCE; | ||||
| @@ -161,10 +174,6 @@ namespace | ||||
| 		"#version 450 core\n" | ||||
| 		"in vec2 texcoord;\n" | ||||
| 		"layout (binding = 1) uniform sampler2DArray " LAYERS_UNIFORM_NAME ";\n" | ||||
| 		"layout (std430, binding = 1) buffer LayerColors" | ||||
| 		"{\n" | ||||
| 		"	vec4 colors[];\n" | ||||
| 		"} lcs;\n" | ||||
| 		"uniform int " LAYER_COUNT_UNIFORM_NAME ";\n" | ||||
| 		"out vec4 FragColor;\n" | ||||
| 		"void main()\n" | ||||
| @@ -172,7 +181,7 @@ namespace | ||||
| 		"	vec3 current_color = vec3(0);\n" | ||||
| 		"	for (int i = 0; i < " LAYER_COUNT_UNIFORM_NAME "; i++)\n" | ||||
| 		"	{\n" | ||||
| 		"		vec4 texsample = lcs.colors[i] * texture(" LAYERS_UNIFORM_NAME ", vec3(texcoord, i));\n" | ||||
| 		"		vec4 texsample = texture(" LAYERS_UNIFORM_NAME ", vec3(texcoord, i));\n" | ||||
| 		"		current_color = mix(current_color, texsample.rgb, texsample.a);\n" | ||||
| 		"	}\n" | ||||
| 		"	FragColor = vec4(current_color, 1);\n" | ||||
| @@ -183,13 +192,18 @@ namespace | ||||
|  | ||||
| namespace glerminal | ||||
| { | ||||
| 	glerminal::glerminal(glerminal_init_cb init, glerminal_main_cb main) : | ||||
| 		m_main(main), | ||||
| 	glerminal::glerminal(glerminal_init_params params) : | ||||
| 		m_main(params.main), | ||||
| 		m_keypressed(params.keypress), | ||||
| 		m_keyreleased(params.keyrelease), | ||||
| 		m_mousemoved(params.moved), | ||||
| 		m_mousepressed(params.mousepress), | ||||
| 		m_mousereleased(params.mouserelease), | ||||
| 		m_cells{ }, | ||||
| 		m_offsets{ }, | ||||
| 		m_sprites{ }, | ||||
| 		m_layer_colors{ }, | ||||
| 		m_layer_scales{ } | ||||
| 		m_colors{ }, | ||||
| 		m_scales{ } | ||||
| #ifdef GLERMINAL_OPENGL_DEBUG_CONTEXT | ||||
| 		, m_log("log.txt") | ||||
| #endif | ||||
| @@ -200,7 +214,7 @@ namespace glerminal | ||||
| 		} | ||||
|  | ||||
| 		// unsure if this should be an error | ||||
| 		if (!init) | ||||
| 		if (!params.init) | ||||
| 		{ | ||||
| 			throw std::runtime_error("No init callback provided."); | ||||
| 		} | ||||
| @@ -210,22 +224,24 @@ namespace glerminal | ||||
| 			throw std::runtime_error("No main callback provided."); | ||||
| 		} | ||||
|  | ||||
| 		for (int i = 0; i < LAYER_COUNT; i++) | ||||
| 		for (int i = 0; i < GRID_AREA_2 * LAYER_COUNT; i++) | ||||
| 		{ | ||||
| 			m_layer_colors[i * 4 + 0] = m_layer_colors[i * 4 + 1] = m_layer_colors[i * 4 + 2] = m_layer_colors[i * 4 + 3] = 1; | ||||
| 			m_layer_scales[i] = 1; | ||||
| 			m_colors[i * 4 + 0] = m_colors[i * 4 + 1] = m_colors[i * 4 + 2] = m_colors[i * 4 + 3] = 255; | ||||
| 			m_scales[i] = 1; | ||||
| 		} | ||||
|  | ||||
| 		init_glfw(); | ||||
| 		init_gl(); | ||||
| 		init_audio(); | ||||
|  | ||||
| 		GLERMINAL_G = this; | ||||
|  | ||||
| 		init(); | ||||
| 		params.init(); | ||||
| 	} | ||||
|  | ||||
| 	glerminal::~glerminal() | ||||
| 	{ | ||||
| 		deinit_audio(); | ||||
| 		deinit_gl(); | ||||
| 		deinit_glfw(); | ||||
|  | ||||
| @@ -234,12 +250,12 @@ namespace glerminal | ||||
|  | ||||
| 	void glerminal::run() | ||||
| 	{ | ||||
| 		float last = glfwGetTime(); | ||||
| 		double last = glfwGetTime(); | ||||
| 		while (!glfwWindowShouldClose(m_window)) | ||||
| 		{ | ||||
| 			glfwPollEvents(); | ||||
|  | ||||
| 			const float current = glfwGetTime(); | ||||
| 			const double current = glfwGetTime(); | ||||
|  | ||||
| 			m_main(current - last); | ||||
|  | ||||
| @@ -254,17 +270,17 @@ namespace glerminal | ||||
|  | ||||
| 	void glerminal::flush() | ||||
| 	{ | ||||
| 		glNamedBufferData(m_sprites_instance_vbo, sizeof(m_cells), m_cells, GL_STREAM_DRAW); | ||||
| 		glNamedBufferData(m_offsets_instance_vbo, sizeof(m_offsets), m_offsets, GL_STREAM_DRAW); | ||||
| 		glNamedBufferSubData(m_sprites_instance_vbo, 0, sizeof(m_cells), m_cells); | ||||
| 		glNamedBufferSubData(m_offsets_instance_vbo, 0, sizeof(m_offsets), m_offsets); | ||||
| 		update_sprites(); | ||||
| 		update_layer_colors(); | ||||
| 		update_layer_scales(); | ||||
| 		update_colors(); | ||||
| 		update_scales(); | ||||
|  | ||||
| 		glUseProgram(m_program); | ||||
| 		glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); | ||||
| 		glBindVertexArray(m_vao); | ||||
| 		glClear(GL_COLOR_BUFFER_BIT); | ||||
| 		glDrawArraysInstanced(GL_TRIANGLES, 0, 6, GRID_AREA * LAYER_COUNT); | ||||
| 		glDrawArraysInstanced(GL_TRIANGLES, 0, 6, GRID_AREA_2 * LAYER_COUNT); | ||||
|  | ||||
| 		glUseProgram(m_screen_program); | ||||
| 		glBindFramebuffer(GL_FRAMEBUFFER, m_screen_framebuffer); | ||||
| @@ -272,24 +288,25 @@ namespace glerminal | ||||
| 		glClear(GL_COLOR_BUFFER_BIT); | ||||
| 		glDrawArrays(GL_TRIANGLES, 0, 6); | ||||
|  | ||||
|     glBindFramebuffer(GL_FRAMEBUFFER, 0); | ||||
|     glClear(GL_COLOR_BUFFER_BIT); | ||||
| 		glBlitNamedFramebuffer(m_screen_framebuffer, 0, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST); | ||||
|  | ||||
| 		glfwSwapBuffers(m_window); | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::set(unsigned char x, unsigned char y, unsigned char layer, unsigned char sprite) | ||||
| 	void glerminal::set(int x, int y, int layer, unsigned short sprite) | ||||
| 	{ | ||||
| 		if (x < GRID_WIDTH && y < GRID_HEIGHT && layer < LAYER_COUNT) | ||||
| 		if (x >= 0 && x < GRID_WIDTH + 2 && y >= 0 && y < GRID_HEIGHT + 2 && layer >= 0 && layer < LAYER_COUNT) | ||||
| 		{ | ||||
| 			m_cells[x + y * GRID_WIDTH + layer * GRID_AREA] = sprite; | ||||
| 			m_cells[x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2] = sprite; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	unsigned char glerminal::get(unsigned char x, unsigned char y, unsigned char layer) const | ||||
| 	unsigned short glerminal::get(int x, int y, int layer) const | ||||
| 	{ | ||||
| 		if (x < GRID_WIDTH && y < GRID_HEIGHT && layer < LAYER_COUNT) | ||||
| 		if (x >= 0 && x < GRID_WIDTH + 2 && y >= 0 && y < GRID_HEIGHT + 2 && layer >= 0 && layer < LAYER_COUNT) | ||||
| 		{ | ||||
| 			return m_cells[x + y * GRID_WIDTH + layer * GRID_AREA]; | ||||
| 			return m_cells[x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2]; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| @@ -297,50 +314,93 @@ namespace glerminal | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::offset(unsigned char x, unsigned char y, unsigned char layer, float x_offset, float y_offset) | ||||
| 	void glerminal::offset(int x, int y, int layer, float x_offset, float y_offset) | ||||
| 	{ | ||||
| 		if (x < GRID_WIDTH && y < GRID_HEIGHT && layer < LAYER_COUNT) | ||||
| 		if (x >= 0 && x < GRID_WIDTH + 2 && y >= 0 && y < GRID_HEIGHT + 2 && layer >= 0 && layer < LAYER_COUNT) | ||||
| 		{ | ||||
| 			m_offsets[2 * (x + y * GRID_WIDTH + layer * GRID_AREA) + 0] = x_offset; | ||||
| 			m_offsets[2 * (x + y * GRID_WIDTH + layer * GRID_AREA) + 1] = y_offset; | ||||
| 			m_offsets[2 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 0] = x_offset; | ||||
| 			m_offsets[2 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 1] = y_offset; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::layer_color(unsigned char layer, unsigned int color) | ||||
| 	void glerminal::color(int x, int y, int layer, unsigned int color) | ||||
| 	{ | ||||
| 		m_layer_colors[layer * 4 + 0] = ((color >>  0) & 0xFF) / 255.0f; | ||||
| 		m_layer_colors[layer * 4 + 1] = ((color >>  8) & 0xFF) / 255.0f; | ||||
| 		m_layer_colors[layer * 4 + 2] = ((color >> 16) & 0xFF) / 255.0f; | ||||
| 		m_layer_colors[layer * 4 + 3] = ((color >> 24) & 0xFF) / 255.0f; | ||||
| 		if (x >= 0 && x < GRID_WIDTH + 2 && y >= 0 && y < GRID_HEIGHT + 2 && layer >= 0 && layer < LAYER_COUNT) | ||||
| 		{ | ||||
| 			m_colors[4 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 0] = (color >>  0) & 0xFF; | ||||
| 			m_colors[4 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 1] = (color >>  8) & 0xFF; | ||||
| 			m_colors[4 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 2] = (color >> 16) & 0xFF; | ||||
| 			m_colors[4 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 3] = (color >> 24) & 0xFF; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::layer_scale(unsigned char layer, float scale) | ||||
| 	void glerminal::scale(int x, int y, int layer, float scale) | ||||
| 	{ | ||||
| 		m_layer_scales[layer] = scale; | ||||
| 		if (x >= 0 && x < GRID_WIDTH + 2 && y >= 0 && y < GRID_HEIGHT + 2 && layer >= 0 && layer < LAYER_COUNT) | ||||
| 		{ | ||||
| 			m_scales[x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2] = scale; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::load_atlas(unsigned char w, unsigned char h, const unsigned int* data) | ||||
| 	{ | ||||
| 		// each row of the atlas | ||||
| 		for (int j = 0; j < h; j++) | ||||
| 		for (int src_atlas_row = 0; src_atlas_row < h; src_atlas_row++) | ||||
| 		{ | ||||
| 			// each column of the atlas | ||||
| 			for (int i = 0; i < w; i++) | ||||
| 			for (int src_atlas_col = 0; src_atlas_col < w; src_atlas_col++) | ||||
| 			{ | ||||
| 				// each row of the individual sprite | ||||
| 				for (int k = 0; k < CELL_SIZE; k++) | ||||
| 				for (int sprite_row = 0; sprite_row < CELL_SIZE; sprite_row++) | ||||
| 				{ | ||||
| 					// offset from base address in atlas layout | ||||
| 					const unsigned int src_offset = i + k * w + j * w * CELL_SIZE; | ||||
| 					// offset from base address in array layout | ||||
| 					const unsigned int dst_offset = k + i * CELL_SIZE + j * w * CELL_SIZE; | ||||
| 					const unsigned int sprite_index = src_atlas_col + src_atlas_row * w; | ||||
| 					const unsigned int dst_atlas_row = sprite_index / MAX_SPRITES_ROW; | ||||
| 					const unsigned int dst_atlas_col = sprite_index % MAX_SPRITES_ROW; | ||||
|  | ||||
| 					memcpy(m_sprites + CELL_SIZE * dst_offset, data + CELL_SIZE * src_offset, CELL_SIZE * sizeof(unsigned int)); | ||||
| 					// offset from base address in source atlas layout | ||||
| 					const unsigned int src_offset = CELL_SIZE * (src_atlas_col + sprite_row * w + src_atlas_row * w * CELL_SIZE); | ||||
|  | ||||
| 					const unsigned int dst_column = dst_atlas_col * (CELL_SIZE + 2) + 1; | ||||
| 					const unsigned int dst_row = MAX_SPRITES_ROW * (CELL_SIZE + 2) * ((sprite_row + 1) + dst_atlas_row * (CELL_SIZE + 2)); | ||||
| 					// offset from base address in glerminal atlas layout | ||||
| 					const unsigned int dst_offset = dst_column + dst_row; | ||||
|  | ||||
| 					memcpy(m_sprites + dst_offset, data + src_offset, CELL_SIZE * sizeof(unsigned int)); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	bool glerminal::load_sound(const char* name) | ||||
| 	{ | ||||
| 		if (m_sounds.find(name) == m_sounds.end()) | ||||
| 		{ | ||||
| 			ma_sound& ref = m_sounds[name]; | ||||
| 			const ma_result result = ma_sound_init_from_file( | ||||
| 				&m_audio_engine, | ||||
| 				name, | ||||
| 				MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_NO_SPATIALIZATION, | ||||
| 				nullptr, | ||||
| 				nullptr, | ||||
| 				&ref | ||||
| 			); | ||||
|  | ||||
| 			if (result != MA_SUCCESS) | ||||
| 			{ | ||||
| 				return false; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::play_sound(const char* name) | ||||
| 	{ | ||||
| 		load_sound(name); | ||||
| 		const ma_result result = ma_engine_play_sound(&m_audio_engine, name, nullptr); | ||||
| 		if (result != MA_SUCCESS) | ||||
| 		{ | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::init_glfw() | ||||
| 	{ | ||||
| 		glfwInit(); | ||||
| @@ -364,6 +424,11 @@ namespace glerminal | ||||
| 		{ | ||||
| 			throw std::runtime_error("Failed to create glerminal window."); | ||||
| 		} | ||||
|  | ||||
| 		glfwSetWindowUserPointer(m_window, this); | ||||
| 		glfwSetKeyCallback(m_window, glfw_key_handler); | ||||
| 		glfwSetCursorPosCallback(m_window, glfw_mousemoved_handler); | ||||
| 		glfwSetMouseButtonCallback(m_window, glfw_mousepress_handler); | ||||
| 	} | ||||
| 	 | ||||
| 	void glerminal::log(GLenum type, GLuint id, GLenum severity, const char* message) const | ||||
| @@ -376,7 +441,6 @@ namespace glerminal | ||||
| 	void glerminal::init_gl() | ||||
| 	{ | ||||
| 		glfwMakeContextCurrent(m_window); | ||||
| 		glfwSwapInterval(1); | ||||
|  | ||||
| 		if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(glfwGetProcAddress))) | ||||
| 		{ | ||||
| @@ -451,8 +515,8 @@ namespace glerminal | ||||
| 		glGenBuffers(1, &m_vbo); | ||||
| 		glGenBuffers(1, &m_sprites_instance_vbo); | ||||
| 		glGenBuffers(1, &m_offsets_instance_vbo); | ||||
| 		glGenBuffers(1, &m_layer_colors_buffer); | ||||
| 		glGenBuffers(1, &m_layer_scales_buffer); | ||||
| 		glGenBuffers(1, &m_colors_instance_vbo); | ||||
| 		glGenBuffers(1, &m_scales_instance_vbo); | ||||
|  | ||||
| 		// create vertex array object | ||||
| 		glGenVertexArrays(1, &m_vao); | ||||
| @@ -460,23 +524,35 @@ namespace glerminal | ||||
|  | ||||
| 		// set up static vertex attributes | ||||
| 		glBindBuffer(GL_ARRAY_BUFFER, m_vbo); | ||||
| 		glBufferData(GL_ARRAY_BUFFER, sizeof(VBO_VERTICES), VBO_VERTICES, GL_STATIC_DRAW); | ||||
| 		glBufferStorage(GL_ARRAY_BUFFER, sizeof(VBO_VERTICES), VBO_VERTICES, GL_NONE); | ||||
| 		glEnableVertexAttribArray(0); | ||||
| 		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(*VBO_VERTICES), reinterpret_cast<void*>(0)); | ||||
|  | ||||
| 		// set up instanced vertex attributes | ||||
| 		glBindBuffer(GL_ARRAY_BUFFER, m_offsets_instance_vbo); | ||||
| 		glBufferData(GL_ARRAY_BUFFER, sizeof(m_offsets), m_offsets, GL_STREAM_DRAW); | ||||
| 		glBufferStorage(GL_ARRAY_BUFFER, sizeof(m_offsets), m_offsets, GL_DYNAMIC_STORAGE_BIT); | ||||
| 		glEnableVertexAttribArray(1); | ||||
| 		glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(*m_offsets), reinterpret_cast<void*>(0)); | ||||
| 		glVertexAttribDivisor(1, 1); | ||||
|  | ||||
| 		glBindBuffer(GL_ARRAY_BUFFER, m_sprites_instance_vbo); | ||||
| 		glBufferData(GL_ARRAY_BUFFER, sizeof(m_cells), m_cells, GL_STREAM_DRAW); | ||||
| 		glBufferStorage(GL_ARRAY_BUFFER, sizeof(m_cells), m_cells, GL_DYNAMIC_STORAGE_BIT); | ||||
| 		glEnableVertexAttribArray(2); | ||||
| 		glVertexAttribIPointer(2, 1, GL_UNSIGNED_BYTE, 1 * sizeof(*m_cells), reinterpret_cast<void*>(0)); | ||||
| 		glVertexAttribIPointer(2, 1, GL_UNSIGNED_SHORT, 1 * sizeof(*m_cells), reinterpret_cast<void*>(0)); | ||||
| 		glVertexAttribDivisor(2, 1); | ||||
|  | ||||
| 		glBindBuffer(GL_ARRAY_BUFFER, m_colors_instance_vbo); | ||||
| 		glBufferStorage(GL_ARRAY_BUFFER, sizeof(m_colors), m_colors, GL_DYNAMIC_STORAGE_BIT); | ||||
| 		glEnableVertexAttribArray(3); | ||||
| 		glVertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, GL_TRUE, 4 * sizeof(*m_colors), reinterpret_cast<void*>(0)); | ||||
| 		glVertexAttribDivisor(3, 1); | ||||
|  | ||||
| 		glBindBuffer(GL_ARRAY_BUFFER, m_scales_instance_vbo); | ||||
| 		glBufferStorage(GL_ARRAY_BUFFER, sizeof(m_scales), m_scales, GL_DYNAMIC_STORAGE_BIT); | ||||
| 		glEnableVertexAttribArray(4); | ||||
| 		glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, 1 * sizeof(*m_scales), reinterpret_cast<void*>(0)); | ||||
| 		glVertexAttribDivisor(4, 1); | ||||
|  | ||||
| 		// set up static vertex attributes | ||||
| 		glGenVertexArrays(1, &m_screen_vao); | ||||
| 		glBindVertexArray(m_screen_vao); | ||||
| @@ -487,14 +563,6 @@ namespace glerminal | ||||
|  | ||||
| 		glBindVertexArray(0); | ||||
|  | ||||
| 		glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_layer_scales_buffer); | ||||
| 		glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(m_layer_scales), m_layer_scales, GL_DYNAMIC_READ); | ||||
| 		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_layer_scales_buffer); | ||||
|  | ||||
| 		glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_layer_colors_buffer); | ||||
| 		glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(m_layer_colors), m_layer_colors, GL_DYNAMIC_READ); | ||||
| 		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_layer_colors_buffer); | ||||
|  | ||||
| 		// -- setup shader program -- | ||||
| 		// test for features | ||||
| 		const bool vertex_shader_layer_supported = | ||||
| @@ -527,8 +595,8 @@ namespace glerminal | ||||
| 			glDeleteShader(geometry_shader); | ||||
| 			glDeleteShader(fragment_shader); | ||||
|  | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 0, 0, "Could not compile vertex shader."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 0, 0, info_log); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, "Could not compile vertex shader."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, info_log); | ||||
| 			throw std::runtime_error("Could not compile vertex shader."); | ||||
| 		} | ||||
|  | ||||
| @@ -541,8 +609,8 @@ namespace glerminal | ||||
| 			glDeleteShader(geometry_shader); | ||||
| 			glDeleteShader(fragment_shader); | ||||
|  | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 1, 0, "Could not compile geometry shader."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 1, 0, info_log); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 1, GL_DEBUG_SEVERITY_HIGH, "Could not compile geometry shader."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 1, GL_DEBUG_SEVERITY_HIGH, info_log); | ||||
| 			throw std::runtime_error("Could not compile geometry shader."); | ||||
| 		} | ||||
|  | ||||
| @@ -555,8 +623,8 @@ namespace glerminal | ||||
| 			glDeleteShader(geometry_shader); | ||||
| 			glDeleteShader(fragment_shader); | ||||
|  | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 2, 0, "Could not compile fragment shader."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 2, 0, info_log); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 2, GL_DEBUG_SEVERITY_HIGH, "Could not compile fragment shader."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 2, GL_DEBUG_SEVERITY_HIGH, info_log); | ||||
| 			throw std::runtime_error("Could not compile fragment shader."); | ||||
| 		} | ||||
|  | ||||
| @@ -582,16 +650,16 @@ namespace glerminal | ||||
|  | ||||
| 			glDeleteProgram(m_program); | ||||
|  | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 3, 0, "Could not link shader program."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 3, 0, info_log); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 3, GL_DEBUG_SEVERITY_HIGH, "Could not link shader program."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 3, GL_DEBUG_SEVERITY_HIGH, info_log); | ||||
| 			throw std::runtime_error("Could not link shader program."); | ||||
| 		} | ||||
|  | ||||
| 		// setup uniforms | ||||
| 		m_screen_size_uniform_location = glGetUniformLocation(m_program, GRID_SIZE_UNIFORM_NAME); | ||||
|  | ||||
| 		glUseProgram(m_program); | ||||
| 		glUniform4f(m_screen_size_uniform_location, GRID_WIDTH, GRID_AREA, 1.0f / GRID_WIDTH, 1.0f / GRID_HEIGHT); | ||||
| 		glUniform1f(glGetUniformLocation(m_program, CELL_SIZE_UNIFORM_NAME), CELL_SIZE); | ||||
| 		glUniform4f(glGetUniformLocation(m_program, GRID_SIZE_UNIFORM_NAME), GRID_WIDTH + 2, GRID_AREA_2, 1.0f / GRID_WIDTH, 1.0f / GRID_HEIGHT); | ||||
| 		glUniform1i(glGetUniformLocation(m_program, ATLAS_WIDTH_UNIFORM_NAME), MAX_SPRITES_ROW); | ||||
|  | ||||
| 		// compile | ||||
| 		const unsigned int screen_vertex_shader = glCreateShader(GL_VERTEX_SHADER); | ||||
| @@ -611,8 +679,8 @@ namespace glerminal | ||||
| 			glDeleteShader(screen_vertex_shader); | ||||
| 			glDeleteShader(screen_fragment_shader); | ||||
|  | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 4, 0, "Could not compile screen vertex shader."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 4, 0, info_log); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 4, GL_DEBUG_SEVERITY_HIGH, "Could not compile screen vertex shader."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 4, GL_DEBUG_SEVERITY_HIGH, info_log); | ||||
| 			throw std::runtime_error("Could not compile screen vertex shader."); | ||||
| 		} | ||||
|  | ||||
| @@ -624,8 +692,8 @@ namespace glerminal | ||||
| 			glDeleteShader(screen_vertex_shader); | ||||
| 			glDeleteShader(screen_fragment_shader); | ||||
|  | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 5, 0, "Could not compile screen fragment shader."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 5, 0, info_log); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 5, GL_DEBUG_SEVERITY_HIGH, "Could not compile screen fragment shader."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 5, GL_DEBUG_SEVERITY_HIGH, info_log); | ||||
| 			throw std::runtime_error("Could not compile screen fragment shader."); | ||||
| 		} | ||||
|  | ||||
| @@ -643,8 +711,8 @@ namespace glerminal | ||||
| 		{ | ||||
| 			glDeleteProgram(m_screen_program); | ||||
|  | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 6, 0, "Could not link screen shader program."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 6, 0, info_log); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 6, GL_DEBUG_SEVERITY_HIGH, "Could not link screen shader program."); | ||||
| 			log(GL_DEBUG_TYPE_ERROR, 6, GL_DEBUG_SEVERITY_HIGH, info_log); | ||||
| 			throw std::runtime_error("Could not link screen shader program."); | ||||
| 		} | ||||
|  | ||||
| @@ -655,20 +723,22 @@ namespace glerminal | ||||
|  | ||||
| 		// -- setup textures -- | ||||
| 		glGenTextures(1, &m_sprites_texture); | ||||
| 		glBindTexture(GL_TEXTURE_2D_ARRAY, m_sprites_texture); | ||||
| 		glBindTexture(GL_TEXTURE_2D, m_sprites_texture); | ||||
|  | ||||
| 		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | ||||
| 		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | ||||
| 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | ||||
| 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | ||||
|  | ||||
| 		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||
| 		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | ||||
| 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||
| 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | ||||
|  | ||||
| 		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, 0); | ||||
| 		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0); | ||||
| 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); | ||||
| 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); | ||||
|  | ||||
| 		glTextureStorage2D(m_sprites_texture, 1, GL_RGBA8, MAX_SPRITES_ROW * (CELL_SIZE + 2), MAX_SPRITES_ROW * (CELL_SIZE + 2)); | ||||
|  | ||||
| 		update_sprites(); | ||||
|  | ||||
| 		glBindTextureUnit(0, m_sprites_texture); | ||||
| 		glBindTextureUnit(2, m_sprites_texture); | ||||
|  | ||||
| 		// -- setup framebuffer -- | ||||
| 		glGenFramebuffers(1, &m_framebuffer); | ||||
| @@ -711,6 +781,18 @@ namespace glerminal | ||||
| 		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_screen_framebuffer_backing_texture, 0); | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::init_audio() | ||||
| 	{ | ||||
| 		const ma_result result = ma_engine_init(nullptr, &m_audio_engine); | ||||
|  | ||||
| 		if (result != MA_SUCCESS) | ||||
| 		{ | ||||
| 			throw std::runtime_error("Failed to initialize audio engine"); | ||||
| 		} | ||||
|  | ||||
| 		ma_engine_set_volume(&m_audio_engine, 1); | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::deinit_glfw() | ||||
| 	{ | ||||
| 		glfwDestroyWindow(m_window); | ||||
| @@ -730,33 +812,82 @@ namespace glerminal | ||||
| 		glDeleteBuffers(1, &m_vbo); | ||||
| 		glDeleteBuffers(1, &m_sprites_instance_vbo); | ||||
| 		glDeleteBuffers(1, &m_offsets_instance_vbo); | ||||
| 		glDeleteBuffers(1, &m_layer_colors_buffer); | ||||
| 		glDeleteBuffers(1, &m_layer_scales_buffer); | ||||
| 		glDeleteBuffers(1, &m_colors_instance_vbo); | ||||
| 		glDeleteBuffers(1, &m_scales_instance_vbo); | ||||
| 		glDeleteProgram(m_program); | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::deinit_audio() | ||||
| 	{ | ||||
| 		for (auto& elem : m_sounds) | ||||
| 		{ | ||||
| 			ma_sound_uninit(&elem.second); | ||||
| 		} | ||||
| 		ma_engine_uninit(&m_audio_engine); | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::update_sprites() | ||||
| 	{ | ||||
| 		glBindTexture(GL_TEXTURE_2D_ARRAY, m_sprites_texture); | ||||
| 		glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, CELL_SIZE, CELL_SIZE, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_sprites); | ||||
| 		glTextureSubImage2D(m_sprites_texture, 0, 0, 0, (CELL_SIZE + 2) * MAX_SPRITES_ROW, (CELL_SIZE + 2) * MAX_SPRITES_ROW, GL_RGBA, GL_UNSIGNED_BYTE, m_sprites); | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::update_layer_colors() | ||||
| 	void glerminal::update_colors() | ||||
| 	{ | ||||
| 		glNamedBufferData(m_layer_colors_buffer, sizeof(m_layer_colors), m_layer_colors, GL_DYNAMIC_READ); | ||||
| 		glNamedBufferSubData(m_colors_instance_vbo, 0, sizeof(m_colors), m_colors); | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::update_layer_scales() | ||||
| 	void glerminal::update_scales() | ||||
| 	{ | ||||
| 		glNamedBufferData(m_layer_scales_buffer, sizeof(m_layer_scales), m_layer_scales, GL_DYNAMIC_READ); | ||||
| 		glNamedBufferSubData(m_scales_instance_vbo, 0, sizeof(m_scales), m_scales); | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::glfw_key_handler(GLFWwindow* window, int key, int scancode, int action, int mods) | ||||
| 	{ | ||||
| 		const glerminal* const self = static_cast<glerminal*>(glfwGetWindowUserPointer(window)); | ||||
|  | ||||
| 		if (self->m_keypressed && action == GLFW_PRESS) | ||||
| 		{ | ||||
| 			self->m_keypressed(key); | ||||
| 		} | ||||
|  | ||||
| 		if (self->m_keyreleased && action == GLFW_RELEASE) | ||||
| 		{ | ||||
| 			self->m_keyreleased(key); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::glfw_mousemoved_handler(GLFWwindow *window, double x, double y) | ||||
| 	{ | ||||
| 		const glerminal* const self = static_cast<glerminal*>(glfwGetWindowUserPointer(window)); | ||||
|  | ||||
| 		if (self->m_mousemoved) { self->m_mousemoved(x / (CELL_SIZE * CELL_SCALE), y / (CELL_SIZE * CELL_SCALE)); } | ||||
| 	} | ||||
|  | ||||
| 	void glerminal::glfw_mousepress_handler(GLFWwindow *window, int button, int action, int mods) | ||||
| 	{ | ||||
| 		const glerminal* const self = static_cast<glerminal*>(glfwGetWindowUserPointer(window)); | ||||
|  | ||||
| 		double x, y; | ||||
| 		glfwGetCursorPos(window, &x, &y); | ||||
|  | ||||
| 		if (self->m_mousepressed && action == GLFW_PRESS) | ||||
| 		{ | ||||
| 			self->m_mousepressed(button, x / (CELL_SIZE * CELL_SCALE), y / (CELL_SIZE * CELL_SCALE)); | ||||
| 		} | ||||
|  | ||||
| 		if (self->m_mousereleased && action == GLFW_RELEASE) | ||||
| 		{ | ||||
| 			self->m_mousereleased(button, x / (CELL_SIZE * CELL_SCALE), y / (CELL_SIZE * CELL_SCALE)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| void glerminal_run(glerminal_init_cb init, glerminal_main_cb main) | ||||
| void glerminal_run(glerminal_init_params params) | ||||
| { | ||||
| 	try | ||||
| 	{ | ||||
| 		glerminal::glerminal* g = new glerminal::glerminal(init, main); | ||||
| 		glerminal::glerminal* g = new glerminal::glerminal(params); | ||||
| 		g->run(); | ||||
| 		delete g; | ||||
| 	} | ||||
| @@ -780,64 +911,83 @@ void glerminal_flush() | ||||
| 	GLERMINAL_G->flush(); | ||||
| } | ||||
|  | ||||
| void glerminal_set(unsigned char x, unsigned char y, unsigned char layer, unsigned char sprite) | ||||
| void glerminal_set(int x, int y, int layer, unsigned short sprite) | ||||
| { | ||||
| 	if (!GLERMINAL_G) { return; } | ||||
|  | ||||
| 	GLERMINAL_G->set(x, y, layer, sprite); | ||||
| 	GLERMINAL_G->set(x + 1, y + 1, layer, sprite); | ||||
| } | ||||
|  | ||||
| unsigned char glerminal_get(unsigned char x, unsigned char y, unsigned char layer) | ||||
| unsigned short glerminal_get(int x, int y, int layer) | ||||
| { | ||||
| 	if (!GLERMINAL_G) { return 0; } | ||||
|  | ||||
| 	return GLERMINAL_G->get(x, y, layer); | ||||
| 	return GLERMINAL_G->get(x + 1, y + 1, layer); | ||||
| } | ||||
|  | ||||
| void glerminal_offset(unsigned char x, unsigned char y, unsigned char layer, float x_offset, float y_offset) | ||||
| void glerminal_offset(int x, int y, int layer, float x_offset, float y_offset) | ||||
| { | ||||
| 	if (!GLERMINAL_G) { return; } | ||||
|  | ||||
| 	GLERMINAL_G->offset(x, y, layer, x_offset, y_offset); | ||||
| 	GLERMINAL_G->offset(x + 1, y + 1, layer, x_offset, y_offset); | ||||
| } | ||||
|  | ||||
| void glerminal_layer_color(unsigned char layer, unsigned int color) | ||||
| void glerminal_color(int x, int y, int layer, unsigned int color) | ||||
| { | ||||
| 	if (!GLERMINAL_G) { return; } | ||||
|  | ||||
| 	GLERMINAL_G->layer_color(layer, color); | ||||
| 	GLERMINAL_G->color(x + 1, y + 1, layer, color); | ||||
| } | ||||
|  | ||||
| void glerminal_layer_scale(unsigned char layer, float scale) | ||||
| void glerminal_scale(int x, int y, int layer, float scale) | ||||
| { | ||||
| 	if (!GLERMINAL_G) { return; } | ||||
|  | ||||
| 	GLERMINAL_G->layer_scale(layer, scale); | ||||
| 	GLERMINAL_G->scale(x + 1, y + 1, layer, scale); | ||||
| } | ||||
|  | ||||
| void glerminal_load_sprites_file(const char* filename) | ||||
| int glerminal_load_sprites_file(const char* filename) | ||||
| { | ||||
| 	if (!GLERMINAL_G) { return; } | ||||
| 	if (!GLERMINAL_G) { return false; } | ||||
|  | ||||
| 	bool success = false; | ||||
|  | ||||
| 	int w, h; | ||||
| 	stbi_uc* const buffer = stbi_load(filename, &w, &h, nullptr, 4); | ||||
|  | ||||
| 	// verify atlas size is a multiple of CELL_SIZE in each dimension | ||||
| 	if (w % glerminal::CELL_SIZE == 0 && h % glerminal::CELL_SIZE == 0) | ||||
| 	if (buffer && w % glerminal::CELL_SIZE == 0 && h % glerminal::CELL_SIZE == 0) | ||||
| 	{ | ||||
| 		GLERMINAL_G->load_atlas(w / glerminal::CELL_SIZE, h / glerminal::CELL_SIZE, reinterpret_cast<unsigned int*>(buffer)); | ||||
|  | ||||
| 		success = true; | ||||
| 	} | ||||
|  | ||||
| 	stbi_image_free(buffer); | ||||
|  | ||||
| 	return success; | ||||
| } | ||||
|  | ||||
| void glerminal_load_sprites_buffer(unsigned char width, unsigned char height, const unsigned int* buffer) | ||||
| int glerminal_load_sprites_buffer(unsigned char width, unsigned char height, const unsigned int* buffer) | ||||
| { | ||||
| 	if (!GLERMINAL_G) { return; } | ||||
| 	if (!GLERMINAL_G) { return false; } | ||||
|  | ||||
| 	// verify atlas size is a multiple of CELL_SIZE in each dimension | ||||
| 	if (width % glerminal::CELL_SIZE == 0 && height % glerminal::CELL_SIZE == 0) | ||||
| 	{ | ||||
| 		GLERMINAL_G->load_atlas(width / glerminal::CELL_SIZE, height / glerminal::CELL_SIZE, buffer); | ||||
|  | ||||
| 		return true; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void glerminal_sound(const char* name) | ||||
| { | ||||
| 	if (!GLERMINAL_G) { return; } | ||||
|  | ||||
| 	GLERMINAL_G->play_sound(name); | ||||
| } | ||||
|   | ||||
							
								
								
									
										7
									
								
								source/miniaudio.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								source/miniaudio.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| #ifndef GLERMINAL_MINIAUDIO_H | ||||
| #define GLERMINAL_MINIAUDIO_H | ||||
|  | ||||
| #define MINIAUDIO_IMPLEMENTATION | ||||
| #include "miniaudio.h" | ||||
|  | ||||
| #endif//GLERMINAL_MINIAUDIO_H | ||||
							
								
								
									
										92621
									
								
								source/miniaudio.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92621
									
								
								source/miniaudio.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -2,11 +2,16 @@ | ||||
|  | ||||
| #include <test-common.h> | ||||
|  | ||||
| #include <iostream> | ||||
|  | ||||
| namespace | ||||
| { | ||||
| 	void init() | ||||
| 	{ | ||||
| 		glerminal_load_sprites_file("resources/image.png"); | ||||
| 		if (!glerminal_load_sprites_file("resources/image.png")) | ||||
| 		{ | ||||
| 			std::cout << "Failed to load texture" << std::endl; | ||||
| 		} | ||||
| 		 | ||||
| 		for (int i = 0; i < GRID_HEIGHT; i++) | ||||
| 		{ | ||||
| @@ -20,12 +25,12 @@ namespace | ||||
| 		glerminal_quit(); | ||||
| 	} | ||||
|  | ||||
| 	void mainloop(float) {} | ||||
| 	void mainloop(double) {} | ||||
| } | ||||
|  | ||||
| int main() | ||||
| { | ||||
| 	glerminal_run(init, mainloop); | ||||
| 	glerminal_run({init, mainloop}); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
| @@ -8,7 +8,7 @@ | ||||
|  | ||||
| namespace | ||||
| { | ||||
| 	unsigned char pixels[(GRID_WIDTH * CELL_SCALE * 8) * (GRID_HEIGHT * CELL_SCALE * 8) * 3]; | ||||
| 	unsigned char pixels[(GRID_WIDTH * CELL_SIZE * CELL_SCALE) * (GRID_HEIGHT * CELL_SIZE * CELL_SCALE) * 3]; | ||||
| } | ||||
|  | ||||
| void glerminal_test_save_image() | ||||
| @@ -21,5 +21,5 @@ void glerminal_test_save_image() | ||||
| 	glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels); | ||||
|  | ||||
| 	stbi_flip_vertically_on_write(true); | ||||
| 	stbi_write_png("image.png", GRID_WIDTH * CELL_SCALE * 8, GRID_HEIGHT * CELL_SCALE * 8, 3, pixels, GRID_WIDTH * CELL_SCALE * 8 * 3); | ||||
| 	stbi_write_png("image.png", GRID_WIDTH * CELL_SIZE * CELL_SCALE, GRID_HEIGHT * CELL_SIZE * CELL_SCALE, 3, pixels, GRID_WIDTH * CELL_SIZE * CELL_SCALE * 3); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user