Created
January 20, 2022 16:34
-
-
Save tanoxyz/fd43e95e09397ce64f601ec3b869f6b6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef _APP_H_ | |
#define _APP_H_ | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <assert.h> | |
#ifndef APP_PUBLIC | |
#define APP_PUBLIC | |
#endif | |
#ifndef APP_INTERNAL | |
#define APP_INTERNAL static | |
#endif | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// Basic GL application API | |
// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
/* Based on GLFW */ | |
typedef enum { | |
APP_KEY_INVALID = 0, | |
APP_KEY_SPACE = 32, | |
APP_KEY_APOSTROPHE = 39, /* ' */ | |
APP_KEY_COMMA = 44, /* , */ | |
APP_KEY_MINUS = 45, /* - */ | |
APP_KEY_PERIOD = 46, /* . */ | |
APP_KEY_SLASH = 47, /* / */ | |
APP_KEY_0 = 48, | |
APP_KEY_1 = 49, | |
APP_KEY_2 = 50, | |
APP_KEY_3 = 51, | |
APP_KEY_4 = 52, | |
APP_KEY_5 = 53, | |
APP_KEY_6 = 54, | |
APP_KEY_7 = 55, | |
APP_KEY_8 = 56, | |
APP_KEY_9 = 57, | |
APP_KEY_SEMICOLON = 59, /* ; */ | |
APP_KEY_EQUAL = 61, /* = */ | |
APP_KEY_A = 65, | |
APP_KEY_B = 66, | |
APP_KEY_C = 67, | |
APP_KEY_D = 68, | |
APP_KEY_E = 69, | |
APP_KEY_F = 70, | |
APP_KEY_G = 71, | |
APP_KEY_H = 72, | |
APP_KEY_I = 73, | |
APP_KEY_J = 74, | |
APP_KEY_K = 75, | |
APP_KEY_L = 76, | |
APP_KEY_M = 77, | |
APP_KEY_N = 78, | |
APP_KEY_O = 79, | |
APP_KEY_P = 80, | |
APP_KEY_Q = 81, | |
APP_KEY_R = 82, | |
APP_KEY_S = 83, | |
APP_KEY_T = 84, | |
APP_KEY_U = 85, | |
APP_KEY_V = 86, | |
APP_KEY_W = 87, | |
APP_KEY_X = 88, | |
APP_KEY_Y = 89, | |
APP_KEY_Z = 90, | |
APP_KEY_LEFT_BRACKET = 91, /* [ */ | |
APP_KEY_BACKSLASH = 92, /* \ */ | |
APP_KEY_RIGHT_BRACKET = 93, /* ] */ | |
APP_KEY_GRAVE_ACCENT = 96, /* ` */ | |
APP_KEY_WORLD_1 = 161, /* non-US #1 */ | |
APP_KEY_WORLD_2 = 162, /* non-US #2 */ | |
APP_KEY_ESCAPE = 256, | |
APP_KEY_ENTER = 257, | |
APP_KEY_TAB = 258, | |
APP_KEY_BACKSPACE = 259, | |
APP_KEY_INSERT = 260, | |
APP_KEY_DELETE = 261, | |
APP_KEY_RIGHT = 262, | |
APP_KEY_LEFT = 263, | |
APP_KEY_DOWN = 264, | |
APP_KEY_UP = 265, | |
APP_KEY_PAGE_UP = 266, | |
APP_KEY_PAGE_DOWN = 267, | |
APP_KEY_HOME = 268, | |
APP_KEY_END = 269, | |
APP_KEY_CAPS_LOCK = 280, | |
APP_KEY_SCROLL_LOCK = 281, | |
APP_KEY_NUM_LOCK = 282, | |
APP_KEY_PRINT_SCREEN = 283, | |
APP_KEY_PAUSE = 284, | |
APP_KEY_F1 = 290, | |
APP_KEY_F2 = 291, | |
APP_KEY_F3 = 292, | |
APP_KEY_F4 = 293, | |
APP_KEY_F5 = 294, | |
APP_KEY_F6 = 295, | |
APP_KEY_F7 = 296, | |
APP_KEY_F8 = 297, | |
APP_KEY_F9 = 298, | |
APP_KEY_F10 = 299, | |
APP_KEY_F11 = 300, | |
APP_KEY_F12 = 301, | |
APP_KEY_F13 = 302, | |
APP_KEY_F14 = 303, | |
APP_KEY_F15 = 304, | |
APP_KEY_F16 = 305, | |
APP_KEY_F17 = 306, | |
APP_KEY_F18 = 307, | |
APP_KEY_F19 = 308, | |
APP_KEY_F20 = 309, | |
APP_KEY_F21 = 310, | |
APP_KEY_F22 = 311, | |
APP_KEY_F23 = 312, | |
APP_KEY_F24 = 313, | |
APP_KEY_F25 = 314, | |
APP_KEY_KP_0 = 320, | |
APP_KEY_KP_1 = 321, | |
APP_KEY_KP_2 = 322, | |
APP_KEY_KP_3 = 323, | |
APP_KEY_KP_4 = 324, | |
APP_KEY_KP_5 = 325, | |
APP_KEY_KP_6 = 326, | |
APP_KEY_KP_7 = 327, | |
APP_KEY_KP_8 = 328, | |
APP_KEY_KP_9 = 329, | |
APP_KEY_KP_DECIMAL = 330, | |
APP_KEY_KP_DIVIDE = 331, | |
APP_KEY_KP_MULTIPLY = 332, | |
APP_KEY_KP_SUBTRACT = 333, | |
APP_KEY_KP_ADD = 334, | |
APP_KEY_KP_ENTER = 335, | |
APP_KEY_KP_EQUAL = 336, | |
APP_KEY_LEFT_SHIFT = 340, | |
APP_KEY_LEFT_CONTROL = 341, | |
APP_KEY_LEFT_ALT = 342, | |
APP_KEY_LEFT_SUPER = 343, | |
APP_KEY_RIGHT_SHIFT = 344, | |
APP_KEY_RIGHT_CONTROL = 345, | |
APP_KEY_RIGHT_ALT = 346, | |
APP_KEY_RIGHT_SUPER = 347, | |
APP_KEY_MENU = 348, | |
APP_MAX_KEYCODES, | |
} APP_KeyCode; | |
typedef enum { | |
APP_MOUSE_BUTTON_LEFT = 0, | |
APP_MOUSE_BUTTON_RIGHT = 1, | |
APP_MOUSE_BUTTON_MIDDLE = 2, | |
APP_MOUSE_BUTTON_INVALID = 3, | |
} APP_MouseButton; | |
typedef enum { | |
APP_TOUCH_STATE_INVALID = 0x0, | |
APP_TOUCH_STATE_BEGIN = 0x1, | |
APP_TOUCH_STATE_MOVE = 0x2, | |
APP_TOUCH_STATE_END = 0x4, | |
APP_TOUCH_STATE_CANCEL = 0x8, | |
} APP_TouchState; | |
typedef enum { | |
APP_POINTER_STATE_INVALID = 0x0, // Invalid state | |
APP_POINTER_STATE_DOWN = 0x1, // Pointer is touching the canvas | |
APP_POINTER_STATE_PRESSED = 0x2, // Pointer changed from up to down | |
APP_POINTER_STATE_RELEASED = 0x4, // Pointer changed from down to up | |
APP_POINTER_STATE_UP = 0x8, // Pointer isn't touching the canvas | |
} APP_PointerState; | |
typedef enum { | |
APP_SUCCESS = 0, | |
} APP_ErrorCode; | |
// Creates a windows based on the description passed. | |
// This must be called before Setup_GL and any window operation. | |
// To close the window properly, Destroy_window must be called | |
// at the end. | |
APP_PUBLIC int | |
APP_Init(const char *title, int width, int height); | |
// Destroys the window | |
APP_PUBLIC void | |
APP_Destroy_window(void); | |
// Gets the window width | |
APP_PUBLIC int | |
APP_Get_window_width(void); | |
// Gets the window height | |
APP_PUBLIC int | |
APP_Get_window_height(void); | |
// Gets if the windows was resized in the last frame | |
APP_PUBLIC bool | |
APP_Window_resized(void); | |
// Gets if the key is down | |
APP_PUBLIC bool | |
APP_Is_key_down(APP_KeyCode key); | |
// Gets if the key is being pressed for first time | |
APP_PUBLIC bool | |
APP_Is_key_pressed(APP_KeyCode key); | |
// The key was previously down | |
APP_PUBLIC bool | |
APP_Is_key_released(APP_KeyCode key); | |
// The key is untouched | |
APP_PUBLIC bool | |
APP_Is_key_up(APP_KeyCode key); | |
// If the key is repeated, (Timing determined by the environment) useful for text input | |
// controls, like backespace to delete characters | |
APP_PUBLIC bool | |
APP_Is_key_repeat(APP_KeyCode key); | |
// Gets a null terminated utf8 string with the last text send by the keyboard | |
APP_PUBLIC const char * | |
APP_Get_text_input(void); | |
// Gets the length (IN BYTES) of the text input | |
APP_PUBLIC unsigned int | |
APP_Get_text_input_length(void); | |
// Gets the mouse x position | |
APP_PUBLIC float | |
APP_Get_mouse_x(void); | |
// Gets the mouse y position (up is 0) | |
APP_PUBLIC float | |
APP_Get_mouse_y(void); | |
// Gets the mouse y position inverted (down is 0) | |
APP_PUBLIC float | |
APP_Get_mouse_y_inv(void); | |
// Gets if the mouse button is down | |
APP_PUBLIC bool | |
APP_Is_mouse_button_down(APP_MouseButton button); | |
// Gets if the button is being pressed for first time | |
APP_PUBLIC bool | |
APP_Is_mouse_button_pressed(APP_MouseButton button); | |
// The button was previously down | |
APP_PUBLIC bool | |
APP_Is_mouse_button_released(APP_MouseButton button); | |
// The button is untouched | |
APP_PUBLIC bool | |
APP_Is_mouse_button_up(APP_MouseButton button); | |
// Gets the mouse wheel delta | |
APP_PUBLIC float | |
APP_Get_wheel_x(void); | |
// Gets the mouse wheel delta | |
APP_PUBLIC float | |
APP_Get_wheel_y(void); | |
// Gets the touch x | |
APP_PUBLIC float | |
APP_Get_touch_x(int id); | |
// Gets the touch y | |
APP_PUBLIC float | |
APP_Get_touch_y(int id); | |
// Gets the touch state | |
APP_PUBLIC unsigned char | |
APP_Get_touch_state(int id); | |
// The pointer is a mix between mouse and touch, tracks the mouse or the touch(0) depending on the | |
// last interaction | |
APP_PUBLIC float | |
APP_Get_pointer_x(void); | |
APP_PUBLIC float | |
APP_Get_pointer_y(void); | |
APP_PUBLIC unsigned char | |
APP_Get_pointer_state(void); | |
// Changes to fullscreen or windowed depending if is true (fullscreen) or | |
// false (windowed). | |
// | |
// The resize event will be triggered when this is called. | |
APP_PUBLIC void | |
APP_Set_fullscreen(bool enable); | |
// Shows or hides the mouse depending on the parameter. | |
// true = shows | |
// false = hides | |
APP_PUBLIC void | |
APP_Show_mouse(bool show); | |
// Changes the window title. | |
APP_PUBLIC void | |
APP_Set_window_title(const char *title); | |
// Changes the swap interval between the backbuffer and the front buffer | |
// This must be called after Setup_GL | |
// | |
// 0 disables the swap interval so unlimits the fps rate. | |
// | |
// NOTE: This doesn't have any effect on WASM. | |
APP_PUBLIC void | |
APP_Set_swap_interval(unsigned int interval); | |
// Runs the application loop, recives a application frame handler | |
// which will execute every frame. | |
// If the handler returns 0 the application should continue normally | |
// If the handler returns 1 the application should finish. | |
// If the handler returns <0 the application will have an abnormal finish. | |
// | |
// Take into account that this function don't does any resource cleanup | |
// when the handler decides tu finish, all resource cleanup should be | |
// done inside the frame handler. | |
// | |
// This function varies in implementaton between wasm and other platforms | |
// because in javascript we need to use the frame handler as a event listener. | |
// Look at platform_wasm.h, WAFN_application_frame(). | |
// | |
// The way to call this function from main should be: | |
// | |
// int | |
// main(...) { | |
// .... | |
// return Run_application_loop(My_frame_handler); | |
// } | |
APP_PUBLIC int | |
APP_Run_application_loop(int(*application_frame)(void)); | |
// Returns the nanoseconds passed from the start of the app | |
APP_PUBLIC int64_t | |
APP_Time(void); | |
APP_PUBLIC int64_t | |
APP_Frame_duration(int samples); | |
typedef int (*APP_SoundPlayerCallback) (void *user_data, float* buffer, int n_frames, int n_channels); | |
// | |
// Description with the desired when the player is launched | |
// | |
typedef struct { | |
int sample_rate; /* requested sample rate */ | |
int num_channels; /* number of channels, default: 1 (mono) */ | |
int buffer_frames; /* number of frames in streaming buffer */ | |
void *user_data; /* optional user data argument for stream_userdata_cb */ | |
APP_SoundPlayerCallback stream_cb; /* Streaming callback */ | |
} APP_SoundPlayerDesc; | |
// Launchs a thread (On linux and windows) or a async rutine (wasm) | |
// Which will play the audio. | |
// | |
// When it needs a new chunk of samples will call the callback | |
APP_PUBLIC int | |
APP_Launch_sound_player(APP_SoundPlayerDesc *desc); | |
// Terminates the player routine. | |
APP_PUBLIC void | |
APP_Terminate_sound_player(void); | |
// Returns a NULL terminated array with de contents of the file, | |
// the parameter size is setted with de file size (without counting | |
// the appended 0). | |
// If the file can't be found, will return a NULL | |
// | |
// If we are on BUNDLED_TREE mode the data returned will be a pointer | |
// to the data embeded into de binary. | |
// | |
// If not, the data will be heap allocated. | |
// | |
// To free this data on a safe way we have to call to Free_file_contents() | |
// this function will take into account the mode. | |
APP_PUBLIC unsigned char * | |
APP_Get_file_contentsz(const char *filename, uint32_t *size); | |
// Frees the data getted by Get_file_contentsz | |
// if the value is NULL nothing happens | |
APP_PUBLIC void | |
APP_Free_file_contents(unsigned char *data); | |
// Gets the file modify time. This will always return 0 on bundled tree mode | |
APP_PUBLIC uint64_t | |
Get_file_modify_time(const char *filename); | |
#if defined(WIN32) | |
/* Windows */ | |
#define APP_WINDOWS (1) | |
#include <windows.h> | |
#elif defined(__wasm__) | |
/* Wasm */ | |
#define APP_WASM (1) | |
#define BUNDLED_TREE (1) // Wasm always need the bundled tree | |
#define GL_ES2_COMPAT (1) | |
#define APP__WA_JS(ret, name, args, ...) extern __attribute__((import_module("JS"), import_name(#name "|" #args "|" #__VA_ARGS__))) ret name args; | |
#elif defined(__linux__) || defined(__unix__) | |
/* Linux */ | |
#define APP_LINUX (1) | |
#define GL_ES2_COMPAT (1) | |
#endif | |
#if defined(APP_WASM) | |
#define APP__EXPORT(func_name) __attribute__((export_name(#func_name))) | |
#else | |
#define APP__EXPORT(func_name) | |
#endif | |
#include <stddef.h> | |
#define __gl_h_ 1 | |
#define __gl32_h_ 1 | |
#define __gl31_h_ 1 | |
#define __GL_H__ 1 | |
#define __glext_h_ 1 | |
#define __GLEXT_H_ 1 | |
#define __gltypes_h_ 1 | |
#define __glcorearb_h_ 1 | |
#define __gl_glcorearb_h_ 1 | |
#define GL_APIENTRY APIENTRY | |
typedef unsigned int GLenum; | |
typedef unsigned int GLuint; | |
typedef int GLsizei; | |
typedef char GLchar; | |
typedef ptrdiff_t GLintptr; | |
typedef ptrdiff_t GLsizeiptr; | |
typedef double GLclampd; | |
typedef unsigned short GLushort; | |
typedef unsigned char GLubyte; | |
typedef unsigned char GLboolean; | |
typedef uint64_t GLuint64; | |
typedef double GLdouble; | |
typedef unsigned short GLhalf; | |
typedef float GLclampf; | |
typedef unsigned int GLbitfield; | |
typedef signed char GLbyte; | |
typedef short GLshort; | |
typedef void GLvoid; | |
typedef int64_t GLint64; | |
typedef float GLfloat; | |
typedef struct __GLsync * GLsync; | |
typedef int GLint; | |
#define GL_INT_2_10_10_10_REV 0x8D9F | |
#define GL_R32F 0x822E | |
#define GL_PROGRAM_POINT_SIZE 0x8642 | |
#define GL_STENCIL_ATTACHMENT 0x8D20 | |
#define GL_DEPTH_ATTACHMENT 0x8D00 | |
#define GL_COLOR_ATTACHMENT2 0x8CE2 | |
#define GL_COLOR_ATTACHMENT0 0x8CE0 | |
#define GL_R16F 0x822D | |
#define GL_COLOR_ATTACHMENT22 0x8CF6 | |
#define GL_DRAW_FRAMEBUFFER 0x8CA9 | |
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 | |
#define GL_NUM_EXTENSIONS 0x821D | |
#define GL_INFO_LOG_LENGTH 0x8B84 | |
#define GL_VERTEX_SHADER 0x8B31 | |
#define GL_INCR 0x1E02 | |
#define GL_DYNAMIC_DRAW 0x88E8 | |
#define GL_STATIC_DRAW 0x88E4 | |
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 | |
#define GL_TEXTURE_CUBE_MAP 0x8513 | |
#define GL_FUNC_SUBTRACT 0x800A | |
#define GL_FUNC_REVERSE_SUBTRACT 0x800B | |
#define GL_CONSTANT_COLOR 0x8001 | |
#define GL_DECR_WRAP 0x8508 | |
#define GL_LINEAR_MIPMAP_LINEAR 0x2703 | |
#define GL_ELEMENT_ARRAY_BUFFER 0x8893 | |
#define GL_SHORT 0x1402 | |
#define GL_DEPTH_TEST 0x0B71 | |
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 | |
#define GL_LINK_STATUS 0x8B82 | |
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 | |
#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E | |
#define GL_RGBA16F 0x881A | |
#define GL_CONSTANT_ALPHA 0x8003 | |
#define GL_READ_FRAMEBUFFER 0x8CA8 | |
#define GL_TEXTURE0 0x84C0 | |
#define GL_TEXTURE_MIN_LOD 0x813A | |
#define GL_CLAMP_TO_EDGE 0x812F | |
#define GL_UNSIGNED_SHORT_5_6_5 0x8363 | |
#define GL_TEXTURE_WRAP_R 0x8072 | |
#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 | |
#define GL_NEAREST_MIPMAP_NEAREST 0x2700 | |
#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 | |
#define GL_SRC_ALPHA_SATURATE 0x0308 | |
#define GL_STREAM_DRAW 0x88E0 | |
#define GL_ONE 1 | |
#define GL_NEAREST_MIPMAP_LINEAR 0x2702 | |
#define GL_RGB10_A2 0x8059 | |
#define GL_RGBA8 0x8058 | |
#define GL_COLOR_ATTACHMENT1 0x8CE1 | |
#define GL_RGBA4 0x8056 | |
#define GL_RGB8 0x8051 | |
#define GL_ARRAY_BUFFER 0x8892 | |
#define GL_STENCIL 0x1802 | |
#define GL_TEXTURE_2D 0x0DE1 | |
#define GL_DEPTH 0x1801 | |
#define GL_FRONT 0x0404 | |
#define GL_STENCIL_BUFFER_BIT 0x00000400 | |
#define GL_REPEAT 0x2901 | |
#define GL_RGBA 0x1908 | |
#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 | |
#define GL_DECR 0x1E03 | |
#define GL_FRAGMENT_SHADER 0x8B30 | |
#define GL_FLOAT 0x1406 | |
#define GL_TEXTURE_MAX_LOD 0x813B | |
#define GL_DEPTH_COMPONENT 0x1902 | |
#define GL_ONE_MINUS_DST_ALPHA 0x0305 | |
#define GL_COLOR 0x1800 | |
#define GL_TEXTURE_2D_ARRAY 0x8C1A | |
#define GL_TRIANGLES 0x0004 | |
#define GL_UNSIGNED_BYTE 0x1401 | |
#define GL_TEXTURE_MAG_FILTER 0x2800 | |
#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 | |
#define GL_NONE 0 | |
#define GL_SRC_COLOR 0x0300 | |
#define GL_BYTE 0x1400 | |
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A | |
#define GL_LINE_STRIP 0x0003 | |
#define GL_TEXTURE_3D 0x806F | |
#define GL_CW 0x0900 | |
#define GL_LINEAR 0x2601 | |
#define GL_RENDERBUFFER 0x8D41 | |
#define GL_GEQUAL 0x0206 | |
#define GL_COLOR_BUFFER_BIT 0x00004000 | |
#define GL_RGBA32F 0x8814 | |
#define GL_BLEND 0x0BE2 | |
#define GL_ONE_MINUS_SRC_ALPHA 0x0303 | |
#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 | |
#define GL_TEXTURE_WRAP_T 0x2803 | |
#define GL_TEXTURE_WRAP_S 0x2802 | |
#define GL_TEXTURE_MIN_FILTER 0x2801 | |
#define GL_LINEAR_MIPMAP_NEAREST 0x2701 | |
#define GL_EXTENSIONS 0x1F03 | |
#define GL_NO_ERROR 0 | |
#define GL_REPLACE 0x1E01 | |
#define GL_KEEP 0x1E00 | |
#define GL_CCW 0x0901 | |
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 | |
#define GL_RGB 0x1907 | |
#define GL_TRIANGLE_STRIP 0x0005 | |
#define GL_TRIANGLE_FAN 0x0006 | |
#define GL_FALSE 0 | |
#define GL_ZERO 0 | |
#define GL_CULL_FACE 0x0B44 | |
#define GL_INVERT 0x150A | |
#define GL_INT 0x1404 | |
#define GL_UNSIGNED_INT 0x1405 | |
#define GL_UNSIGNED_SHORT 0x1403 | |
#define GL_NEAREST 0x2600 | |
#define GL_SCISSOR_TEST 0x0C11 | |
#define GL_LEQUAL 0x0203 | |
#define GL_STENCIL_TEST 0x0B90 | |
#define GL_DITHER 0x0BD0 | |
#define GL_DEPTH_COMPONENT16 0x81A5 | |
#define GL_EQUAL 0x0202 | |
#define GL_FRAMEBUFFER 0x8D40 | |
#define GL_RGB5 0x8050 | |
#define GL_LINES 0x0001 | |
#define GL_DEPTH_BUFFER_BIT 0x00000100 | |
#define GL_SRC_ALPHA 0x0302 | |
#define GL_INCR_WRAP 0x8507 | |
#define GL_LESS 0x0201 | |
#define GL_MULTISAMPLE 0x809D | |
#define GL_FRAMEBUFFER_BINDING 0x8CA6 | |
#define GL_BACK 0x0405 | |
#define GL_ALWAYS 0x0207 | |
#define GL_FUNC_ADD 0x8006 | |
#define GL_ONE_MINUS_DST_COLOR 0x0307 | |
#define GL_NOTEQUAL 0x0205 | |
#define GL_DST_COLOR 0x0306 | |
#define GL_COMPILE_STATUS 0x8B81 | |
#if defined (GL_ES2_COMPAT) | |
#define GL_RED 0x1909 // GL_LUMINANCE | |
#else | |
#define GL_RED 0x1903 | |
#endif | |
#define GL_COLOR_ATTACHMENT3 0x8CE3 | |
#define GL_DST_ALPHA 0x0304 | |
#define GL_RGB5_A1 0x8057 | |
#define GL_GREATER 0x0204 | |
#define GL_POLYGON_OFFSET_FILL 0x8037 | |
#define GL_TRUE 1 | |
#define GL_NEVER 0x0200 | |
#define GL_POINTS 0x0000 | |
#define GL_ONE_MINUS_SRC_COLOR 0x0301 | |
#define GL_MIRRORED_REPEAT 0x8370 | |
#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D | |
#define GL_R11F_G11F_B10F 0x8C3A | |
#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B | |
#define GL_RGBA32UI 0x8D70 | |
#define GL_RGB32UI 0x8D71 | |
#define GL_RGBA16UI 0x8D76 | |
#define GL_RGB16UI 0x8D77 | |
#define GL_RGBA8UI 0x8D7C | |
#define GL_RGB8UI 0x8D7D | |
#define GL_RGBA32I 0x8D82 | |
#define GL_RGB32I 0x8D83 | |
#define GL_RGBA16I 0x8D88 | |
#define GL_RGB16I 0x8D89 | |
#define GL_RGBA8I 0x8D8E | |
#define GL_RGB8I 0x8D8F | |
#define GL_RED_INTEGER 0x8D94 | |
#define GL_RG 0x8227 | |
#define GL_RG_INTEGER 0x8228 | |
#if defined (GL_ES2_COMPAT) | |
#define GL_R8 0x1909 // GL_LUMINANCE | |
#else | |
#define GL_R8 0x8229 | |
#endif | |
#define GL_R16 0x822A | |
#define GL_RG8 0x822B | |
#define GL_RG16 0x822C | |
#define GL_R16F 0x822D | |
#define GL_R32F 0x822E | |
#define GL_RG16F 0x822F | |
#define GL_RG32F 0x8230 | |
#define GL_R8I 0x8231 | |
#define GL_R8UI 0x8232 | |
#define GL_R16I 0x8233 | |
#define GL_R16UI 0x8234 | |
#define GL_R32I 0x8235 | |
#define GL_R32UI 0x8236 | |
#define GL_RG8I 0x8237 | |
#define GL_RG8UI 0x8238 | |
#define GL_RG16I 0x8239 | |
#define GL_RG16UI 0x823A | |
#define GL_RG32I 0x823B | |
#define GL_RG32UI 0x823C | |
#define GL_RGBA_INTEGER 0x8D99 | |
#define GL_R8_SNORM 0x8F94 | |
#define GL_RG8_SNORM 0x8F95 | |
#define GL_RGB8_SNORM 0x8F96 | |
#define GL_RGBA8_SNORM 0x8F97 | |
#define GL_R16_SNORM 0x8F98 | |
#define GL_RG16_SNORM 0x8F99 | |
#define GL_RGB16_SNORM 0x8F9A | |
#define GL_RGBA16_SNORM 0x8F9B | |
#define GL_RGBA16 0x805B | |
#define GL_MAX_TEXTURE_SIZE 0x0D33 | |
#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C | |
#define GL_MAX_3D_TEXTURE_SIZE 0x8073 | |
#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF | |
#define GL_MAX_VERTEX_ATTRIBS 0x8869 | |
#define GL_CLAMP_TO_BORDER 0x812D | |
#define GL_TEXTURE_BORDER_COLOR 0x1004 | |
#define GL_CURRENT_PROGRAM 0x8B8D | |
#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB | |
#define GL_UNPACK_ALIGNMENT 0x0CF5 | |
#define GL_PACK_ALIGNMENT 0x0D05 | |
#define APP__GL_FUNCS \ | |
APP__GL_XMACRO(glBindVertexArray, void, (GLuint array)) \ | |
APP__GL_XMACRO(glFramebufferTextureLayer, void, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)) \ | |
APP__GL_XMACRO(glGenFramebuffers, void, (GLsizei n, GLuint * framebuffers)) \ | |
APP__GL_XMACRO(glBindFramebuffer, void, (GLenum target, GLuint framebuffer)) \ | |
APP__GL_XMACRO(glBindRenderbuffer, void, (GLenum target, GLuint renderbuffer)) \ | |
APP__GL_XMACRO(glGetStringi, const GLubyte *, (GLenum name, GLuint index)) \ | |
APP__GL_XMACRO(glClearBufferfi, void, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)) \ | |
APP__GL_XMACRO(glClearBufferfv, void, (GLenum buffer, GLint drawbuffer, const GLfloat * value)) \ | |
APP__GL_XMACRO(glClearBufferuiv, void, (GLenum buffer, GLint drawbuffer, const GLuint * value)) \ | |
APP__GL_XMACRO(glClearBufferiv, void, (GLenum buffer, GLint drawbuffer, const GLint * value)) \ | |
APP__GL_XMACRO(glDeleteRenderbuffers, void, (GLsizei n, const GLuint * renderbuffers)) \ | |
APP__GL_XMACRO(glUniform1i, void, (GLint location, GLint v0)) \ | |
APP__GL_XMACRO(glUniform2i, void, (GLint location, GLint v0, GLint v1)) \ | |
APP__GL_XMACRO(glUniform1f, void, (GLint location, GLfloat v0)) \ | |
APP__GL_XMACRO(glUniform2f, void, (GLint location, GLfloat v0, GLfloat v1)) \ | |
APP__GL_XMACRO(glUniform3f, void, (GLint location, GLfloat v0, GLfloat v1, GLfloat v2)) \ | |
APP__GL_XMACRO(glUniform4f, void, (GLint location, const GLfloat v0, const GLfloat v1, const GLfloat v2, const GLfloat v3)) \ | |
APP__GL_XMACRO(glUniform1fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ | |
APP__GL_XMACRO(glUniform2fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ | |
APP__GL_XMACRO(glUniform3fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ | |
APP__GL_XMACRO(glUniform4fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ | |
APP__GL_XMACRO(glUniformMatrix4fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)) \ | |
APP__GL_XMACRO(glUseProgram, void, (GLuint program)) \ | |
APP__GL_XMACRO(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length)) \ | |
APP__GL_XMACRO(glLinkProgram, void, (GLuint program)) \ | |
APP__GL_XMACRO(glGetUniformLocation, GLint, (GLuint program, const GLchar * name)) \ | |
APP__GL_XMACRO(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint * params)) \ | |
APP__GL_XMACRO(glGetProgramInfoLog, void, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ | |
APP__GL_XMACRO(glGetAttribLocation, GLint, (GLuint program, const GLchar * name)) \ | |
APP__GL_XMACRO(glDisableVertexAttribArray, void, (GLuint index)) \ | |
APP__GL_XMACRO(glDeleteShader, void, (GLuint shader)) \ | |
APP__GL_XMACRO(glDeleteProgram, void, (GLuint program)) \ | |
APP__GL_XMACRO(glCompileShader, void, (GLuint shader)) \ | |
APP__GL_XMACRO(glStencilFuncSeparate, void, (GLenum face, GLenum func, GLint ref, GLuint mask)) \ | |
APP__GL_XMACRO(glStencilOpSeparate, void, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) \ | |
APP__GL_XMACRO(glRenderbufferStorageMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)) \ | |
APP__GL_XMACRO(glDrawBuffers, void, (GLsizei n, const GLenum * bufs)) \ | |
APP__GL_XMACRO(glVertexAttribDivisor, void, (GLuint index, GLuint divisor)) \ | |
APP__GL_XMACRO(glBufferSubData, void, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data)) \ | |
APP__GL_XMACRO(glGenBuffers, void, (GLsizei n, GLuint * buffers)) \ | |
APP__GL_XMACRO(glCheckFramebufferStatus, GLenum, (GLenum target)) \ | |
APP__GL_XMACRO(glFramebufferRenderbuffer, void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \ | |
APP__GL_XMACRO(glCompressedTexImage2D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data)) \ | |
APP__GL_XMACRO(glCompressedTexImage3D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data)) \ | |
APP__GL_XMACRO(glActiveTexture, void, (GLenum texture)) \ | |
APP__GL_XMACRO(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels)) \ | |
APP__GL_XMACRO(glTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels)) \ | |
APP__GL_XMACRO(glRenderbufferStorage, void, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) \ | |
APP__GL_XMACRO(glGenTextures, void, (GLsizei n, GLuint * textures)) \ | |
APP__GL_XMACRO(glPolygonOffset, void, (GLfloat factor, GLfloat units)) \ | |
APP__GL_XMACRO(glDrawElements, void, (GLenum mode, GLsizei count, GLenum type, const void * indices)) \ | |
APP__GL_XMACRO(glDeleteFramebuffers, void, (GLsizei n, const GLuint * framebuffers)) \ | |
APP__GL_XMACRO(glBlendEquationSeparate, void, (GLenum modeRGB, GLenum modeAlpha)) \ | |
APP__GL_XMACRO(glDeleteTextures, void, (GLsizei n, const GLuint * textures)) \ | |
APP__GL_XMACRO(glGetProgramiv, void, (GLuint program, GLenum pname, GLint * params)) \ | |
APP__GL_XMACRO(glBindTexture, void, (GLenum target, GLuint texture)) \ | |
APP__GL_XMACRO(glTexImage3D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels)) \ | |
APP__GL_XMACRO(glCreateShader, GLuint, (GLenum type)) \ | |
APP__GL_XMACRO(glClearDepth, void, (GLdouble depth)) \ | |
APP__GL_XMACRO(glFramebufferTexture2D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ | |
APP__GL_XMACRO(glCreateProgram, GLuint, (void)) \ | |
APP__GL_XMACRO(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ | |
APP__GL_XMACRO(glDeleteBuffers, void, (GLsizei n, const GLuint * buffers)) \ | |
APP__GL_XMACRO(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) \ | |
APP__GL_XMACRO(glDrawElementsInstanced, void, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount)) \ | |
APP__GL_XMACRO(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)) \ | |
APP__GL_XMACRO(glDisable, void, (GLenum cap)) \ | |
APP__GL_XMACRO(glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ | |
APP__GL_XMACRO(glColorMaski, void, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ | |
APP__GL_XMACRO(glBindBuffer, void, (GLenum target, GLuint buffer)) \ | |
APP__GL_XMACRO(glDeleteVertexArrays, void, (GLsizei n, const GLuint * arrays)) \ | |
APP__GL_XMACRO(glDepthMask, void, (GLboolean flag)) \ | |
APP__GL_XMACRO(glDrawArraysInstanced, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \ | |
APP__GL_XMACRO(glClearStencil, void, (GLint s)) \ | |
APP__GL_XMACRO(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ | |
APP__GL_XMACRO(glGenRenderbuffers, void, (GLsizei n, GLuint * renderbuffers)) \ | |
APP__GL_XMACRO(glBufferData, void, (GLenum target, GLsizeiptr size, const void * data, GLenum usage)) \ | |
APP__GL_XMACRO(glBlendFuncSeparate, void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) \ | |
APP__GL_XMACRO(glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) \ | |
APP__GL_XMACRO(glGetIntegerv, void, (GLenum pname, GLint * data)) \ | |
APP__GL_XMACRO(glEnable, void, (GLenum cap)) \ | |
APP__GL_XMACRO(glBlitFramebuffer, void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \ | |
APP__GL_XMACRO(glStencilMask, void, (GLuint mask)) \ | |
APP__GL_XMACRO(glAttachShader, void, (GLuint program, GLuint shader)) \ | |
APP__GL_XMACRO(glDetachShader, void, (GLuint program, GLuint shader)) \ | |
APP__GL_XMACRO(glGetError, GLenum, (void)) \ | |
APP__GL_XMACRO(glClearColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ | |
APP__GL_XMACRO(glBlendColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ | |
APP__GL_XMACRO(glTexParameterf, void, (GLenum target, GLenum pname, GLfloat param)) \ | |
APP__GL_XMACRO(glTexParameterfv, void, (GLenum target, GLenum pname, GLfloat* params)) \ | |
APP__GL_XMACRO(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ | |
APP__GL_XMACRO(glDepthFunc, void, (GLenum func)) \ | |
APP__GL_XMACRO(glStencilOp , void, (GLenum fail, GLenum zfail, GLenum zpass)) \ | |
APP__GL_XMACRO(glStencilFunc, void, (GLenum func, GLint ref, GLuint mask)) \ | |
APP__GL_XMACRO(glEnableVertexAttribArray, void, (GLuint index)) \ | |
APP__GL_XMACRO(glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) \ | |
APP__GL_XMACRO(glReadBuffer, void, (GLenum src)) \ | |
APP__GL_XMACRO(glClear, void, (GLbitfield mask)) \ | |
APP__GL_XMACRO(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels)) \ | |
APP__GL_XMACRO(glGenVertexArrays, void, (GLsizei n, GLuint * arrays)) \ | |
APP__GL_XMACRO(glFrontFace, void, (GLenum mode)) \ | |
APP__GL_XMACRO(glCullFace, void, (GLenum mode)) \ | |
APP__GL_XMACRO(glFinish, void, (void)) \ | |
APP__GL_XMACRO(glPixelStorei, void, (GLenum pname, GLint param)) | |
#if defined(WIN32) | |
// generate GL function pointer typedefs | |
#define APP__GL_XMACRO(name, ret, args) typedef ret (GL_APIENTRY* PFN_ ## name) args; | |
APP__GL_FUNCS | |
#undef APP__GL_XMACRO | |
// generate GL extern function pointers | |
#define APP__GL_XMACRO(name, ret, args) extern PFN_ ## name name; | |
APP__GL_FUNCS | |
#undef APP__GL_XMACRO | |
#elif defined(APP_LINUX) | |
// generate extern functions | |
#define APP__GL_XMACRO(name, ret, args) extern ret name args; | |
APP__GL_FUNCS | |
#undef APP__GL_XMACRO | |
#elif defined(APP_WASM) | |
// This doesn't exist on webgl | |
void glBindVertexArray(GLuint array) {} | |
// APP__WA_JS(void, glFramebufferTextureLayer, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer), { | |
// | |
// }); | |
APP__WA_JS(void, glGenFramebuffers, (GLsizei n, GLuint * framebuffers), { | |
checkHeap(); | |
for (var i = 0; i < n; ++i) { | |
var framebuffer = Module.GLctx.createFramebuffer(); | |
if (!framebuffer) | |
{ | |
Module.GLrecordError(0x0502); // GL_INVALID_OPERATION | |
while(i < n) HEAP32[(((ids)+(i++*4))>>2)]=0; | |
return; | |
} | |
var id = Module.GLgetNewId(GLframebuffers); | |
framebuffer.name = id; | |
Module.GLframebuffers[id] = framebuffer; | |
HEAP32[(((framebuffers)+(i*4))>>2)] = id; | |
} | |
}) | |
APP__WA_JS(void, glBindFramebuffer, (GLenum target, GLuint framebuffer), { | |
Module.GLctx.bindFramebuffer(target, framebuffer ? Module.GLframebuffers[framebuffer] : null); | |
}) | |
// APP__WA_JS(void, glBindRenderbuffer, (GLenum target, GLuint renderbuffer), { | |
// | |
// }); | |
// APP__WA_JS(const GLubyte *, glGetStringi, (GLenum name, GLuint index), { | |
// | |
// }); | |
// APP__WA_JS(void, glClearBufferfi, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil), { | |
// | |
// }); | |
// APP__WA_JS(void, glClearBufferfv, (GLenum buffer, GLint drawbuffer, const GLfloat * value), { | |
// | |
// }); | |
// | |
// APP__WA_JS(void, glClearBufferuiv, (GLenum buffer, GLint drawbuffer, const GLuint * value), { | |
// | |
// }); | |
// | |
// APP__WA_JS(void, glClearBufferiv, (GLenum buffer, GLint drawbuffer, const GLint * value), { | |
// | |
// }); | |
// APP__WA_JS(void, glDeleteRenderbuffers,(GLsizei n, const GLuint * renderbuffers), { | |
// | |
// }); | |
APP__WA_JS(void, glUniform1i, (GLint location, GLint v0), { | |
Module.GLctx.uniform1i(Module.GLuniforms[location], v0); | |
}) | |
APP__WA_JS(void, glUniform2i, (GLint location, GLint v0, GLint v1), { | |
Module.GLctx.uniform2i(Module.GLuniforms[location], v0, v1); | |
}) | |
APP__WA_JS(void, glUniform1f, (GLint location, GLfloat v0), { | |
Module.GLctx.uniform1f(Module.GLuniforms[location], v0); | |
}) | |
APP__WA_JS(void, glUniform2f, (GLint location, GLfloat v0, GLfloat v1), { | |
Module.GLctx.uniform2f(Module.GLuniforms[location], v0, v1); | |
}) | |
APP__WA_JS(void, glUniform3f, (GLint location, GLfloat v0, GLfloat v1, GLfloat v2), { | |
Module.GLctx.uniform3f(Module.GLuniforms[location], v0, v1, v2); | |
}) | |
APP__WA_JS(void, glUniform4f, (GLint location, const GLfloat v0, const GLfloat v1, const GLfloat v2, const GLfloat v3), { | |
Module.GLctx.uniform4f(Module.GLuniforms[loccation], v0, v1, v2, v3); | |
}) | |
// APP__WA_JS(void, glUniform1fv, (GLint location, GLsizei count, const GLfloat * value), { | |
// | |
// }); | |
// | |
// APP__WA_JS(void, glUniform2fv, (GLint location, GLsizei count, const GLfloat * value), { | |
// | |
// }); | |
APP__WA_JS(void, glUniform3fv, (GLint location, GLsizei count, const GLfloat * value), { | |
checkHeap(); | |
var view; | |
if (3*count <= Module.GLMINI_TEMP_BUFFER_SIZE) { | |
// avoid allocation when uploading few enough uniforms | |
view = Module.GLminiTempBufferViews[3*count-1]; | |
for (var ptr = value>>2, i = 0; i != 3*count; i++) { | |
view[i] = HEAPF32[ptr+i]; | |
} | |
} | |
else view = HEAPF32.subarray((value)>>2,(value+count*12)>>2); | |
Module.GLctx.uniform3fv(Module.GLuniforms[location], view); | |
}) | |
// APP__WA_JS(void, glUniform4fv, (GLint location, GLsizei count, const GLfloat * value), { | |
// | |
// }); | |
APP__WA_JS(void, glUniformMatrix4fv, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value), { | |
checkHeap(); | |
count<<=4; | |
var view; | |
if (count <= Module.GLMINI_TEMP_BUFFER_SIZE) | |
{ | |
// avoid allocation when uploading few enough uniforms | |
view = Module.GLminiTempBufferViews[count-1]; | |
for (var ptr = value>>2, i = 0; i != count; i += 4) | |
{ | |
view[i ] = HEAPF32[ptr+i ]; | |
view[i+1] = HEAPF32[ptr+i+1]; | |
view[i+2] = HEAPF32[ptr+i+2]; | |
view[i+3] = HEAPF32[ptr+i+3]; | |
} | |
} | |
else view = HEAPF32.subarray((value)>>2,(value+count*4)>>2); | |
Module.GLctx.uniformMatrix4fv(Module.GLuniforms[location], !!transpose, view); | |
}) | |
APP__WA_JS(void, glUseProgram, (GLuint program), { | |
Module.GLctx.useProgram(program ? Module.GLprograms[program] : null); | |
}) | |
APP__WA_JS(void, glShaderSource, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length), { | |
checkHeap(); | |
var source = Module.GLgetSource(shader, count, string, length); | |
Module.GLctx.shaderSource(Module.GLshaders[shader], source); | |
}) | |
APP__WA_JS(void, glLinkProgram, (GLuint program), { | |
Module.GLctx.linkProgram(Module.GLprograms[program]); | |
Module.GLprogramInfos[program] = null; // uniforms no longer keep the same names after linking | |
Module.GLpopulateUniformTable(program);; | |
}) | |
APP__WA_JS(GLint, glGetUniformLocation, (GLuint program, const GLchar * name), { | |
checkHeap(); | |
name = Pointer_stringify(name); | |
var arrayOffset = 0; | |
if (name.indexOf(']', name.length-1) !== -1) { | |
// If user passed an array accessor "[index]", parse the array index off the accessor. | |
var ls = name.lastIndexOf('['); | |
var arrayIndex = name.slice(ls+1, -1); | |
if (arrayIndex.length > 0) | |
{ | |
arrayOffset = parseInt(arrayIndex); | |
if (arrayOffset < 0) return -1; | |
} | |
name = name.slice(0, ls); | |
} | |
var ptable = Module.GLprogramInfos[program]; | |
if (!ptable) return -1; | |
var utable = ptable.uniforms; | |
var uniformInfo = utable[name]; // returns pair [ dimension_of_uniform_array, uniform_location ] | |
if (uniformInfo && arrayOffset < uniformInfo[0]) { | |
// Check if user asked for an out-of-bounds element, i.e. for 'vec4 colors[3];' user could ask for 'colors[10]' which should return -1. | |
return uniformInfo[1] + arrayOffset; | |
} | |
return -1; | |
}) | |
APP__WA_JS(void, glGetShaderiv, (GLuint shader, GLenum pname, GLint * params), { | |
checkHeap(); | |
if (!params) { | |
// GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense | |
// if params == null, issue a GL error to notify user about it. | |
Module.GLrecordError(0x0501); // GL_INVALID_VALUE | |
return; | |
} | |
if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH | |
var log = Module.GLctx.getShaderInfoLog(Module.GLshaders[shader]); | |
if (log === null) log = '(unknown error)'; | |
HEAP32[((params)>>2)] = log.length + 1; | |
} | |
else if (pname == 0x8B88) { // GL_SHADER_SOURCE_LENGTH | |
var source = Module.GLctx.getShaderSource(Module.GLshaders[shader]); | |
var sourceLength = (source === null || source.length == 0) ? 0 : source.length + 1; | |
HEAP32[((params)>>2)] = sourceLength; | |
} | |
else HEAP32[((params)>>2)] = Module.GLctx.getShaderParameter(Module.GLshaders[shader], pname); | |
}) | |
APP__WA_JS(void, glGetProgramInfoLog, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog), { | |
checkHeap(); | |
var log = Module.GLctx.getProgramInfoLog(Module.GLprograms[program]); | |
if (log === null) log = '(unknown error)'; | |
if (bufSize > 0 && infoLog) | |
{ | |
var numBytesWrittenExclNull = stringToUTF8Array(log, infoLog, bufSize); | |
if (length) HEAP32[((length)>>2)]=numBytesWrittenExclNull; | |
} | |
else if (length) HEAP32[((length)>>2)]=0; | |
}) | |
APP__WA_JS(GLint, glGetAttribLocation, (GLuint program, const GLchar * name), { | |
checkHeap(); | |
program = Module.GLprograms[program]; | |
name = Pointer_stringify(name); | |
return Module.GLctx.getAttribLocation(program, name); | |
}) | |
APP__WA_JS(void, glDisableVertexAttribArray, (GLuint index), { | |
Module.GLctx.disableVertexAttribArray(index); | |
}) | |
APP__WA_JS(void, glDeleteShader, (GLuint shader), { | |
if (!shader) return; | |
var shader_object = Module.GLshaders[shader]; | |
if (!shader_object) | |
{ | |
// glDeleteShader actually signals an error when deleting a nonexisting object, unlike some other GL delete functions. | |
Module.GLrecordError(0x0501); // GL_INVALID_VALUE | |
return; | |
} | |
Module.GLctx.deleteShader(shader_object); | |
Module.GLshaders[shader] = null; | |
}) | |
APP__WA_JS(void, glDeleteProgram, (GLuint program), { | |
if (!program) return; | |
var program_object = Module.GLprograms[program]; | |
if (!program_object) | |
{ | |
// glDeleteProgram actually signals an error when deleting a nonexisting object, unlike some other GL delete functions. | |
Module.GLrecordError(0x0501); // GL_INVALID_VALUE | |
return; | |
} | |
Module.GLctx.deleteProgram(program_object); | |
program_object.name = 0; | |
Module.GLprograms[program] = null; | |
Module.GLprogramInfos[program] = null; | |
}) | |
APP__WA_JS(void, glCompileShader, (GLuint shader), { | |
Module.GLctx.compileShader(Module.GLshaders[shader]); | |
}) | |
// APP__WA_JS(void, glStencilFuncSeparate, (GLenum face, GLenum func, GLint ref, GLuint mask), { | |
// | |
// }); | |
// | |
// APP__WA_JS(void, glStencilOpSeparate, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass), { | |
// | |
// }); | |
// APP__WA_JS(void, glRenderbufferStorageMultisample, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height), { | |
// | |
// }); | |
// APP__WA_JS(void, glDrawBuffers, (GLsizei n, const GLenum * bufs), { | |
// | |
// }); | |
// APP__WA_JS(void, glVertexAttribDivisor, (GLuint index, GLuint divisor), { | |
// | |
// }); | |
APP__WA_JS(void, glBufferSubData, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data), { | |
checkHeap(); | |
Module.GLctx.bufferSubData(target, offset, HEAPU8.subarray(data, data+size)); | |
}) | |
APP__WA_JS(void, glGenBuffers, (GLsizei n, GLuint *buffers), { | |
checkHeap(); | |
for (var i = 0; i < n; i++) { | |
var buffer = Module.GLctx.createBuffer(); | |
if (!buffer) { | |
Module.GLrecordError(0x0502); // GL_INVALID_OPERATION | |
while(i < n) HEAP32[(((buffers)+(i++*4))>>2)]=0; | |
return; | |
} | |
var id = Module.GLgetNewId(Module.GLbuffers); | |
buffer.name = id; | |
Module.GLbuffers[id] = buffer; | |
HEAP32[(((buffers)+(i*4))>>2)]=id; | |
} | |
}) | |
// APP__WA_JS(GLenum, glCheckFramebufferStatus, (GLenum target), { | |
// | |
// }); | |
// APP__WA_JS(void, glFramebufferRenderbuffer, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer), { | |
// | |
// }); | |
// APP__WA_JS(void, glCompressedTexImage2D, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data), { | |
// | |
// }); | |
// | |
// APP__WA_JS(void, glCompressedTexImage3D, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data), { | |
// | |
// }); | |
APP__WA_JS(void, glActiveTexture, (GLenum texture), { | |
Module.GLctx.activeTexture(texture); | |
}) | |
APP__WA_JS(void, glTexSubImage2D, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels), { | |
checkHeap(); | |
var pixelData = null; | |
if (pixels) pixelData = Module.webGLGetTexPixelData(type, format, width, height, pixels, 0); | |
Module.GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixelData); | |
}) | |
// APP__WA_JS(void, glTexSubImage3D, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels), { | |
// | |
// }); | |
// APP__WA_JS(void, glRenderbufferStorage, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height), { | |
// | |
// }); | |
APP__WA_JS(void, glGenTextures, (GLsizei n, GLuint * textures), { | |
checkHeap(); | |
for (var i = 0; i < n; i++) { | |
var texture = Module.GLctx.createTexture(); | |
if (!texture) { | |
// GLES + EGL specs don't specify what should happen here, so best to issue an error and create IDs with 0. | |
Module.GLrecordError(0x0502); // GL_INVALID_OPERATION | |
while(i < n) HEAP32[(((textures)+(i++*4))>>2)]=0; | |
return; | |
} | |
var id = Module.GLgetNewId(Module.GLtextures); | |
texture.name = id; | |
Module.GLtextures[id] = texture; | |
HEAP32[(((textures)+(i*4))>>2)]=id; | |
} | |
}) | |
// APP__WA_JS(void, glPolygonOffset, (GLfloat factor, GLfloat units), { | |
// | |
// }); | |
APP__WA_JS(void, glDrawElements, (GLenum mode, GLsizei count, GLenum type, const void * indices), { | |
Module.GLctx.drawElements(mode, count, type, indices); | |
}) | |
APP__WA_JS(void, glDeleteFramebuffers, (GLsizei n, const GLuint *framebuffers), { | |
checkHeap(); | |
for (var i = 0; i < n; ++i) | |
{ | |
var id = HEAP32[(((framebuffers)+(i*4))>>2)]; | |
var framebuffer = Module.GLframebuffers[id]; | |
if (!framebuffer) continue; // GL spec: "glDeleteFramebuffers silently ignores 0s and names that do not correspond to existing framebuffer objects". | |
Module.GLctx.deleteFramebuffer(framebuffer); | |
framebuffer.name = 0; | |
Module.GLframebuffers[id] = null; | |
} | |
}) | |
APP__WA_JS(void, glBlendEquationSeparate, (GLenum modeRGB, GLenum modeAlpha), { | |
Module.GLctx.blendEquationSeparate(modeRGB, modeAlpha); | |
}) | |
APP__WA_JS(void, glDeleteTextures, (GLsizei n, const GLuint *textures), { | |
checkHeap(); | |
for (var i = 0; i < n; i++) { | |
var id = HEAP32[(((textures)+(i*4))>>2)]; | |
var texture = Module.GLtextures[id]; | |
if (!texture) continue; // GL spec: "glDeleteTextures silently ignores 0s and names that do not correspond to existing textures". | |
Module.GLctx.deleteTexture(texture); | |
texture.name = 0; | |
Module.GLtextures[id] = null; | |
} | |
}) | |
APP__WA_JS(void, glGetProgramiv, (GLuint program, GLenum pname, GLint * params), { | |
checkHeap(); | |
if (!params) { | |
// GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense | |
// if params == null, issue a GL error to notify user about it. | |
Module.GLrecordError(0x0501); // GL_INVALID_VALUE | |
return; | |
} | |
if (program >= GLcounter) { | |
Module.GLrecordError(0x0501); // GL_INVALID_VALUE | |
return; | |
} | |
var ptable = Module.GLprogramInfos[program]; | |
if (!ptable) { | |
Module.GLrecordError(0x0502); //GL_INVALID_OPERATION | |
return; | |
} | |
if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH | |
var log = Module.GLctx.getProgramInfoLog(Module.GLprograms[program]); | |
if (log === null) log = '(unknown error)'; | |
HEAP32[((params)>>2)] = log.length + 1; | |
} | |
else if (pname == 0x8B87) { //GL_ACTIVE_UNIFORM_MAX_LENGTH | |
HEAP32[((params)>>2)] = ptable.maxUniformLength; | |
} | |
else if (pname == 0x8B8A) { //GL_ACTIVE_ATTRIBUTE_MAX_LENGTH | |
if (ptable.maxAttributeLength == -1) { | |
program = Module.GLprograms[program]; | |
var numAttribs = Module.GLctx.getProgramParameter(program, Module.GLctx.ACTIVE_ATTRIBUTES); | |
ptable.maxAttributeLength = 0; // Spec says if there are no active attribs, 0 must be returned. | |
for (var i = 0; i < numAttribs; ++i) { | |
var activeAttrib = Module.GLctx.getActiveAttrib(program, i); | |
ptable.maxAttributeLength = Math.max(ptable.maxAttributeLength, activeAttrib.name.length+1); | |
} | |
} | |
HEAP32[((params)>>2)] = ptable.maxAttributeLength; | |
} | |
else if (pname == 0x8A35) { //GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH | |
if (ptable.maxUniformBlockNameLength == -1) { | |
program = Module.GLprograms[program]; | |
var numBlocks = Module.GLctx.getProgramParameter(program, GLctx.ACTIVE_UNIFORM_BLOCKS); | |
ptable.maxUniformBlockNameLength = 0; | |
for (var i = 0; i < numBlocks; ++i) { | |
var activeBlockName = Module.GLctx.getActiveUniformBlockName(program, i); | |
ptable.maxUniformBlockNameLength = Math.max(ptable.maxUniformBlockNameLength, activeBlockName.length+1); | |
} | |
} | |
HEAP32[((params)>>2)] = ptable.maxUniformBlockNameLength; | |
} else { | |
HEAP32[((params)>>2)] = Module.GLctx.getProgramParameter(Module.GLprograms[program], pname); | |
} | |
}) | |
APP__WA_JS(void, glBindTexture, (GLenum target, GLuint texture), { | |
Module.GLctx.bindTexture(target, texture ? Module.GLtextures[texture] : null); | |
}) | |
APP__WA_JS(void, glTexImage2D, (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels), { | |
checkHeap(); | |
var pixelData = null; | |
if (pixels) pixelData = Module.webGLGetTexPixelData(type, format, width, height, pixels, internalFormat); | |
Module.GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixelData); | |
}) | |
// APP__WA_JS(void, glTexImage3D, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels), { | |
// | |
// }); | |
APP__WA_JS(GLuint, glCreateShader, (GLenum type), { | |
var id = Module.GLgetNewId(Module.GLshaders); | |
Module.GLshaders[id] = Module.GLctx.createShader(type); | |
return id; | |
}) | |
// APP__WA_JS(void, glClearDepth, (GLdouble depth), { | |
// | |
// }); | |
APP__WA_JS(void, glFramebufferTexture2D, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level), { | |
Module.GLctx.framebufferTexture2D(target, attachment, textarget, Module.GLtextures[texture], level); | |
}) | |
APP__WA_JS(GLuint, glCreateProgram, (void), { | |
var id = Module.GLgetNewId(Module.GLprograms); | |
var program = Module.GLctx.createProgram(); | |
program.name = id; | |
Module.GLprograms[id] = program; | |
return id; | |
}) | |
APP__WA_JS(void, glViewport, (GLint x, GLint y, GLsizei width, GLsizei height), { | |
Module.GLctx.viewport(x, y, width, height); | |
}) | |
APP__WA_JS(void, glDeleteBuffers, (GLsizei n, const GLuint *buffers), { | |
checkHeap(); | |
for (var i = 0; i < n; i++) { | |
var id = HEAP32[(((buffers)+(i*4))>>2)]; | |
var buffer = Module.GLbuffers[id]; | |
// From spec: "glDeleteBuffers silently ignores 0's and names that do not correspond to existing buffer objects." | |
if (!buffer) continue; | |
Module.GLctx.deleteBuffer(buffer); | |
buffer.name = 0; | |
Module.GLbuffers[id] = null; | |
} | |
}) | |
APP__WA_JS(void, glDrawArrays, (GLenum mode, GLint first, GLsizei count), { | |
Module.GLctx.drawArrays(mode, first, count); | |
}) | |
// APP__WA_JS(void, glDrawElementsInstanced, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount), { | |
// | |
// }); | |
APP__WA_JS(void, glVertexAttribPointer, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer), { | |
Module.GLctx.vertexAttribPointer(index, size, type, !!normalized, stride, pointer); | |
}) | |
APP__WA_JS(void, glDisable, (GLenum cap), { | |
Module.GLctx.disable(cap); | |
}) | |
APP__WA_JS(void, glColorMask, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha), { | |
Module.GLctx.colorMask(!!red, !!green, !!blue, !!alpha); | |
}) | |
// APP__WA_JS(void, glColorMaski, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha), { | |
// | |
// }); | |
APP__WA_JS(void, glBindBuffer, (GLenum target, GLuint buffer), { | |
Module.GLctx.bindBuffer(target, buffer ? Module.GLbuffers[buffer] : null); | |
}) | |
// APP__WA_JS(void, glDeleteVertexArrays, (GLsizei n, const GLuint *arrays), { | |
// | |
// }); | |
APP__WA_JS(void, glDepthMask, (GLboolean flag), { | |
Module.GLctx.depthMask(!!flag); | |
}) | |
// APP__WA_JS(void, glDrawArraysInstanced, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount), { | |
// | |
// }); | |
// APP__WA_JS(void, glClearStencil, (GLint s), { | |
// | |
// }); | |
APP__WA_JS(void, glScissor, (GLint x, GLint y, GLsizei width, GLsizei height), { | |
Module.GLctx.scissor(x, y, width, height); | |
}) | |
// APP__WA_JS(void, glGenRenderbuffers, (GLsizei n, GLuint * renderbuffers), { | |
// | |
// }); | |
APP__WA_JS(void, glBufferData, (GLenum target, GLsizeiptr size, const void *data, GLenum usage), { | |
checkHeap(); | |
if (!data) Module.GLctx.bufferData(target, size, usage); | |
else Module.GLctx.bufferData(target, HEAPU8.subarray(data, data+size), usage); | |
}) | |
APP__WA_JS(void, glBlendFuncSeparate, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha), { | |
Module.GLctx.blendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); | |
}) | |
APP__WA_JS(void, glTexParameteri, (GLenum target, GLenum pname, GLint param), { | |
Module.GLctx.texParameteri(target, pname, param); | |
}) | |
APP__WA_JS(void, glGetIntegerv, (GLenum pname, GLint *data), { | |
Module.webGLGet(pname, data, 'Integer'); | |
}) | |
APP__WA_JS(void, glEnable, (GLenum cap), { | |
Module.GLctx.enable(cap); | |
}) | |
// APP__WA_JS(void, glBlitFramebuffer, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter), { | |
// | |
// }); | |
// APP__WA_JS(void, glStencilMask, (GLuint mask), { | |
// | |
// }); | |
APP__WA_JS(void, glAttachShader, (GLuint program, GLuint shader), { | |
Module.GLctx.attachShader(Module.GLprograms[program], Module.GLshaders[shader]); | |
}) | |
APP__WA_JS(void, glDetachShader, (GLuint program, GLuint shader), { | |
Module.GLctx.detachShader(Module.GLprograms[program], Module.GLshaders[shader]); | |
}) | |
APP__WA_JS(GLenum, glGetError, (void), { | |
if (Module.GLlastError) { | |
var e = Module.GLlastError; | |
Module.GLlastError = 0; | |
return e; | |
} | |
return Module.GLctx.getError(); | |
}) | |
APP__WA_JS(void, glClearColor, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha), { | |
Module.GLctx.clearColor(red, green, blue, alpha); | |
}) | |
APP__WA_JS(void, glBlendColor, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha), { | |
Module.GLctx.blendColor(red, green, blue, alpha); | |
}) | |
// APP__WA_JS(void, glTexParameterf, (GLenum target, GLenum pname, GLfloat param), { | |
// | |
// }); | |
// | |
// APP__WA_JS(void, glTexParameterfv, (GLenum target, GLenum pname, GLfloat* params), { | |
// | |
// }); | |
APP__WA_JS(void, glGetShaderInfoLog, (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog), { | |
checkHeap(); | |
var log = Module.GLctx.getShaderInfoLog(Module.GLshaders[shader]); | |
if (log === null) log = '(unknown error)'; | |
if (bufSize > 0 && infoLog) | |
{ | |
var numBytesWrittenExclNull = stringToUTF8Array(log, infoLog, bufSize); | |
if (length) HEAP32[((length)>>2)] = numBytesWrittenExclNull; | |
} | |
else if (length) HEAP32[((length)>>2)] = 0; | |
}) | |
APP__WA_JS(void, glDepthFunc, (GLenum func), { | |
Module.GLctx.depthFunc(func); | |
}) | |
// APP__WA_JS(void, glStencilOp, (GLenum fail, GLenum zfail, GLenum zpass), { | |
// | |
// }); | |
// | |
// APP__WA_JS(void, glStencilFunc, (GLenum func, GLint ref, GLuint mask), { | |
// | |
// }); | |
APP__WA_JS(void, glEnableVertexAttribArray, (GLuint index), { | |
Module.GLctx.enableVertexAttribArray(index); | |
}) | |
APP__WA_JS(void, glBlendFunc, (GLenum sfactor, GLenum dfactor), { | |
Module.GLctx.blendFunc(sfactor, dfactor); | |
}) | |
// APP__WA_JS(void, glReadBuffer, (GLenum src), { | |
// | |
// }); | |
APP__WA_JS(void, glClear, (GLbitfield mask), { | |
Module.GLctx.clear(mask); | |
}) | |
// This doesn't exist on webgl | |
void glGenVertexArrays(GLsizei n, GLuint * arrays) {} | |
// APP__WA_JS(void, glFrontFace, (GLenum mode), { | |
// | |
// }); | |
// | |
// APP__WA_JS(void, glCullFace, (GLenum mode), { | |
// | |
// }); | |
// APP__WA_JS(void, glFinish, (void), { | |
// | |
// }); | |
APP__WA_JS(void, glPixelStorei, (GLenum pname, GLint param), { | |
if (pname == 0x0D05) Module.GLpackAlignment = param; //GL_PACK_ALIGNMENT | |
else if (pname == 0x0cf5) Module.GLunpackAlignment = param; //GL_UNPACK_ALIGNMENT | |
Module.GLctx.pixelStorei(pname, param); | |
}) | |
#endif | |
#ifndef APP__TEXT_INPUT_BUFFER_MAX | |
#define APP__TEXT_INPUT_BUFFER_MAX 1024 | |
#endif | |
APP_INTERNAL const char APP__default_text_input_filter[] = { | |
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, | |
26, 27, 28, 29, 30, 31, 127}; | |
typedef enum { | |
APP__KEY_STATUS_UP = 0x1, | |
APP__KEY_STATUS_PRESSED = 0x2, | |
APP__KEY_STATUS_DOWN = 0x4, | |
APP__KEY_STATUS_RELEASED = 0x8, | |
APP__KEY_STATUS_REPEAT = 0x10, | |
} APP__KeyStatus; | |
#ifndef APP__MAX_TOUCH_POINTS | |
#define APP__MAX_TOUCH_POINTS 10 | |
#endif | |
#define APP__MAX_FRAME_DURATION_SAMPLES 255 | |
// | |
// Global struct that will contain all the crossplatform application data | |
// | |
APP_INTERNAL struct { | |
int(*frame_callback)(void); | |
int w_width; | |
int w_height; | |
int default_width; | |
int default_height; | |
bool window_resized; | |
bool fullscreen_changed; | |
unsigned char keyboard[APP_MAX_KEYCODES]; | |
struct { | |
float x; | |
float y; | |
unsigned char buttons[3]; | |
float wheel_x; | |
float wheel_y; | |
} mouse; | |
bool focus; | |
// NOTE(Tano): We may split this into SOA (structures of arrays) to get more performance but | |
// anyway... | |
struct { | |
int id; | |
float x; | |
float y; | |
float force; | |
unsigned char state; | |
} touch_points[APP__MAX_TOUCH_POINTS]; | |
struct { | |
float x; | |
float y; | |
unsigned char state; | |
} pointer; | |
bool quit_requested; | |
char text_input_buffer[APP__TEXT_INPUT_BUFFER_MAX]; | |
unsigned int text_input_bytes_count; | |
const char *text_input_filter; | |
unsigned int text_input_filter_len; | |
// Manage frame duration and statistics | |
int64_t prev_time; | |
int64_t frame_duration_samples[APP__MAX_FRAME_DURATION_SAMPLES]; | |
int frame_duration_samples_count; | |
int frame_duration_samples_head; | |
// Sound player information | |
struct SoundPlayerInfo { | |
int sample_rate; | |
int num_channels; | |
int buffer_frames; | |
void *user_data; | |
APP_SoundPlayerCallback stream_cb; | |
} sound_player_info; | |
} APP__data = { | |
.text_input_filter = APP__default_text_input_filter, | |
.text_input_filter_len = sizeof(APP__default_text_input_filter), | |
}; | |
#if defined(APP_LINUX) | |
#include <pthread.h> | |
typedef pthread_mutex_t Mutex; | |
#elif defined(APP_WINDOWS) | |
typedef HANDLE Mutex; | |
#elif defined(APP_WASM) | |
typedef bool Mutex; | |
#endif | |
void | |
Mutex_init(Mutex *mutex); | |
void | |
Mutex_deinit(Mutex *mutex); | |
void | |
Mutex_lock(Mutex *mutex); | |
void | |
Mutex_unlock(Mutex *mutex); | |
///////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// IMPLEMENTATION STARTS HERE | |
// | |
// | | | | | | | | | | | | | | | | | | | |
// V V V V V V V V V V V V V V V V V V | |
///////////////////////////////////////////////////////////////////////////////// | |
APP__EXPORT(APP__Report_mouse_button_down) void | |
APP__Report_mouse_button_down(APP_MouseButton button_id); | |
APP__EXPORT(APP__Report_mouse_button_up) void | |
APP__Report_mouse_button_up(APP_MouseButton button_id); | |
APP__EXPORT(APP__Report_mouse_move) void | |
APP__Report_mouse_move(float x, float y); | |
APP__EXPORT(APP__Report_touch_event) void | |
APP__Report_touch_event(int id, int state, float x, float y); | |
APP__EXPORT(APP__Handle_frame) int | |
APP__Handle_frame(void); | |
#if defined (APP_LINUX) | |
APP_INTERNAL int APP__linux_Init(const char *title, int width, int height); | |
APP_INTERNAL void APP__linux_Shutdown(void); | |
APP_INTERNAL void APP__linux_Process_events(void); | |
APP_INTERNAL void APP__linux_Swap_buffers(void); | |
APP_INTERNAL void APP__linux_Set_fullscreen(bool enable); | |
APP_INTERNAL void APP__linux_Set_swap_interval(unsigned int interval); | |
APP_INTERNAL void APP__linux_Show_mouse(bool show); | |
APP_INTERNAL void APP__linux_Set_window_title(const char *title); | |
APP_INTERNAL int64_t APP__linux_Time(void); | |
#elif defined (APP_WINDOWS) | |
APP_INTERNAL int APP__win32_Init(const char *title, int width, int height); | |
APP_INTERNAL void APP__win32_Shutdown(void); | |
APP_INTERNAL void APP__win32_Process_events(void); | |
APP_INTERNAL void APP__win32_Swap_buffers(void); | |
APP_INTERNAL void APP__win32_Set_fullscreen(bool enable); | |
APP_INTERNAL void APP__win32_Set_swap_interval(unsigned int interval); | |
APP_INTERNAL void APP__win32_Show_mouse(bool show); | |
APP_INTERNAL void APP__win32_Set_window_title(const char *title); | |
APP_INTERNAL int64_t APP__win32_Time(void); | |
#elif defined (APP_WASM) | |
static struct { | |
int64_t initial_time; | |
} APP__wasm_data = {0}; | |
APP_INTERNAL int APP__wasm_Init(const char *title, int width, int height); | |
APP_INTERNAL void APP__wasm_Shutdown(void); | |
APP_INTERNAL void APP__wasm_Process_events(void); | |
APP_INTERNAL void APP__wasm_Set_fullscreen(bool enable); | |
APP_INTERNAL void APP__wasm_Show_mouse(bool show); | |
APP_INTERNAL void APP__wasm_Set_window_title(const char *title); | |
APP_INTERNAL int64_t APP__wasm_Time(void); | |
#endif | |
APP_PUBLIC int | |
APP_Init(const char *title, int width, int height) { | |
APP__data.focus = true; | |
// Init the touch points id | |
for (int i = 0; i < APP__MAX_TOUCH_POINTS; i+=1) { | |
APP__data.touch_points[i].id = -1; | |
} | |
#if defined (APP_LINUX) | |
return APP__linux_Init(title, width, height); | |
#elif defined (APP_WINDOWS) | |
return APP__win32_Init(title, width, height); | |
#elif defined (APP_WASM) | |
return APP__wasm_Init(title, width, height); | |
#endif | |
APP__data.prev_time = APP_Time(); | |
} | |
// Destroys the window | |
APP_PUBLIC void | |
APP_Destroy_window(void) { | |
#if defined (APP_LINUX) | |
APP__linux_Shutdown(); | |
#elif defined (APP_WINDOWS) | |
APP__win32_Shutdown(); | |
#elif defined (APP_WASM) | |
APP__wasm_Shutdown(); | |
#endif | |
} | |
APP_PUBLIC int | |
APP_Get_window_width(void) { | |
return APP__data.w_width; | |
} | |
APP_PUBLIC int | |
APP_Get_window_height(void) { | |
return APP__data.w_height; | |
} | |
APP_PUBLIC bool | |
APP_Window_resized(void) { | |
return APP__data.window_resized; | |
} | |
APP_PUBLIC bool | |
APP_Quit_requested(void) { | |
return APP__data.quit_requested; | |
} | |
APP_PUBLIC bool | |
APP_Is_key_up(APP_KeyCode key) { | |
bool result = (APP__data.keyboard[key] & APP__KEY_STATUS_UP) != 0; | |
return result; | |
} | |
APP_PUBLIC bool | |
APP_Is_key_pressed(APP_KeyCode key) { | |
bool result = (APP__data.keyboard[key] & APP__KEY_STATUS_PRESSED) != 0; | |
return result; | |
} | |
APP_PUBLIC bool | |
APP_Is_key_down(APP_KeyCode key) { | |
bool result = (APP__data.keyboard[key] & APP__KEY_STATUS_DOWN) != 0; | |
return result; | |
} | |
APP_PUBLIC bool | |
APP_Is_key_released(APP_KeyCode key) { | |
bool result = (APP__data.keyboard[key] & APP__KEY_STATUS_RELEASED) != 0; | |
return result; | |
} | |
APP_PUBLIC bool | |
APP_Is_key_repeat(APP_KeyCode key) { | |
bool result = (APP__data.keyboard[key] & APP__KEY_STATUS_REPEAT) != 0; | |
return result; | |
} | |
APP_PUBLIC const char * | |
APP_Get_text_input(void) { | |
return APP__data.text_input_buffer; | |
} | |
APP_PUBLIC unsigned int | |
APP_Get_text_input_length(void) { | |
return APP__data.text_input_bytes_count; | |
} | |
APP_PUBLIC float | |
APP_Get_mouse_x(void) { | |
float result = APP__data.mouse.x; | |
return result; | |
} | |
APP_PUBLIC float | |
APP_Get_mouse_y(void) { | |
float result = APP__data.mouse.y; | |
return result; | |
} | |
APP_PUBLIC float | |
APP_Get_mouse_y_inv(void) { | |
float y = APP__data.mouse.y; | |
float w_height = APP__data.w_height; | |
float result = w_height - y; | |
return result; | |
} | |
APP_PUBLIC bool | |
APP_Is_mouse_button_down(APP_MouseButton button) { | |
bool result = APP__data.mouse.buttons[button] == APP__KEY_STATUS_DOWN; | |
return result; | |
} | |
APP_PUBLIC bool | |
APP_Is_mouse_button_pressed(APP_MouseButton button) { | |
bool result = APP__data.mouse.buttons[button] == APP__KEY_STATUS_PRESSED; | |
return result; | |
} | |
APP_PUBLIC bool | |
APP_Is_mouse_button_released(APP_MouseButton button) { | |
bool result = APP__data.mouse.buttons[button] == APP__KEY_STATUS_RELEASED; | |
return result; | |
} | |
APP_PUBLIC bool | |
APP_Is_mouse_button_up(APP_MouseButton button) { | |
bool result = APP__data.mouse.buttons[button] == APP__KEY_STATUS_UP; | |
return result; | |
} | |
APP_PUBLIC float | |
APP_Get_wheel_x(void) { | |
return APP__data.mouse.wheel_x; | |
} | |
APP_PUBLIC float | |
APP_Get_wheel_y(void) { | |
return APP__data.mouse.wheel_y; | |
} | |
APP_PUBLIC float | |
APP_Get_touch_x(int id) { | |
if (id < 0 || id >= APP__MAX_TOUCH_POINTS) return 0.0f; | |
return APP__data.touch_points[id].x; | |
} | |
APP_PUBLIC float | |
APP_Get_touch_y(int id) { | |
if (id < 0 || id >= APP__MAX_TOUCH_POINTS) return 0.0f; | |
return APP__data.touch_points[id].y; | |
} | |
APP_PUBLIC unsigned char | |
APP_Get_touch_state(int id) { | |
if (id < 0 || id >= APP__MAX_TOUCH_POINTS) return APP_TOUCH_STATE_INVALID; | |
return APP__data.touch_points[id].state; | |
} | |
APP_PUBLIC float | |
APP_Get_pointer_x(void) { | |
return APP__data.pointer.x; | |
} | |
APP_PUBLIC float | |
APP_Get_pointer_y(void) { | |
return APP__data.pointer.y; | |
} | |
APP_PUBLIC unsigned char | |
APP_Get_pointer_state(void) { | |
return APP__data.pointer.state; | |
} | |
APP_PUBLIC void | |
APP_Set_fullscreen(bool enable) { | |
#if defined (APP_LINUX) | |
APP__linux_Set_fullscreen(enable); | |
#elif defined (APP_WINDOWS) | |
APP__win32_Set_fullscreen(enable); | |
#elif defined (APP_WASM) | |
APP__wasm_Set_fullscreen(enable); | |
#endif | |
} | |
APP_PUBLIC void | |
APP_Set_swap_interval(unsigned int interval) { | |
#if defined (APP_LINUX) | |
APP__linux_Set_swap_interval(interval); | |
#elif defined (APP_WINDOWS) | |
APP__win32_Set_swap_interval(interval); | |
#elif defined (APP_WASM) | |
// This doesn't work on wasm | |
#endif | |
} | |
APP_PUBLIC void | |
APP_Show_mouse(bool show) { | |
#if defined (APP_LINUX) | |
APP__linux_Show_mouse(show); | |
#elif defined (APP_WINDOWS) | |
APP__win32_Show_mouse(show); | |
#elif defined (APP_WASM) | |
APP__wasm_Show_mouse(show); | |
#endif | |
} | |
APP_PUBLIC void | |
APP_Set_window_title(const char *title) { | |
#if defined (APP_LINUX) | |
APP__linux_Set_window_title(title); | |
#elif defined (APP_WINDOWS) | |
APP__win32_Set_window_title(title); | |
#elif defined (APP_WASM) | |
APP__wasm_Set_window_title(title); | |
#endif | |
} | |
APP_PUBLIC int64_t | |
APP_Time(void) { | |
#if defined (APP_LINUX) | |
return APP__linux_Time(); | |
#elif defined (APP_WINDOWS) | |
return APP__win32_Time(); | |
#elif defined (APP_WASM) | |
return APP__wasm_Time(); | |
#endif | |
} | |
APP_PUBLIC int64_t | |
APP_Frame_duration(int samples) { | |
if (APP__data.frame_duration_samples_count < samples) { | |
samples = APP__data.frame_duration_samples_count; | |
} | |
if (samples == 0) { | |
return 0; | |
} | |
int head = APP__data.frame_duration_samples_head; | |
int64_t acum = 0; | |
for (int i = 0; i < samples; i+=1) { | |
acum += APP__data.frame_duration_samples[(head+i)%APP__MAX_FRAME_DURATION_SAMPLES]; | |
} | |
acum /= (int64_t)samples; | |
return acum; | |
} | |
APP_INTERNAL bool | |
APP__Is_char_filtered(char c) { | |
const int total = APP__data.text_input_filter_len; | |
const char *filter = APP__data.text_input_filter; | |
for (int i = 0; i < total; ++i) { | |
if (c == filter[i]) return true; | |
} | |
return false; | |
} | |
APP_INTERNAL void | |
APP__Clear_events(void) { | |
// Clear the text input stuff | |
APP__data.text_input_buffer[0] = '\0'; | |
APP__data.text_input_bytes_count = 0; | |
// We have to reset the window resize event every frame. | |
APP__data.window_resized = false; | |
// We must trigger the windows resize event if we went to fullscreen, the right dimensions | |
// are informed when the fullscreen has changed. | |
// On x11 and wasm the resize event is triggered anyway, so this doesn't matter. | |
if (APP__data.fullscreen_changed == true) { | |
APP__data.fullscreen_changed = false; | |
APP__data.window_resized = true; | |
} | |
// The keys that are UP must be NONE the next frame | |
for (int i = 0; i < APP_MAX_KEYCODES; ++i) { | |
if (APP__data.keyboard[i] & APP__KEY_STATUS_REPEAT) { | |
APP__data.keyboard[i] &= (~APP__KEY_STATUS_REPEAT); | |
} | |
if (APP__data.keyboard[i] & APP__KEY_STATUS_PRESSED) { | |
APP__data.keyboard[i] = APP__KEY_STATUS_DOWN; | |
} | |
if (APP__data.keyboard[i] & APP__KEY_STATUS_RELEASED) { | |
APP__data.keyboard[i] = APP__KEY_STATUS_UP; | |
} | |
} | |
// The same with mouse buttons | |
for (int i = 0; i < 3; i+=1) { | |
if (APP__data.mouse.buttons[i] & APP__KEY_STATUS_PRESSED) { | |
APP__data.mouse.buttons[i] = APP__KEY_STATUS_DOWN; | |
} | |
if (APP__data.mouse.buttons[i] & APP__KEY_STATUS_RELEASED) { | |
APP__data.mouse.buttons[i] = APP__KEY_STATUS_UP; | |
} | |
} | |
APP__data.mouse.wheel_x = 0.0f; | |
APP__data.mouse.wheel_y = 0.0f; | |
// Clear the touch points | |
for (int i = 0; i < APP__MAX_TOUCH_POINTS; i+=1) { | |
if (APP__data.touch_points[i].state & (APP_TOUCH_STATE_END|APP_TOUCH_STATE_CANCEL)) { | |
APP__data.touch_points[i].state = APP_TOUCH_STATE_INVALID; | |
APP__data.touch_points[i].id = -1; | |
} | |
} | |
if (APP__data.pointer.state & APP_POINTER_STATE_PRESSED) APP__data.pointer.state = APP_POINTER_STATE_DOWN; | |
if (APP__data.pointer.state & APP_POINTER_STATE_RELEASED) APP__data.pointer.state = APP_POINTER_STATE_UP; | |
if (APP__data.pointer.state == APP_POINTER_STATE_INVALID) APP__data.pointer.state = APP_POINTER_STATE_UP; | |
} | |
APP_INTERNAL void | |
APP__Put_keys_up(void) { | |
// The keys that are DOWN go UP | |
for (int i = 0; i < APP_MAX_KEYCODES; ++i) { | |
if (APP__data.keyboard[i] == APP__KEY_STATUS_PRESSED || | |
APP__data.keyboard[i] == APP__KEY_STATUS_DOWN) { | |
APP__data.keyboard[i] = APP__KEY_STATUS_RELEASED; | |
} | |
} | |
// The same with mouse buttons | |
for (int i = 0; i < 3; ++i) { | |
if (APP__data.mouse.buttons[i] == APP__KEY_STATUS_PRESSED || | |
APP__data.keyboard[i] == APP__KEY_STATUS_DOWN) { | |
APP__data.mouse.buttons[i] = APP__KEY_STATUS_RELEASED; | |
} | |
} | |
} | |
void | |
APP__Report_mouse_button_down(APP_MouseButton button_id) { | |
if (button_id < APP_MOUSE_BUTTON_LEFT || button_id >= APP_MOUSE_BUTTON_INVALID) return; | |
if (APP__data.mouse.buttons[button_id] == APP__KEY_STATUS_PRESSED || | |
APP__data.mouse.buttons[button_id] == APP__KEY_STATUS_DOWN) { | |
APP__data.mouse.buttons[button_id] = APP__KEY_STATUS_DOWN; | |
} | |
else { | |
APP__data.mouse.buttons[button_id] = APP__KEY_STATUS_PRESSED; | |
} | |
// Handle pointer case | |
if (button_id == APP_MOUSE_BUTTON_LEFT) { | |
if (APP__data.pointer.state & APP_POINTER_STATE_PRESSED) { | |
APP__data.pointer.state = APP_POINTER_STATE_DOWN; | |
} | |
else { | |
APP__data.pointer.state = (APP_POINTER_STATE_PRESSED|APP_POINTER_STATE_DOWN); | |
} | |
} | |
} | |
void | |
APP__Report_mouse_button_up(APP_MouseButton button_id) { | |
if (button_id < APP_MOUSE_BUTTON_LEFT || button_id >= APP_MOUSE_BUTTON_INVALID) return; | |
APP__data.mouse.buttons[button_id] = APP__KEY_STATUS_RELEASED; | |
// Handle pointer case | |
if (button_id == APP_MOUSE_BUTTON_LEFT) { | |
APP__data.pointer.state = (APP_POINTER_STATE_RELEASED|APP_POINTER_STATE_UP); | |
} | |
} | |
void | |
APP__Report_mouse_move(float x, float y) { | |
APP__data.mouse.x = x; | |
APP__data.mouse.y = y; | |
APP__data.pointer.x = x; | |
APP__data.pointer.y = y; | |
} | |
void | |
APP__Report_touch_event(int id, int state, float x, float y) { | |
int target_index = -1; | |
for (int i = 0; i < APP__MAX_TOUCH_POINTS; i+=1) { | |
// We set the target to the first invalid point that we see | |
if (APP__data.touch_points[i].state == APP_TOUCH_STATE_INVALID && target_index < 0) target_index = i; | |
// If the touch point already exists and has a valid state we target it | |
else if (APP__data.touch_points[i].id == id) { | |
target_index = i; | |
break; | |
} | |
} | |
// If the target index is less than 0 means that all the touch points are in use | |
if (target_index >= 0) { | |
APP__data.touch_points[target_index].id = id; | |
APP__data.touch_points[target_index].x = x; | |
APP__data.touch_points[target_index].y = y; | |
if (state == 0) APP__data.touch_points[target_index].state = APP_TOUCH_STATE_BEGIN; | |
else if (state == 1) APP__data.touch_points[target_index].state = APP_TOUCH_STATE_MOVE; | |
else if (state == 2) APP__data.touch_points[target_index].state = APP_TOUCH_STATE_END; | |
else if (state == 3) APP__data.touch_points[target_index].state = APP_TOUCH_STATE_CANCEL; | |
else APP__data.touch_points[target_index].state = APP_TOUCH_STATE_INVALID; | |
//if (target_index == 0) { | |
APP__data.pointer.x = x; | |
APP__data.pointer.y = y; | |
if (state == 0 || state == 1) { | |
if (APP__data.pointer.state & APP_POINTER_STATE_PRESSED) { | |
APP__data.pointer.state = APP_POINTER_STATE_DOWN; | |
} | |
else { | |
APP__data.pointer.state = (APP_POINTER_STATE_PRESSED|APP_POINTER_STATE_DOWN); | |
} | |
} | |
else { | |
APP__data.pointer.state = (APP_POINTER_STATE_RELEASED|APP_POINTER_STATE_UP); | |
} | |
//} | |
} | |
} | |
int | |
APP__Handle_frame(void) { | |
int64_t time_now = APP_Time(); | |
int64_t frame_duration = time_now - APP__data.prev_time; | |
APP__data.prev_time = time_now; | |
int head = APP__data.frame_duration_samples_head; | |
head = (head + APP__MAX_FRAME_DURATION_SAMPLES - 1) % APP__MAX_FRAME_DURATION_SAMPLES; | |
APP__data.frame_duration_samples[head] = frame_duration; | |
APP__data.frame_duration_samples_head = head; | |
if (APP__data.frame_duration_samples_count < APP__MAX_FRAME_DURATION_SAMPLES) { | |
APP__data.frame_duration_samples_count += 1; | |
} | |
#if defined (APP_LINUX) | |
APP__linux_Process_events(); | |
#elif defined (APP_WINDOWS) | |
APP__win32_Process_events(); | |
#elif defined (APP_WINDOWS) | |
APP__wasm_Process_events(); | |
#endif | |
int result = -1; | |
if (APP__data.frame_callback) { | |
result = APP__data.frame_callback(); | |
} | |
APP__Clear_events(); | |
// If the focus has changed to not focus | |
// Puts all the keys down to up because will never notify the up event | |
if (!APP__data.focus) { | |
APP__Put_keys_up(); | |
} | |
#if defined (APP_LINUX) | |
APP__linux_Swap_buffers(); | |
#elif defined (APP_WINDOWS) | |
APP__win32_Swap_buffers(); | |
#endif | |
return result; | |
} | |
///////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// WINDOWS IMPLEMENTATION | |
// | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
#if defined(APP_WINDOWS) | |
// https://www.opengl.org/archives/resources/code/samples/win32_tutorial/ | |
// https://github.com/floooh/sokol/blob/master/sokol_app.h | |
#include <windowsx.h> // GET_X_LPARAM ... | |
typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC); | |
typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); | |
typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); | |
typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); | |
typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); | |
typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); | |
#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 | |
#define WGL_SUPPORT_OPENGL_ARB 0x2010 | |
#define WGL_DRAW_TO_WINDOW_ARB 0x2001 | |
#define WGL_PIXEL_TYPE_ARB 0x2013 | |
#define WGL_TYPE_RGBA_ARB 0x202b | |
#define WGL_ACCELERATION_ARB 0x2003 | |
#define WGL_NO_ACCELERATION_ARB 0x2025 | |
#define WGL_RED_BITS_ARB 0x2015 | |
#define WGL_GREEN_BITS_ARB 0x2017 | |
#define WGL_BLUE_BITS_ARB 0x2019 | |
#define WGL_ALPHA_BITS_ARB 0x201b | |
#define WGL_DEPTH_BITS_ARB 0x2022 | |
#define WGL_STENCIL_BITS_ARB 0x2023 | |
#define WGL_DOUBLE_BUFFER_ARB 0x2011 | |
#define WGL_SAMPLES_ARB 0x2042 | |
#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 | |
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 | |
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 | |
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 | |
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 | |
#define WGL_CONTEXT_FLAGS_ARB 0x2094 | |
#define ERROR_INVALID_VERSION_ARB 0x2095 | |
#define ERROR_INVALID_PROFILE_ARB 0x2096 | |
#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 | |
// generate GL function pointers | |
#define APP__GL_XMACRO(name, ret, args) PFN_ ## name name; | |
APP__GL_FUNCS | |
#undef APP__GL_XMACRO | |
static struct { | |
HWND hwnd; | |
HDC hdc; | |
HWND msg_hwnd; | |
HDC msg_hdc; | |
wchar_t window_title_wide[128]; | |
HGLRC gl_ctx; | |
HINSTANCE opengl32; | |
PFN_wglCreateContext wglCreateContext; | |
PFN_wglDeleteContext wglDeleteContext; | |
PFN_wglGetProcAddress wglGetProcAddress; | |
PFN_wglGetCurrentDC wglGetCurrentDC; | |
PFN_wglMakeCurrent wglMakeCurrent; | |
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; | |
// Time tracking | |
LARGE_INTEGER initial_time; | |
LARGE_INTEGER time_freq; | |
} APP__win32_data = {0}; | |
#define APP__MAX_KEYMAPPING 512 | |
static APP_KeyCode APP__key_mapping[APP__MAX_KEYMAPPING] = { | |
/* same as GLFW */ | |
[0x00B] = APP_KEY_0, | |
[0x002] = APP_KEY_1, | |
[0x003] = APP_KEY_2, | |
[0x004] = APP_KEY_3, | |
[0x005] = APP_KEY_4, | |
[0x006] = APP_KEY_5, | |
[0x007] = APP_KEY_6, | |
[0x008] = APP_KEY_7, | |
[0x009] = APP_KEY_8, | |
[0x00A] = APP_KEY_9, | |
[0x01E] = APP_KEY_A, | |
[0x030] = APP_KEY_B, | |
[0x02E] = APP_KEY_C, | |
[0x020] = APP_KEY_D, | |
[0x012] = APP_KEY_E, | |
[0x021] = APP_KEY_F, | |
[0x022] = APP_KEY_G, | |
[0x023] = APP_KEY_H, | |
[0x017] = APP_KEY_I, | |
[0x024] = APP_KEY_J, | |
[0x025] = APP_KEY_K, | |
[0x026] = APP_KEY_L, | |
[0x032] = APP_KEY_M, | |
[0x031] = APP_KEY_N, | |
[0x018] = APP_KEY_O, | |
[0x019] = APP_KEY_P, | |
[0x010] = APP_KEY_Q, | |
[0x013] = APP_KEY_R, | |
[0x01F] = APP_KEY_S, | |
[0x014] = APP_KEY_T, | |
[0x016] = APP_KEY_U, | |
[0x02F] = APP_KEY_V, | |
[0x011] = APP_KEY_W, | |
[0x02D] = APP_KEY_X, | |
[0x015] = APP_KEY_Y, | |
[0x02C] = APP_KEY_Z, | |
[0x028] = APP_KEY_APOSTROPHE, | |
[0x02B] = APP_KEY_BACKSLASH, | |
[0x033] = APP_KEY_COMMA, | |
[0x00D] = APP_KEY_EQUAL, | |
[0x029] = APP_KEY_GRAVE_ACCENT, | |
[0x01A] = APP_KEY_LEFT_BRACKET, | |
[0x00C] = APP_KEY_MINUS, | |
[0x034] = APP_KEY_PERIOD, | |
[0x01B] = APP_KEY_RIGHT_BRACKET, | |
[0x027] = APP_KEY_SEMICOLON, | |
[0x035] = APP_KEY_SLASH, | |
[0x056] = APP_KEY_WORLD_2, | |
[0x00E] = APP_KEY_BACKSPACE, | |
[0x153] = APP_KEY_DELETE, | |
[0x14F] = APP_KEY_END, | |
[0x01C] = APP_KEY_ENTER, | |
[0x001] = APP_KEY_ESCAPE, | |
[0x147] = APP_KEY_HOME, | |
[0x152] = APP_KEY_INSERT, | |
[0x15D] = APP_KEY_MENU, | |
[0x151] = APP_KEY_PAGE_DOWN, | |
[0x149] = APP_KEY_PAGE_UP, | |
[0x045] = APP_KEY_PAUSE, | |
[0x146] = APP_KEY_PAUSE, | |
[0x039] = APP_KEY_SPACE, | |
[0x00F] = APP_KEY_TAB, | |
[0x03A] = APP_KEY_CAPS_LOCK, | |
[0x145] = APP_KEY_NUM_LOCK, | |
[0x046] = APP_KEY_SCROLL_LOCK, | |
[0x03B] = APP_KEY_F1, | |
[0x03C] = APP_KEY_F2, | |
[0x03D] = APP_KEY_F3, | |
[0x03E] = APP_KEY_F4, | |
[0x03F] = APP_KEY_F5, | |
[0x040] = APP_KEY_F6, | |
[0x041] = APP_KEY_F7, | |
[0x042] = APP_KEY_F8, | |
[0x043] = APP_KEY_F9, | |
[0x044] = APP_KEY_F10, | |
[0x057] = APP_KEY_F11, | |
[0x058] = APP_KEY_F12, | |
[0x064] = APP_KEY_F13, | |
[0x065] = APP_KEY_F14, | |
[0x066] = APP_KEY_F15, | |
[0x067] = APP_KEY_F16, | |
[0x068] = APP_KEY_F17, | |
[0x069] = APP_KEY_F18, | |
[0x06A] = APP_KEY_F19, | |
[0x06B] = APP_KEY_F20, | |
[0x06C] = APP_KEY_F21, | |
[0x06D] = APP_KEY_F22, | |
[0x06E] = APP_KEY_F23, | |
[0x076] = APP_KEY_F24, | |
[0x038] = APP_KEY_LEFT_ALT, | |
[0x01D] = APP_KEY_LEFT_CONTROL, | |
[0x02A] = APP_KEY_LEFT_SHIFT, | |
[0x15B] = APP_KEY_LEFT_SUPER, | |
[0x137] = APP_KEY_PRINT_SCREEN, | |
[0x138] = APP_KEY_RIGHT_ALT, | |
[0x11D] = APP_KEY_RIGHT_CONTROL, | |
[0x036] = APP_KEY_RIGHT_SHIFT, | |
[0x15C] = APP_KEY_RIGHT_SUPER, | |
[0x150] = APP_KEY_DOWN, | |
[0x14B] = APP_KEY_LEFT, | |
[0x14D] = APP_KEY_RIGHT, | |
[0x148] = APP_KEY_UP, | |
[0x052] = APP_KEY_KP_0, | |
[0x04F] = APP_KEY_KP_1, | |
[0x050] = APP_KEY_KP_2, | |
[0x051] = APP_KEY_KP_3, | |
[0x04B] = APP_KEY_KP_4, | |
[0x04C] = APP_KEY_KP_5, | |
[0x04D] = APP_KEY_KP_6, | |
[0x047] = APP_KEY_KP_7, | |
[0x048] = APP_KEY_KP_8, | |
[0x049] = APP_KEY_KP_9, | |
[0x04E] = APP_KEY_KP_ADD, | |
[0x053] = APP_KEY_KP_DECIMAL, | |
[0x135] = APP_KEY_KP_DIVIDE, | |
[0x11C] = APP_KEY_KP_ENTER, | |
[0x037] = APP_KEY_KP_MULTIPLY, | |
[0x04A] = APP_KEY_KP_SUBTRACT, | |
}; | |
APP_INTERNAL LONG WINAPI | |
APP__Window_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { | |
static bool mouse_tracked = false; | |
switch (uMsg) { | |
case WM_CLOSE: | |
APP__data.quit_requested = true; | |
break; | |
case WM_SYSCOMMAND: | |
break; | |
case WM_ERASEBKGND: | |
return 1; | |
case WM_SIZE: { | |
APP__data.w_width = LOWORD(lParam); | |
APP__data.w_height = HIWORD(lParam); | |
APP__data.window_resized = true; | |
break; | |
} | |
case WM_SETCURSOR: | |
break; | |
case WM_LBUTTONDOWN: { | |
APP__Report_mouse_button_down(APP_MOUSE_BUTTON_LEFT); | |
break; | |
} | |
case WM_RBUTTONDOWN: { | |
APP__Report_mouse_button_down(APP_MOUSE_BUTTON_RIGHT); | |
break; | |
} | |
case WM_MBUTTONDOWN: { | |
APP__Report_mouse_button_down(APP_MOUSE_BUTTON_MIDDLE); | |
break; | |
} | |
case WM_LBUTTONUP: { | |
APP__Report_mouse_button_up(APP_MOUSE_BUTTON_LEFT); | |
break; | |
} | |
case WM_RBUTTONUP: { | |
APP__Report_mouse_button_up(APP_MOUSE_BUTTON_RIGHT); | |
break; | |
} | |
case WM_MBUTTONUP: { | |
APP__Report_mouse_button_up(APP_MOUSE_BUTTON_MIDDLE); | |
break; | |
} | |
case WM_MOUSEMOVE: { | |
APP__Report_mouse_move((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam)); | |
if (!mouse_tracked) { | |
TRACKMOUSEEVENT tme; | |
memset(&tme, 0, sizeof(tme)); | |
tme.cbSize = sizeof(tme); | |
tme.dwFlags = TME_LEAVE; | |
tme.hwndTrack = APP__win32_data.hwnd; | |
TrackMouseEvent(&tme); | |
} | |
break; | |
} | |
case WM_INPUT: | |
break; | |
case WM_MOUSELEAVE: { | |
mouse_tracked = false; | |
// Puts all the keys down to up because will never notify the up event | |
APP__Put_keys_up(); | |
break; | |
} | |
case WM_MOUSEWHEEL: | |
APP__data.mouse.wheel_y = (float)((SHORT)HIWORD(wParam)); | |
break; | |
case WM_MOUSEHWHEEL: | |
APP__data.mouse.wheel_x = (float)((SHORT)HIWORD(wParam)); | |
break; | |
case WM_CHAR: { | |
wchar_t input_char = (wchar_t)wParam; | |
char multibyte_buffer[32] = ""; | |
int multibyte_len = WideCharToMultiByte(CP_UTF8, 0, &input_char, 1, multibyte_buffer, 31, NULL, NULL); | |
if (!APP__Is_char_filtered(multibyte_buffer[0]) && | |
APP__data.text_input_bytes_count + multibyte_len < APP__TEXT_INPUT_BUFFER_MAX) { | |
for (int i = 0; i < multibyte_len; ++i) { | |
APP__data.text_input_buffer[APP__data.text_input_bytes_count+i] = multibyte_buffer[i]; | |
} | |
APP__data.text_input_bytes_count = APP__data.text_input_bytes_count + multibyte_len; | |
APP__data.text_input_buffer[APP__data.text_input_bytes_count] = '\0'; | |
} | |
break; | |
} | |
case WM_KEYDOWN: | |
case WM_SYSKEYDOWN: { | |
int key = (int)(HIWORD(lParam)&0x1FF); | |
if (key >= 0 && key < APP__MAX_KEYMAPPING) { | |
int key_id = APP__key_mapping[key]; | |
if (APP__data.keyboard[key_id] & APP__KEY_STATUS_PRESSED || | |
APP__data.keyboard[key_id] & APP__KEY_STATUS_DOWN) { | |
APP__data.keyboard[key_id] = APP__KEY_STATUS_DOWN; | |
} | |
else { | |
APP__data.keyboard[key_id] = APP__KEY_STATUS_PRESSED; | |
} | |
APP__data.keyboard[key_id] |= APP__KEY_STATUS_REPEAT; | |
} | |
break; | |
} | |
case WM_KEYUP: | |
case WM_SYSKEYUP: { | |
int key = (int)(HIWORD(lParam)&0x1FF); | |
if (key >= 0 && key < APP__MAX_KEYMAPPING) { | |
APP__data.keyboard[APP__key_mapping[key]] = APP__KEY_STATUS_RELEASED; | |
} | |
break; | |
} | |
case WM_KILLFOCUS: { | |
// Puts all the keys down to up because will never notify the up event | |
APP__Put_keys_up(); | |
break; | |
} | |
case WM_ENTERSIZEMOVE: | |
break; | |
case WM_EXITSIZEMOVE: | |
break; | |
case WM_TIMER: | |
// SwapBuffers(hdc); | |
break; | |
case WM_DROPFILES: | |
break; | |
default: | |
break; | |
} | |
return DefWindowProcW(hWnd, uMsg, wParam, lParam); | |
} | |
/* | |
* Extracted from sokol_app.h _sAPP_win32_uwp_utf8_to_wide | |
* | |
*/ | |
APP_INTERNAL bool | |
APP__String_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { | |
assert(src && dst && (dst_num_bytes > 1)); | |
memset(dst, 0, (size_t)dst_num_bytes); | |
const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); | |
const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); | |
if ((dst_needed > 0) && (dst_needed < dst_chars)) { | |
MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); | |
return true; | |
} | |
else { | |
/* input string doesn't fit into destination buffer */ | |
return false; | |
} | |
} | |
APP_INTERNAL void * | |
APP__Load_gl_function(const char *func_name) { | |
void* proc_addr = (void*)APP__win32_data.wglGetProcAddress(func_name); | |
if (proc_addr == NULL) { | |
proc_addr = (void*) GetProcAddress(APP__win32_data.opengl32, func_name); | |
} | |
if (proc_addr == NULL) { | |
printf("CANNOT LOAD %s\n", func_name); | |
} | |
return proc_addr; | |
} | |
APP_INTERNAL int | |
APP__win32_Init(const char *title, int width, int height) { | |
APP__data.default_width = width; | |
APP__data.default_height = height; | |
APP__data.w_width = width; | |
APP__data.w_height = height; | |
APP__String_to_wide( | |
title, | |
APP__win32_data.window_title_wide, | |
sizeof(APP__win32_data.window_title_wide)); | |
WNDCLASSW wndclassw; | |
memset(&wndclassw, 0, sizeof(wndclassw)); | |
wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; | |
wndclassw.lpfnWndProc = (WNDPROC) APP__Window_proc; | |
wndclassw.hInstance = GetModuleHandleW(NULL); | |
wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); | |
wndclassw.hIcon = LoadIcon(NULL, IDI_WINLOGO); | |
wndclassw.lpszClassName = L"APP"; | |
RegisterClassW(&wndclassw); | |
DWORD win_style; | |
const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; | |
RECT rect = { 0, 0, 0, 0 }; | |
win_style = WS_CLIPSIBLINGS | | |
WS_CLIPCHILDREN | | |
WS_CAPTION | | |
WS_SYSMENU | | |
WS_MINIMIZEBOX | | |
WS_MAXIMIZEBOX | | |
WS_SIZEBOX; | |
rect.right = width; | |
rect.bottom = height; | |
AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); | |
const int win_width = rect.right - rect.left; | |
const int win_height = rect.bottom - rect.top; | |
APP__win32_data.hwnd = CreateWindowExW( | |
win_ex_style, /* dwExStyle */ | |
L"APP", /* lpClassName */ | |
APP__win32_data.window_title_wide, /* lpWindowName */ | |
win_style, /* dwStyle */ | |
CW_USEDEFAULT, /* X */ | |
CW_USEDEFAULT, /* Y */ | |
win_width, /* nWidth */ | |
win_height, /* nHeight */ | |
NULL, /* hWndParent */ | |
NULL, /* hMenu */ | |
GetModuleHandle(NULL), /* hInstance */ | |
NULL); /* lParam */ | |
ShowWindow(APP__win32_data.hwnd, SW_SHOW); | |
APP__win32_data.hdc = GetDC(APP__win32_data.hwnd); | |
if (!APP__win32_data.hdc) { | |
APP_Destroy_window(); | |
return -1; | |
} | |
// SETUP GL | |
{ | |
APP__win32_data.opengl32 = LoadLibraryA("opengl32.dll"); | |
if (!APP__win32_data.opengl32) { | |
fprintf(stderr, "Failed to load opengl32.dll\n"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
APP__win32_data.wglCreateContext = (PFN_wglCreateContext)(void*)GetProcAddress(APP__win32_data.opengl32, "wglCreateContext"); | |
if (!APP__win32_data.wglCreateContext) { | |
fprintf(stderr, "Failed to get proc address wglCreateContext\n"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
APP__win32_data.wglDeleteContext = (PFN_wglDeleteContext)(void*)GetProcAddress(APP__win32_data.opengl32, "wglDeleteContext"); | |
if (!APP__win32_data.wglDeleteContext) { | |
fprintf(stderr, "Failed to get proc address wglDeleteContext\n"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
APP__win32_data.wglGetProcAddress = (PFN_wglGetProcAddress)(void*)GetProcAddress(APP__win32_data.opengl32, "wglGetProcAddress"); | |
if (!APP__win32_data.wglGetProcAddress) { | |
fprintf(stderr, "Failed to get proc address wglGetProcAddress\n"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
APP__win32_data.wglGetCurrentDC = (PFN_wglGetCurrentDC)(void*)GetProcAddress(APP__win32_data.opengl32, "wglGetCurrentDC"); | |
if (!APP__win32_data.wglGetCurrentDC) { | |
fprintf(stderr, "Failed to get proc address wglGetCurrentDC\n"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
APP__win32_data.wglMakeCurrent = (PFN_wglMakeCurrent)(void*)GetProcAddress(APP__win32_data.opengl32, "wglMakeCurrent"); | |
if (!APP__win32_data.wglMakeCurrent) { | |
fprintf(stderr, "Failed to get proc address wglMakeCurrent\n"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
PIXELFORMATDESCRIPTOR pfd; | |
/* there is no guarantee that the contents of the stack that become | |
the pfd are zeroed, therefore _make sure_ to clear these bits. */ | |
memset(&pfd, 0, sizeof(pfd)); | |
pfd.nSize = sizeof(pfd); | |
pfd.nVersion = 1; | |
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; | |
pfd.iPixelType = PFD_TYPE_RGBA; | |
pfd.cColorBits = 24; | |
pfd.cDepthBits = 24; | |
int pf = ChoosePixelFormat(APP__win32_data.hdc, &pfd); | |
if (pf == 0) { | |
fprintf(stderr, "ChoosePixelFormat() failed: Cannot find a suitable pixel format."); | |
APP_Destroy_window(); | |
return -1; | |
} | |
if (!SetPixelFormat(APP__win32_data.hdc, pf, &pfd)) { | |
fprintf(stderr, "SetPixelFormat() failed: Cannot set format specified."); | |
APP_Destroy_window(); | |
return -1; | |
} | |
DescribePixelFormat(APP__win32_data.hdc, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd); | |
APP__win32_data.gl_ctx = APP__win32_data.wglCreateContext(APP__win32_data.hdc); | |
if (!APP__win32_data.gl_ctx) { | |
const DWORD err = GetLastError(); | |
if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) { | |
fprintf(stderr, "WGL: Driver does not support OpenGL version 3.3\n"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) { | |
fprintf(stderr, "WGL: Driver does not support the requested OpenGL profile"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) { | |
fprintf(stderr, "WGL: The share context is not compatible with the requested context"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
else { | |
fprintf(stderr, "WGL: Failed to create OpenGL context"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
} | |
APP__win32_data.wglMakeCurrent(APP__win32_data.hdc, APP__win32_data.gl_ctx); | |
// What the fuck is this (Tano) | |
//if (_sapp.wgl.ext_swap_control) { | |
// /* FIXME: DwmIsCompositionEnabled() (see GLFW) */ | |
// _sapp.wgl.SwapIntervalEXT(_sapp.swap_interval); | |
//} | |
APP__win32_data.wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(void*) APP__win32_data.wglGetProcAddress("wglSwapIntervalEXT"); | |
if (!APP__win32_data.wglSwapIntervalEXT) { | |
fprintf(stderr, "Failed to get proc address wglSwapIntervalEXT\n"); | |
} | |
// BIND ALL GL FUNCTIONS | |
#define APP__GL_XMACRO(name, ret, args) name = (PFN_ ## name) APP__Load_gl_function(#name); | |
APP__GL_FUNCS | |
#undef APP__GL_XMACRO | |
} | |
{ // INIT APP TIME | |
QueryPerformanceFrequency(&APP__win32_data.time_freq); | |
QueryPerformanceCounter(&APP__win32_data.initial_time); | |
} | |
return 0; | |
} | |
APP_INTERNAL void | |
APP__win32_Set_fullscreen(bool enable) { | |
HMONITOR monitor = MonitorFromWindow(APP__win32_data.hwnd, MONITOR_DEFAULTTONEAREST); | |
MONITORINFO minfo; | |
memset(&minfo, 0, sizeof(minfo)); | |
minfo.cbSize = sizeof(MONITORINFO); | |
GetMonitorInfo(monitor, &minfo); | |
const RECT mr = minfo.rcMonitor; | |
const int monitor_w = mr.right - mr.left; | |
const int monitor_h = mr.bottom - mr.top; | |
const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; | |
DWORD win_style; | |
RECT rect = { 0, 0, 0, 0 }; | |
if (enable) { | |
win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; | |
rect.right = monitor_w; | |
rect.bottom = monitor_h; | |
} | |
else { | |
win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; | |
rect.right = APP__data.default_width; | |
rect.bottom = APP__data.default_height; | |
} | |
AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); | |
int win_width = rect.right - rect.left; | |
int win_height = rect.bottom - rect.top; | |
if (!enable) { | |
rect.left = (monitor_w - win_width) / 2; | |
rect.top = (monitor_h - win_height) / 2; | |
} | |
SetWindowLongPtr(APP__win32_data.hwnd, GWL_STYLE, win_style); | |
SetWindowPos(APP__win32_data.hwnd, HWND_TOP, mr.left + rect.left, mr.top + rect.top, win_width, win_height, SWP_SHOWWINDOW | SWP_FRAMECHANGED); | |
// Seems that when we call SetWindowPos it calls internally to our Window_proc with the WM_SIZE | |
// event, but Set_fullscreen is called from the frame, and Clear_events clears all events after | |
// the frame callback, so the application wouldn't notice the resize. | |
// | |
// To fix this we set fullscreen_changed to true and clear events will handle this case | |
// correctly. | |
// | |
// Tano 15-08-2021 | |
APP__data.fullscreen_changed = true; | |
} | |
APP_INTERNAL void | |
APP__win32_Show_mouse(bool show) { | |
ShowCursor((BOOL)show); | |
} | |
APP_INTERNAL void | |
APP__win32_Set_window_title(const char *title) { | |
APP__String_to_wide( | |
title, | |
APP__win32_data.window_title_wide, | |
sizeof(APP__win32_data.window_title_wide) | |
); | |
SetWindowTextW(APP__win32_data.hwnd, APP__win32_data.window_title_wide); | |
} | |
APP_INTERNAL void | |
APP__win32_Shutdown() { | |
{ // Cleanup GL | |
APP__win32_data.wglDeleteContext(APP__win32_data.gl_ctx); | |
APP__win32_data.gl_ctx = 0; | |
FreeLibrary(APP__win32_data.opengl32); | |
APP__win32_data.opengl32 = 0; | |
} | |
DestroyWindow(APP__win32_data.hwnd); | |
} | |
APP_INTERNAL void | |
APP__win32_Process_events() { | |
MSG msg; | |
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { | |
if (WM_QUIT == msg.message) { | |
APP__data.quit_requested = true; | |
continue; | |
} | |
else { | |
TranslateMessage(&msg); | |
DispatchMessage(&msg); | |
} | |
} | |
} | |
APP_INTERNAL void | |
APP__win32_Swap_buffers(void) { | |
/* get rendered buffer to the screen */ | |
SwapBuffers(APP__win32_data.hdc); | |
} | |
APP_INTERNAL void | |
APP__win32_Set_swap_interval(unsigned int interval) { | |
if (APP__win32_data.wglSwapIntervalEXT) { | |
APP__win32_data.wglSwapIntervalEXT(interval); | |
} | |
} | |
APP_INTERNAL int64_t | |
APP__win32_Time(void) { | |
LARGE_INTEGER qpc_t; | |
QueryPerformanceCounter(&qpc_t); | |
int64_t counter_diff = qpc_t.QuadPart - APP__win32_data.initial_time.QuadPart; | |
int64_t freq = APP__win32_data.time_freq.QuadPart; | |
// This is to preserve the precission (from sokol time) | |
int64_t q = counter_diff / freq; | |
int64_t r = counter_diff % freq; | |
const int64_t SECOND = 1000*1000*1000; | |
return q * SECOND + (r * SECOND) / freq; | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// SOUND API | |
// | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
#ifndef WIN32_LEAN_AND_MEAN | |
#define WIN32_LEAN_AND_MEAN | |
#endif | |
#ifndef NOMINMAX | |
#define NOMINMAX | |
#endif | |
#include <synchapi.h> | |
#ifndef CINTERFACE | |
#define CINTERFACE | |
#endif | |
#ifndef COBJMACROS | |
#define COBJMACROS | |
#endif | |
#ifndef CONST_VTABLE | |
#define CONST_VTABLE | |
#endif | |
#include <mmdeviceapi.h> | |
#include <audioclient.h> | |
static const IID splayer_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } }; | |
static const IID splayer_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, { 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } }; | |
static const CLSID splayer_CLSID_IMMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, { 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } }; | |
static const IID splayer_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483,{ 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } }; | |
static const IID splayer_IID_Devinterface_Audio_Render = { 0xe6327cad, 0xdcec, 0x4949, {0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2 } }; | |
static const IID splayer_IID_IActivateAudioInterface_Completion_Handler = { 0x94ea2b94, 0xe9cc, 0x49e0, {0xc0, 0xff, 0xee, 0x64, 0xca, 0x8f, 0x5b, 0x90} }; | |
#define AUDIO_WIN32COM_ID(x) (&x) | |
/* fix for Visual Studio 2015 SDKs */ | |
static struct { | |
IMMDeviceEnumerator* device_enumerator; | |
IMMDevice* device; | |
IAudioClient* audio_client; | |
IAudioRenderClient* render_client; | |
int si16_bytes_per_frame; | |
struct { | |
HANDLE thread_handle; | |
HANDLE buffer_end_event; | |
volatile bool stop; | |
UINT32 dst_buffer_frames; | |
int src_buffer_frames; | |
int src_buffer_byte_size; | |
int src_buffer_pos; | |
float* src_buffer; | |
} thread; | |
} APP__sound_player_backend = {0}; | |
APP_INTERNAL void | |
APP__Cleanup_sound_player(void) { | |
if (APP__sound_player_backend.thread.src_buffer) { | |
free(APP__sound_player_backend.thread.src_buffer); | |
APP__sound_player_backend.thread.src_buffer = NULL; | |
} | |
if (APP__sound_player_backend.render_client) { | |
IAudioRenderClient_Release(APP__sound_player_backend.render_client); | |
APP__sound_player_backend.render_client = 0; | |
} | |
if (APP__sound_player_backend.audio_client) { | |
IAudioClient_Release(APP__sound_player_backend.audio_client); | |
APP__sound_player_backend.audio_client = 0; | |
} | |
if (APP__sound_player_backend.device) { | |
IMMDevice_Release(APP__sound_player_backend.device); | |
APP__sound_player_backend.device = 0; | |
} | |
if (APP__sound_player_backend.device_enumerator) { | |
IMMDeviceEnumerator_Release(APP__sound_player_backend.device_enumerator); | |
APP__sound_player_backend.device_enumerator = 0; | |
} | |
if (0 != APP__sound_player_backend.thread.buffer_end_event) { | |
CloseHandle(APP__sound_player_backend.thread.buffer_end_event); | |
APP__sound_player_backend.thread.buffer_end_event = 0; | |
} | |
} | |
APP_INTERNAL void | |
APP__Sound_player_submit_buffer(int num_frames) { | |
BYTE* wasapi_buffer = 0; | |
if (FAILED(IAudioRenderClient_GetBuffer(APP__sound_player_backend.render_client, num_frames, &wasapi_buffer))) { | |
return; | |
} | |
/* convert float samples to int16_t, refill float buffer if needed */ | |
const int num_samples = num_frames * APP__data.sound_player_info.num_channels; | |
int16_t* dst = (int16_t*) wasapi_buffer; | |
int buffer_pos = APP__sound_player_backend.thread.src_buffer_pos; | |
const int buffer_float_size = APP__sound_player_backend.thread.src_buffer_byte_size / (int)sizeof(float); | |
float* src = APP__sound_player_backend.thread.src_buffer; | |
for (int i = 0; i < num_samples; i++) { | |
if (0 == buffer_pos) { | |
APP__data.sound_player_info.stream_cb( | |
APP__data.sound_player_info.user_data, | |
APP__sound_player_backend.thread.src_buffer, | |
APP__data.sound_player_info.buffer_frames, | |
APP__data.sound_player_info.num_channels | |
); | |
} | |
int value = (src[buffer_pos] * 0x7FFF); | |
value = value < -32768 ? -32768 : (value > 32767 ? 32767 : value); // Clamp the value to prevent overflow | |
dst[i] = (int16_t) (value); | |
buffer_pos += 1; | |
if (buffer_pos == buffer_float_size) { | |
buffer_pos = 0; | |
} | |
} | |
APP__sound_player_backend.thread.src_buffer_pos = buffer_pos; | |
IAudioRenderClient_ReleaseBuffer(APP__sound_player_backend.render_client, num_frames, 0); | |
} | |
APP_INTERNAL DWORD WINAPI | |
APP__Sound_player_thread(LPVOID param) { | |
(void)param; | |
APP__Sound_player_submit_buffer(APP__sound_player_backend.thread.src_buffer_frames); | |
IAudioClient_Start(APP__sound_player_backend.audio_client); | |
while (!APP__sound_player_backend.thread.stop) { | |
WaitForSingleObject(APP__sound_player_backend.thread.buffer_end_event, INFINITE); | |
UINT32 padding = 0; | |
if (FAILED(IAudioClient_GetCurrentPadding(APP__sound_player_backend.audio_client, &padding))) { | |
continue; | |
} | |
if (APP__sound_player_backend.thread.dst_buffer_frames < padding) { | |
fprintf(stderr, "Error, on sound player thread, buffer frames less than padding"); | |
return 1; | |
} | |
int num_frames = (int)APP__sound_player_backend.thread.dst_buffer_frames - (int)padding; | |
if (num_frames > 0) { | |
APP__Sound_player_submit_buffer(num_frames); | |
} | |
} | |
return 0; | |
} | |
APP_PUBLIC int | |
APP_Launch_sound_player(APP_SoundPlayerDesc *desc) { | |
REFERENCE_TIME dur; | |
/* CoInitializeEx could have been called elsewhere already, in which | |
case the function returns with S_FALSE (thus it does not make much | |
sense to check the result) | |
*/ | |
CoInitializeEx(0, COINIT_MULTITHREADED); | |
APP__sound_player_backend.thread.buffer_end_event = CreateEvent(0, FALSE, FALSE, 0); | |
if (0 == APP__sound_player_backend.thread.buffer_end_event) { | |
fprintf(stderr, "failed to create buffer_end_event.\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
if (FAILED(CoCreateInstance( | |
&splayer_CLSID_IMMDeviceEnumerator, | |
0, | |
CLSCTX_ALL, | |
&splayer_IID_IMMDeviceEnumerator, | |
(void**)&APP__sound_player_backend.device_enumerator)) | |
) { | |
fprintf(stderr, "failed to create device enumerator.\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
if (FAILED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(APP__sound_player_backend.device_enumerator, | |
eRender, eConsole, | |
&APP__sound_player_backend.device))) { | |
fprintf(stderr, "GetDefaultAudioEndPoint failed.\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
if (FAILED(IMMDevice_Activate(APP__sound_player_backend.device, &splayer_IID_IAudioClient, CLSCTX_ALL, 0, | |
(void**)&APP__sound_player_backend.audio_client))) | |
{ | |
fprintf(stderr, "device activate failed\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
WAVEFORMATEX fmt; | |
memset(&fmt, 0, sizeof(fmt)); | |
fmt.nChannels = (WORD)desc->num_channels; | |
fmt.nSamplesPerSec = (DWORD)desc->sample_rate; | |
fmt.wFormatTag = WAVE_FORMAT_PCM; | |
fmt.wBitsPerSample = 16; | |
fmt.nBlockAlign = (fmt.nChannels * fmt.wBitsPerSample) / 8; | |
fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; | |
dur = (REFERENCE_TIME) | |
(((double)desc->buffer_frames) / (((double)desc->sample_rate) * (1.0/10000000.0))); | |
if (FAILED(IAudioClient_Initialize(APP__sound_player_backend.audio_client, | |
AUDCLNT_SHAREMODE_SHARED, | |
AUDCLNT_STREAMFLAGS_EVENTCALLBACK|AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, | |
dur, 0, &fmt, 0))) | |
{ | |
fprintf(stderr, "audio client initialize failed.\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
if (FAILED(IAudioClient_GetBufferSize(APP__sound_player_backend.audio_client, &APP__sound_player_backend.thread.dst_buffer_frames))) { | |
fprintf(stderr, "audio client get buffer size failed.\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
if (FAILED(IAudioClient_GetService(APP__sound_player_backend.audio_client, &splayer_IID_IAudioRenderClient, | |
(void**)&APP__sound_player_backend.render_client))) { | |
fprintf(stderr, "audio client GetService failed.\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
if (FAILED(IAudioClient_SetEventHandle(APP__sound_player_backend.audio_client, | |
APP__sound_player_backend.thread.buffer_end_event))) { | |
fprintf(stderr, "audio client SetEventHandle failed.\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
APP__sound_player_backend.si16_bytes_per_frame = desc->num_channels * (int)sizeof(int16_t); | |
size_t bytes_per_frame = desc->num_channels * (int)sizeof(float); | |
APP__sound_player_backend.thread.src_buffer_frames = desc->buffer_frames; | |
APP__sound_player_backend.thread.src_buffer_byte_size = APP__sound_player_backend.thread.src_buffer_frames * bytes_per_frame; | |
/* allocate an intermediate buffer for sample format conversion */ | |
// TODO(Tano) Check this?? | |
APP__sound_player_backend.thread.src_buffer = (float *)malloc(sizeof(float) * desc->num_channels*desc->buffer_frames); | |
/* create streaming thread */ | |
APP__sound_player_backend.thread.thread_handle = CreateThread(NULL, 0, APP__Sound_player_thread, 0, 0, 0); | |
if (0 == APP__sound_player_backend.thread.thread_handle) { | |
fprintf(stderr, "audio wasapi: CreateThread failed\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
APP__data.sound_player_info.sample_rate = desc->sample_rate; | |
APP__data.sound_player_info.buffer_frames = desc->buffer_frames; | |
APP__data.sound_player_info.user_data = desc->user_data; | |
APP__data.sound_player_info.stream_cb = desc->stream_cb; | |
APP__data.sound_player_info.num_channels = desc->num_channels; | |
return 0; | |
} | |
APP_PUBLIC void | |
APP_Terminate_sound_player(void) { | |
if (APP__sound_player_backend.thread.thread_handle) { | |
APP__sound_player_backend.thread.stop = true; | |
SetEvent(APP__sound_player_backend.thread.buffer_end_event); | |
WaitForSingleObject(APP__sound_player_backend.thread.thread_handle, INFINITE); | |
CloseHandle(APP__sound_player_backend.thread.thread_handle); | |
APP__sound_player_backend.thread.thread_handle = 0; | |
} | |
if (APP__sound_player_backend.audio_client) { | |
IAudioClient_Stop(APP__sound_player_backend.audio_client); | |
} | |
APP__Cleanup_sound_player(); | |
CoUninitialize(); | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// THREAD API | |
// | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
void | |
Mutex_init(Mutex *mutex) { | |
*mutex = CreateMutex( | |
NULL, // default security attributes. | |
FALSE, // initially not owned | |
NULL); // unnamed mutex | |
if (*mutex == NULL) { | |
abort(); | |
} | |
} | |
void | |
Mutex_deinit(Mutex *mutex) { | |
CloseHandle(*mutex); | |
} | |
void | |
Mutex_lock(Mutex *mutex) { | |
DWORD dwWaitResult = dwWaitResult = WaitForSingleObject( | |
*mutex, // handle to mutex | |
INFINITE); // no time-out interval | |
if (WAIT_OBJECT_0 != dwWaitResult) { | |
abort(); | |
} | |
} | |
void | |
Mutex_unlock(Mutex *mutex) { | |
if (!ReleaseMutex(*mutex)) { | |
abort(); | |
} | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// FILESYSTEM API | |
// | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
// Documented above | |
#if !defined(BUNDLED_TREE) | |
uint64_t | |
Get_file_modify_time(const char *filename) { | |
HANDLE file_handle = CreateFileA(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | |
if (file_handle == INVALID_HANDLE_VALUE) { | |
return 0; | |
} | |
FILETIME time; | |
uint64_t result = 0; | |
if (GetFileTime(file_handle, NULL, NULL, &time)) { | |
result = (uint64_t)time.dwLowDateTime; | |
result |= (uint64_t)time.dwHighDateTime << 32; | |
} | |
CloseHandle(file_handle); | |
return result; | |
} | |
#endif | |
///////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// LINUX IMPLEMENTATION |||| | |
// VVVV | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
#elif defined(APP_LINUX) | |
#include <EGL/egl.h> | |
#include <EGL/eglplatform.h> | |
#include <time.h> | |
#include <X11/keysym.h> | |
#include <X11/Xcursor/Xcursor.h> | |
static struct { | |
Window root; | |
Window win; | |
Display *xdisplay; | |
XSetWindowAttributes swa; | |
Atom WM_PROTOCOLS; | |
Atom WM_DELETE_WINDOW; | |
Atom NET_WM_STATE; | |
Atom NET_WM_STATE_FULLSCREEN; | |
// Text input | |
XIM xim; | |
XIC xic; | |
Cursor hidden_cursor; | |
// EGL-related objects | |
EGLDisplay egl_display; | |
EGLConfig egl_conf; | |
EGLContext egl_context; | |
EGLSurface egl_surface; | |
// APP TIME | |
struct timespec initial_time; | |
} APP__x11_data = {0}; | |
APP_INTERNAL int | |
APP__linux_Init(const char *title, int width, int height) { | |
APP__data.default_width = width; | |
APP__data.default_height = height; | |
APP__data.w_width = width; | |
APP__data.w_height = height; | |
/* open standard display (primary screen) */ | |
APP__x11_data.xdisplay = XOpenDisplay(NULL); | |
if (APP__x11_data.xdisplay == NULL) { | |
printf("Error opening X display\n"); | |
return -1; | |
} | |
// get the root window (usually the whole screen) | |
APP__x11_data.root = DefaultRootWindow(APP__x11_data.xdisplay); | |
// list all events this window accepts | |
APP__x11_data.swa.event_mask = | |
StructureNotifyMask | | |
ExposureMask | | |
PointerMotionMask | | |
KeyPressMask | | |
KeyReleaseMask | | |
ButtonPressMask | | |
FocusChangeMask | | |
ButtonReleaseMask; | |
// Xlib's window creation | |
APP__x11_data.win = XCreateWindow ( | |
APP__x11_data.xdisplay, APP__x11_data.root, 0, 0, width, height, 0, | |
CopyFromParent, InputOutput, CopyFromParent, CWEventMask, | |
&APP__x11_data.swa); | |
XMapWindow(APP__x11_data.xdisplay, APP__x11_data.win); // make window visible | |
XStoreName(APP__x11_data.xdisplay, APP__x11_data.win, title); | |
// To handle windows close | |
APP__x11_data.WM_PROTOCOLS = XInternAtom(APP__x11_data.xdisplay, "WM_PROTOCOLS", False); | |
APP__x11_data.WM_DELETE_WINDOW = XInternAtom(APP__x11_data.xdisplay, "WM_DELETE_WINDOW", False); | |
APP__x11_data.NET_WM_STATE = XInternAtom(APP__x11_data.xdisplay, "_NET_WM_STATE", False); | |
APP__x11_data.NET_WM_STATE_FULLSCREEN = XInternAtom(APP__x11_data.xdisplay, "_NET_WM_STATE_FULLSCREEN", False); | |
Atom protocols[] = { | |
APP__x11_data.WM_DELETE_WINDOW | |
}; | |
XSetWMProtocols(APP__x11_data.xdisplay, APP__x11_data.win, protocols, 1); | |
{ // Prepare for keyboard text input | |
APP__x11_data.xim = XOpenIM(APP__x11_data.xdisplay, 0, 0, 0); | |
APP__x11_data.xic = XCreateIC(APP__x11_data.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL); | |
} | |
{ // Create a invisible cursor | |
const int w = 16; | |
const int h = 16; | |
XcursorImage* img = XcursorImageCreate(w, h); | |
if (img == NULL) { | |
APP_Destroy_window(); | |
return -1; | |
} | |
img->xhot = 0; | |
img->yhot = 0; | |
for (int i = 0; i < w*h; i+=1) img->pixels[i] = 0; // Set all to 0 | |
APP__x11_data.hidden_cursor = XcursorImageLoadCursor(APP__x11_data.xdisplay, img); | |
XcursorImageDestroy(img); | |
} | |
{ // Setup OpenGL | |
EGLint attr[] = { | |
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, | |
EGL_RED_SIZE, 8, | |
EGL_GREEN_SIZE, 8, | |
EGL_BLUE_SIZE, 8, | |
EGL_DEPTH_SIZE, 24, | |
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, /* If one needs GLES2 */ | |
EGL_NONE | |
}; | |
EGLint num_config; | |
EGLint major, minor; | |
EGLint ctxattr[] = { | |
EGL_CONTEXT_CLIENT_VERSION, 2, | |
EGL_NONE | |
}; | |
APP__x11_data.egl_display = eglGetDisplay((EGLNativeDisplayType)APP__x11_data.xdisplay); | |
if (APP__x11_data.egl_display == EGL_NO_DISPLAY) { | |
fprintf(stderr, "Error getting EGL display\n"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
if (eglInitialize(APP__x11_data.egl_display, &major, &minor) != EGL_TRUE) { | |
fprintf(stderr, "Error initializing EGL\n"); | |
APP_Destroy_window(); | |
return -1; | |
} | |
// printf("EGL major: %d, minor %d\n", major, minor); | |
/* create EGL rendering context */ | |
if (!eglChooseConfig(APP__x11_data.egl_display, attr, &APP__x11_data.egl_conf, 1, &num_config )) { | |
fprintf(stderr, "Failed to choose config (eglError: %x)\n", eglGetError()); | |
APP_Destroy_window(); | |
return -1; | |
} | |
if (num_config != 1) { | |
return -1; | |
} | |
APP__x11_data.egl_surface = eglCreateWindowSurface( | |
APP__x11_data.egl_display, | |
APP__x11_data.egl_conf, | |
APP__x11_data.win, | |
NULL | |
); | |
if (APP__x11_data.egl_surface == EGL_NO_SURFACE) { | |
fprintf(stderr, "CreateWindowSurface, EGL eglError: %d\n", eglGetError()); | |
APP_Destroy_window(); | |
return -1; | |
} | |
APP__x11_data.egl_context = eglCreateContext( | |
APP__x11_data.egl_display, | |
APP__x11_data.egl_conf, | |
EGL_NO_CONTEXT, | |
ctxattr | |
); | |
if (APP__x11_data.egl_context == EGL_NO_CONTEXT) { | |
fprintf(stderr, "CreateContext, EGL eglError: %d\n", eglGetError() ); | |
APP_Destroy_window(); | |
return -1; | |
} | |
// | |
// WARNING LEAK!!!! | |
// This generates a leak, no idea why.... | |
// | |
if (eglMakeCurrent( | |
APP__x11_data.egl_display, | |
APP__x11_data.egl_surface, | |
APP__x11_data.egl_surface, | |
APP__x11_data.egl_context | |
) != EGL_TRUE) { | |
fprintf(stderr, "MakeCurrent, EGL eglError: %d\n", eglGetError() ); | |
APP_Destroy_window(); | |
return -1; | |
} | |
} | |
APP_Set_swap_interval(1); | |
return 0; | |
} | |
APP_INTERNAL void | |
APP__X11_send_event(Atom type, int a, int b, int c, int d, int e) { | |
XEvent event = {0}; | |
event.type = ClientMessage; | |
event.xclient.window = APP__x11_data.win; | |
event.xclient.format = 32; | |
event.xclient.message_type = type; | |
event.xclient.data.l[0] = a; | |
event.xclient.data.l[1] = b; | |
event.xclient.data.l[2] = c; | |
event.xclient.data.l[3] = d; | |
event.xclient.data.l[4] = e; | |
XSendEvent(APP__x11_data.xdisplay, APP__x11_data.root, | |
False, | |
SubstructureNotifyMask | SubstructureRedirectMask, | |
&event); | |
} | |
APP_INTERNAL void | |
APP__linux_Set_fullscreen(bool enable) { | |
/* NOTE: this function must be called after XMapWindow (which happens in _sAPP_x11_show_window()) */ | |
if (APP__x11_data.NET_WM_STATE && APP__x11_data.NET_WM_STATE_FULLSCREEN) { | |
if (enable) { | |
const int _NET_WM_STATE_ADD = 1; | |
APP__X11_send_event(APP__x11_data.NET_WM_STATE, | |
_NET_WM_STATE_ADD, | |
APP__x11_data.NET_WM_STATE_FULLSCREEN, | |
0, 1, 0); | |
} | |
else { | |
const int _NET_WM_STATE_REMOVE = 0; | |
APP__X11_send_event(APP__x11_data.NET_WM_STATE, | |
_NET_WM_STATE_REMOVE, | |
APP__x11_data.NET_WM_STATE_FULLSCREEN, | |
0, 1, 0); | |
} | |
} | |
XFlush(APP__x11_data.xdisplay); | |
} | |
APP_INTERNAL void | |
APP__linux_Show_mouse(bool show) { | |
if (show) { | |
XUndefineCursor(APP__x11_data.xdisplay, APP__x11_data.win); | |
} | |
else { | |
XDefineCursor(APP__x11_data.xdisplay, APP__x11_data.win, APP__x11_data.hidden_cursor); | |
} | |
} | |
APP_INTERNAL void | |
APP__linux_Set_window_title(const char *title) { | |
XStoreName(APP__x11_data.xdisplay, APP__x11_data.win, title); | |
XFlush(APP__x11_data.xdisplay); | |
} | |
APP_INTERNAL void | |
APP__linux_Shutdown(void) { | |
{ | |
// I readed somewhere that is important that all gl commands must finalize before clean | |
// everything. | |
glFinish(); | |
// This unbinds the context, must be done before destroy the context. | |
if (EGL_TRUE != eglMakeCurrent( | |
APP__x11_data.egl_display, | |
EGL_NO_SURFACE, | |
EGL_NO_SURFACE, | |
EGL_NO_CONTEXT)) { | |
fprintf(stderr, "Error, cannot unbind context and surfaces.\n"); | |
} | |
// Destroys the surface and the context | |
if (eglDestroySurface(APP__x11_data.egl_display, APP__x11_data.egl_surface) != EGL_TRUE) { | |
fprintf(stderr, "Error, cannot destroy egl surface.\n"); | |
} | |
if (eglDestroyContext(APP__x11_data.egl_display, APP__x11_data.egl_context) != EGL_TRUE) { | |
fprintf(stderr, "Error, cannot destroy egl context.\n"); | |
} | |
// Destroys the egl_display | |
if (eglTerminate(APP__x11_data.egl_display) != EGL_TRUE) { | |
fprintf(stderr, "Error, cannot terminate egl display connection.\n"); | |
} | |
} | |
XDestroyIC(APP__x11_data.xic); | |
XCloseIM(APP__x11_data.xim); | |
XUnmapWindow(APP__x11_data.xdisplay, APP__x11_data.win); | |
XDestroyWindow(APP__x11_data.xdisplay, APP__x11_data.win); | |
XCloseDisplay(APP__x11_data.xdisplay); | |
} | |
APP_INTERNAL void | |
APP__linux_Set_swap_interval(unsigned int interval) { | |
eglSwapInterval(APP__x11_data.egl_display, interval); | |
} | |
APP_INTERNAL void | |
APP__linux_Swap_buffers() { | |
/* get rendered buffer to the screen */ | |
eglSwapBuffers (APP__x11_data.egl_display, APP__x11_data.egl_surface); | |
} | |
// Based on sokol_app.h | |
APP_INTERNAL APP_KeyCode | |
APP__Translate_key(int scancode) { | |
int dummy; | |
KeySym* keysyms = XGetKeyboardMapping(APP__x11_data.xdisplay, scancode, 1, &dummy); | |
if (keysyms == NULL) { | |
return APP_KEY_INVALID; | |
} | |
KeySym keysym = keysyms[0]; | |
XFree(keysyms); | |
switch (keysym) { | |
case XK_Escape: return APP_KEY_ESCAPE; | |
case XK_Tab: return APP_KEY_TAB; | |
case XK_Shift_L: return APP_KEY_LEFT_SHIFT; | |
case XK_Shift_R: return APP_KEY_RIGHT_SHIFT; | |
case XK_Control_L: return APP_KEY_LEFT_CONTROL; | |
case XK_Control_R: return APP_KEY_RIGHT_CONTROL; | |
case XK_Meta_L: | |
case XK_Alt_L: return APP_KEY_LEFT_ALT; | |
case XK_Mode_switch: /* Mapped to Alt_R on many keyboards */ | |
case XK_ISO_Level3_Shift: /* AltGr on at least some machines */ | |
case XK_Meta_R: | |
case XK_Alt_R: return APP_KEY_RIGHT_ALT; | |
case XK_Super_L: return APP_KEY_LEFT_SUPER; | |
case XK_Super_R: return APP_KEY_RIGHT_SUPER; | |
case XK_Menu: return APP_KEY_MENU; | |
case XK_Num_Lock: return APP_KEY_NUM_LOCK; | |
case XK_Caps_Lock: return APP_KEY_CAPS_LOCK; | |
case XK_Print: return APP_KEY_PRINT_SCREEN; | |
case XK_Scroll_Lock: return APP_KEY_SCROLL_LOCK; | |
case XK_Pause: return APP_KEY_PAUSE; | |
case XK_Delete: return APP_KEY_DELETE; | |
case XK_BackSpace: return APP_KEY_BACKSPACE; | |
case XK_Return: return APP_KEY_ENTER; | |
case XK_Home: return APP_KEY_HOME; | |
case XK_End: return APP_KEY_END; | |
case XK_Page_Up: return APP_KEY_PAGE_UP; | |
case XK_Page_Down: return APP_KEY_PAGE_DOWN; | |
case XK_Insert: return APP_KEY_INSERT; | |
case XK_Left: return APP_KEY_LEFT; | |
case XK_Right: return APP_KEY_RIGHT; | |
case XK_Down: return APP_KEY_DOWN; | |
case XK_Up: return APP_KEY_UP; | |
case XK_F1: return APP_KEY_F1; | |
case XK_F2: return APP_KEY_F2; | |
case XK_F3: return APP_KEY_F3; | |
case XK_F4: return APP_KEY_F4; | |
case XK_F5: return APP_KEY_F5; | |
case XK_F6: return APP_KEY_F6; | |
case XK_F7: return APP_KEY_F7; | |
case XK_F8: return APP_KEY_F8; | |
case XK_F9: return APP_KEY_F9; | |
case XK_F10: return APP_KEY_F10; | |
case XK_F11: return APP_KEY_F11; | |
case XK_F12: return APP_KEY_F12; | |
case XK_F13: return APP_KEY_F13; | |
case XK_F14: return APP_KEY_F14; | |
case XK_F15: return APP_KEY_F15; | |
case XK_F16: return APP_KEY_F16; | |
case XK_F17: return APP_KEY_F17; | |
case XK_F18: return APP_KEY_F18; | |
case XK_F19: return APP_KEY_F19; | |
case XK_F20: return APP_KEY_F20; | |
case XK_F21: return APP_KEY_F21; | |
case XK_F22: return APP_KEY_F22; | |
case XK_F23: return APP_KEY_F23; | |
case XK_F24: return APP_KEY_F24; | |
case XK_F25: return APP_KEY_F25; | |
case XK_KP_Divide: return APP_KEY_KP_DIVIDE; | |
case XK_KP_Multiply: return APP_KEY_KP_MULTIPLY; | |
case XK_KP_Subtract: return APP_KEY_KP_SUBTRACT; | |
case XK_KP_Add: return APP_KEY_KP_ADD; | |
case XK_KP_Insert: return APP_KEY_KP_0; | |
case XK_KP_End: return APP_KEY_KP_1; | |
case XK_KP_Down: return APP_KEY_KP_2; | |
case XK_KP_Page_Down: return APP_KEY_KP_3; | |
case XK_KP_Left: return APP_KEY_KP_4; | |
case XK_KP_Begin: return APP_KEY_KP_5; | |
case XK_KP_Right: return APP_KEY_KP_6; | |
case XK_KP_Home: return APP_KEY_KP_7; | |
case XK_KP_Up: return APP_KEY_KP_8; | |
case XK_KP_Page_Up: return APP_KEY_KP_9; | |
case XK_KP_Delete: return APP_KEY_KP_DECIMAL; | |
case XK_KP_Equal: return APP_KEY_KP_EQUAL; | |
case XK_KP_Enter: return APP_KEY_KP_ENTER; | |
case XK_a: return APP_KEY_A; | |
case XK_b: return APP_KEY_B; | |
case XK_c: return APP_KEY_C; | |
case XK_d: return APP_KEY_D; | |
case XK_e: return APP_KEY_E; | |
case XK_f: return APP_KEY_F; | |
case XK_g: return APP_KEY_G; | |
case XK_h: return APP_KEY_H; | |
case XK_i: return APP_KEY_I; | |
case XK_j: return APP_KEY_J; | |
case XK_k: return APP_KEY_K; | |
case XK_l: return APP_KEY_L; | |
case XK_m: return APP_KEY_M; | |
case XK_n: return APP_KEY_N; | |
case XK_o: return APP_KEY_O; | |
case XK_p: return APP_KEY_P; | |
case XK_q: return APP_KEY_Q; | |
case XK_r: return APP_KEY_R; | |
case XK_s: return APP_KEY_S; | |
case XK_t: return APP_KEY_T; | |
case XK_u: return APP_KEY_U; | |
case XK_v: return APP_KEY_V; | |
case XK_w: return APP_KEY_W; | |
case XK_x: return APP_KEY_X; | |
case XK_y: return APP_KEY_Y; | |
case XK_z: return APP_KEY_Z; | |
case XK_1: return APP_KEY_1; | |
case XK_2: return APP_KEY_2; | |
case XK_3: return APP_KEY_3; | |
case XK_4: return APP_KEY_4; | |
case XK_5: return APP_KEY_5; | |
case XK_6: return APP_KEY_6; | |
case XK_7: return APP_KEY_7; | |
case XK_8: return APP_KEY_8; | |
case XK_9: return APP_KEY_9; | |
case XK_0: return APP_KEY_0; | |
case XK_space: return APP_KEY_SPACE; | |
case XK_minus: return APP_KEY_MINUS; | |
case XK_equal: return APP_KEY_EQUAL; | |
case XK_bracketleft: return APP_KEY_LEFT_BRACKET; | |
case XK_bracketright: return APP_KEY_RIGHT_BRACKET; | |
case XK_backslash: return APP_KEY_BACKSLASH; | |
case XK_semicolon: return APP_KEY_SEMICOLON; | |
case XK_apostrophe: return APP_KEY_APOSTROPHE; | |
case XK_grave: return APP_KEY_GRAVE_ACCENT; | |
case XK_comma: return APP_KEY_COMMA; | |
case XK_period: return APP_KEY_PERIOD; | |
case XK_slash: return APP_KEY_SLASH; | |
case XK_less: return APP_KEY_WORLD_1; /* At least in some layouts... */ | |
default: return APP_KEY_INVALID; | |
} | |
} | |
APP_INTERNAL APP_MouseButton | |
APP__Translate_mouse_button(unsigned int button) { | |
switch (button) { | |
case Button1: return APP_MOUSE_BUTTON_LEFT; | |
case Button2: return APP_MOUSE_BUTTON_MIDDLE; | |
case Button3: return APP_MOUSE_BUTTON_RIGHT; | |
default: return APP_MOUSE_BUTTON_INVALID; | |
} | |
} | |
APP_INTERNAL void | |
APP__linux_Process_events() { | |
XEvent event; | |
for (int remaining_events = XPending(APP__x11_data.xdisplay); | |
remaining_events > 0; | |
--remaining_events) { | |
XNextEvent(APP__x11_data.xdisplay, &event); | |
switch (event.type) { | |
case ConfigureNotify: { | |
if ((event.xconfigure.width != APP__data.w_width) || | |
(event.xconfigure.height != APP__data.w_width)) { | |
APP__data.w_width = event.xconfigure.width; | |
APP__data.w_height = event.xconfigure.height; | |
APP__data.window_resized = true; | |
} | |
break; | |
} | |
case FocusOut: { | |
// Puts all the keys down to up because will never notify the up event | |
APP__Put_keys_up(); | |
break; | |
} | |
case KeyPress: { | |
int keycode = (int)event.xkey.keycode; | |
APP_KeyCode key = APP__Translate_key(keycode); | |
if (APP__data.keyboard[key] == APP__KEY_STATUS_PRESSED || | |
APP__data.keyboard[key] == APP__KEY_STATUS_DOWN) { | |
APP__data.keyboard[key] = APP__KEY_STATUS_DOWN; | |
} | |
else { | |
APP__data.keyboard[key] = APP__KEY_STATUS_PRESSED; | |
} | |
APP__data.keyboard[key] |= APP__KEY_STATUS_REPEAT; // Always set as repeat | |
Status return_status; | |
char buffer[32] = ""; | |
int bytes_length = Xutf8LookupString(APP__x11_data.xic, (XKeyPressedEvent *)&event, buffer, 32, NULL, &return_status); | |
if (return_status == XLookupChars || return_status == XLookupBoth) { | |
if (!APP__Is_char_filtered(buffer[0]) && | |
APP__data.text_input_bytes_count + bytes_length < APP__TEXT_INPUT_BUFFER_MAX) { | |
for (int i = 0; i < bytes_length; ++i) { | |
APP__data.text_input_buffer[APP__data.text_input_bytes_count+i] = buffer[i]; | |
} | |
APP__data.text_input_bytes_count = APP__data.text_input_bytes_count + bytes_length; | |
APP__data.text_input_buffer[APP__data.text_input_bytes_count] = '\0'; | |
} | |
} | |
break; | |
} | |
case KeyRelease: { | |
// Checks if the key release is from an autorepeat | |
// https://stackoverflow.com/questions/2100654/ignore-auto-repeat-in-x11-applications | |
if (XEventsQueued(APP__x11_data.xdisplay, QueuedAfterReading)) { | |
XEvent next_event; | |
XPeekEvent(APP__x11_data.xdisplay, &next_event); | |
if (next_event.type == KeyPress && | |
next_event.xkey.time == event.xkey.time && | |
next_event.xkey.keycode == event.xkey.keycode) { | |
break; | |
} | |
} | |
int keycode = (int)event.xkey.keycode; | |
APP_KeyCode key = APP__Translate_key(keycode); | |
APP__data.keyboard[key] = APP__KEY_STATUS_RELEASED; | |
break; | |
} | |
case ButtonPress: { | |
APP_MouseButton btn = APP__Translate_mouse_button(event.xbutton.button); | |
if (btn != APP_MOUSE_BUTTON_INVALID) { | |
APP__Report_mouse_button_down(btn); | |
} | |
else { | |
/* might be a scroll event */ | |
switch (event.xbutton.button) { | |
case 4: APP__data.mouse.wheel_y = 1.0f; break; | |
case 5: APP__data.mouse.wheel_y = -1.0f; break; | |
case 6: APP__data.mouse.wheel_x = 1.0f; break; | |
case 7: APP__data.mouse.wheel_x = -1.0f; break; | |
} | |
} | |
break; | |
} | |
case ButtonRelease: | |
{ | |
APP_MouseButton btn = APP__Translate_mouse_button(event.xbutton.button); | |
if (btn != APP_MOUSE_BUTTON_INVALID) { | |
APP__Report_mouse_button_up(btn); | |
} | |
break; | |
} | |
case ClientMessage: { | |
if (event.xclient.message_type == APP__x11_data.WM_PROTOCOLS) { | |
const Atom protocol = (Atom)event.xclient.data.l[0]; | |
if (protocol == APP__x11_data.WM_DELETE_WINDOW) { | |
APP__data.quit_requested = true; | |
} | |
} | |
break; | |
} | |
case MotionNotify: { | |
APP__Report_mouse_move((float)event.xmotion.x, (float)event.xmotion.y); | |
break; | |
} | |
break;// TODO(Tano) HANDLE EVERYTHING!!!!! | |
} | |
} | |
} | |
APP_INTERNAL int64_t | |
APP__linux_Time(void) { | |
struct timespec current; | |
clock_gettime(CLOCK_MONOTONIC_RAW, ¤t); | |
const int64_t SECOND = 1000*1000*1000; | |
int64_t result = (current.tv_sec - APP__x11_data.initial_time.tv_sec) * (1 * SECOND); | |
result += current.tv_nsec - APP__x11_data.initial_time.tv_nsec; | |
return result; | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// SOUND API | |
// | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
#include <pthread.h> | |
#define ALSA_PCM_NEW_HW_PARAMS_API | |
#include <alsa/asoundlib.h> | |
static struct { | |
snd_pcm_t* device; | |
float* buffer; | |
pthread_t thread; | |
volatile bool thread_stop; | |
} APP__sound_player_backend = {0}; | |
APP_INTERNAL void | |
APP__Cleanup_sound_player(void) { | |
if (APP__sound_player_backend.device) { | |
snd_pcm_close(APP__sound_player_backend.device); | |
APP__sound_player_backend.device = NULL; | |
snd_config_update_free_global(); | |
} | |
} | |
/* the streaming callback runs in a separate thread */ | |
APP_INTERNAL void * | |
APP__Sound_player_thread(void *param) { | |
snd_pcm_prepare(APP__sound_player_backend.device); | |
while (!APP__sound_player_backend.thread_stop) { | |
/* snd_pcm_writei() will be blocking until it needs data */ | |
int write_res = snd_pcm_writei( | |
APP__sound_player_backend.device, | |
APP__sound_player_backend.buffer, | |
(snd_pcm_uframes_t)APP__data.sound_player_info.buffer_frames | |
); | |
if (write_res < 0) { | |
/* underrun occurred */ | |
snd_pcm_prepare(APP__sound_player_backend.device); | |
} | |
else { | |
/* fill the streaming buffer with new data */ | |
APP__data.sound_player_info.stream_cb( | |
APP__data.sound_player_info.user_data, | |
APP__sound_player_backend.buffer, | |
APP__data.sound_player_info.buffer_frames, | |
APP__data.sound_player_info.num_channels | |
); | |
} | |
} | |
return 0; | |
} | |
APP_PUBLIC int | |
APP_Launch_sound_player(APP_SoundPlayerDesc *desc) { | |
int dir; uint32_t rate; | |
int rc = snd_pcm_open(&APP__sound_player_backend.device, "default", SND_PCM_STREAM_PLAYBACK, 0); | |
if (rc < 0) { | |
fprintf(stderr, "Cannot open the audio device.\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
/* configuration works by restricting the 'configuration space' step | |
by step, we require all parameters except the sample rate and buffer size to | |
match perfectly | |
*/ | |
snd_pcm_hw_params_t* params = 0; | |
snd_pcm_hw_params_alloca(¶ms); | |
int error_code = snd_pcm_hw_params_any(APP__sound_player_backend.device, params); | |
if (0 > error_code) { | |
printf("Broken configuration for %s PCM: no configurations available\n", snd_strerror(error_code)); | |
return error_code; | |
} | |
error_code = snd_pcm_hw_params_set_access(APP__sound_player_backend.device, params, SND_PCM_ACCESS_RW_INTERLEAVED); | |
if (0 > error_code) { | |
printf("snd_pcm_hw_params_set_access() failed. Code: %d. Error: %s\n", error_code, snd_strerror(error_code)); | |
return error_code; | |
} | |
if (0 > snd_pcm_hw_params_set_format(APP__sound_player_backend.device, params, SND_PCM_FORMAT_FLOAT_LE)) { | |
fprintf(stderr, "Cannot device doesn't support floats\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
/* let ALSA pick a nearby sampling rate */ | |
rate = (uint32_t) desc->sample_rate; | |
dir = 0; | |
error_code = snd_pcm_hw_params_set_rate_near(APP__sound_player_backend.device, params, &rate, &dir); | |
if (0 > error_code) { | |
const char *error = snd_strerror(error_code); | |
fprintf(stderr, "snd_pcm_hw_params_set_rate_near() failed. Code: %d, Error: %s\n", error_code, error); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
/* Pick a near buffer size */ | |
snd_pcm_uframes_t buffer_frames = desc->buffer_frames; | |
if (0 > snd_pcm_hw_params_set_buffer_size_near(APP__sound_player_backend.device, params, &buffer_frames)) { | |
fprintf(stderr, "Requested buffer size not supported\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
if (0 > snd_pcm_hw_params_set_channels(APP__sound_player_backend.device, params, (uint32_t)desc->num_channels)) { | |
fprintf(stderr, "Requested channel count not supported\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
if (0 > snd_pcm_hw_params(APP__sound_player_backend.device, params)) { | |
fprintf(stderr, "snd_pcm_hw_params() failed\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
/* read back actual sample rate and channels */ | |
APP__data.sound_player_info.sample_rate = rate; | |
APP__data.sound_player_info.buffer_frames = buffer_frames; | |
APP__data.sound_player_info.user_data = desc->user_data; | |
APP__data.sound_player_info.stream_cb = desc->stream_cb; | |
APP__data.sound_player_info.num_channels = desc->num_channels; | |
/* allocate the streaming buffer */ | |
size_t buffer_nelems = APP__data.sound_player_info.buffer_frames * APP__data.sound_player_info.num_channels; | |
APP__sound_player_backend.buffer = (float *)malloc(sizeof(float) * buffer_nelems); // TODO(Tano) Check this | |
for (int i = 0; i < buffer_nelems; i+=1) APP__sound_player_backend.buffer[i] = 0.0f; | |
/* create the buffer-streaming start thread */ | |
if (0 != pthread_create(&APP__sound_player_backend.thread, 0, APP__Sound_player_thread, 0)) { | |
fprintf(stderr, "pthread_create() failed\n"); | |
APP__Cleanup_sound_player(); | |
return -1; | |
} | |
return 0; | |
} | |
APP_PUBLIC void | |
APP_Terminate_sound_player(void) { | |
APP__sound_player_backend.thread_stop = true; | |
void *dummy; | |
pthread_join(APP__sound_player_backend.thread, &dummy); | |
APP__Cleanup_sound_player(); | |
if (APP__sound_player_backend.buffer != NULL) { | |
free(APP__sound_player_backend.buffer); | |
APP__sound_player_backend.buffer = NULL; | |
} | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// THREAD API | |
// | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
#include <pthread.h> | |
void | |
Mutex_init(Mutex *mutex) { | |
if (pthread_mutex_init(mutex, NULL) != 0) { | |
abort(); | |
} | |
} | |
void | |
Mutex_deinit(Mutex *mutex) { | |
} | |
void | |
Mutex_lock(Mutex *mutex) { | |
if (pthread_mutex_lock(mutex) != 0) { | |
abort(); | |
} | |
} | |
void | |
Mutex_unlock(Mutex *mutex) { | |
if (pthread_mutex_unlock(mutex) != 0) { | |
abort(); | |
} | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// FILESYSTEM API | |
// | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <unistd.h> | |
// Documented above | |
#if !defined(BUNDLED_TREE) | |
uint64_t | |
Get_file_modify_time(const char *filename) { | |
struct stat file_stat; | |
int err = stat(filename, &file_stat); | |
if (err != 0) { | |
return 0; | |
} | |
return (uint64_t)file_stat.st_mtime; | |
} | |
#endif | |
///////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// WASM IMPLEMENTATION |||| | |
// VVVV | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
#elif defined(APP_WASM) | |
// Macro to generate a JavaScript function that can be called from C | |
APP__WA_JS(void, APP__JS_Init, (void), { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
Module.ASM.WAFN_report_resize(canvas.width, canvas.height); // We call directly to set the correct size on the first frame | |
// Sets the handler to report to the application the windows resizing. | |
window.onresize = () => { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
Module.ASM.WAFN_report_resize(window.innerWidth, window.innerHeight); | |
}; | |
// Key down | |
window.addEventListener('keydown', (e) => { | |
if (e.key.length === 1) { // Also fills the text input buffer | |
var char_buffer_pointer = Module.ASM.WAFN_get_char_buffer(); | |
var char_len = stringToUTF8Array(e.key, char_buffer_pointer, 128); | |
Module.ASM.WAFN_report_input_char(char_buffer_pointer, char_len); | |
} | |
// Prevent default browser's shortcuts | |
e.preventDefault(); | |
Module.ASM.WAFN_report_keydown(e.keyCode); | |
}, true); | |
// Key up | |
window.addEventListener('keyup', (e) => { | |
Module.ASM.WAFN_report_keyup(e.keyCode); | |
}, true); | |
// Mouse position | |
window.addEventListener('mousemove', function(e) { | |
let rect = canvas.getBoundingClientRect(); | |
Module.ASM.APP__Report_mouse_move(e.clientX - rect.left, e.clientY - rect.top); | |
}, false); | |
// Mouse button down | |
window.addEventListener('mousedown', function(e) { | |
if (e.buttons & 1) Module.ASM.APP__Report_mouse_button_down(0); // left button | |
if (e.buttons & 2) Module.ASM.APP__Report_mouse_button_down(1); // right button | |
if (e.buttons & 4) Module.ASM.APP__Report_mouse_button_down(2); // middle button | |
}); | |
// Mouse button up | |
window.addEventListener('mouseup', function(e) { | |
if (e.button == 0) Module.ASM.APP__Report_mouse_button_up(0); // left button | |
else if (e.button == 2) Module.ASM.APP__Report_mouse_button_up(1); // right button | |
else if (e.button == 1) Module.ASM.APP__Report_mouse_button_up(2); // middle button | |
}); | |
// Mouse button up | |
window.addEventListener('wheel', function(e) { | |
Module.ASM.WAFN_set_wheel(e.deltaX, -e.deltaY); | |
}); | |
// Touch start | |
window.addEventListener("touchstart", function(e) { | |
let touches = e.changedTouches; | |
let canvas_rect = canvas.getBoundingClientRect(); | |
for (var i = 0; i < touches.length; i++) { | |
let pos_x = touches[i].clientX - canvas_rect.left; | |
let pos_y = touches[i].clientY - canvas_rect.top; | |
Module.ASM.APP__Report_touch_event(touches[i].identifier, 0, pos_x, pos_y); | |
} | |
}, false); | |
// Touch move | |
window.addEventListener("touchmove", function(e) { | |
let touches = e.changedTouches; | |
let canvas_rect = canvas.getBoundingClientRect(); | |
for (var i = 0; i < touches.length; i++) { | |
let pos_x = touches[i].clientX - canvas_rect.left; | |
let pos_y = touches[i].clientY - canvas_rect.top; | |
Module.ASM.APP__Report_touch_event(touches[i].identifier, 1, pos_x, pos_y); | |
} | |
}, false); | |
// Touch end | |
window.addEventListener("touchend", function(e) { | |
let touches = e.changedTouches; | |
let canvas_rect = canvas.getBoundingClientRect(); | |
for (var i = 0; i < touches.length; i++) { | |
let pos_x = touches[i].clientX - canvas_rect.left; | |
let pos_y = touches[i].clientY - canvas_rect.top; | |
Module.ASM.APP__Report_touch_event(touches[i].identifier, 2, pos_x, pos_y); | |
} | |
}, false); | |
// Touch cancel | |
window.addEventListener("touchcancel", function(e) { | |
let touches = e.changedTouches; | |
let canvas_rect = canvas.getBoundingClientRect(); | |
for (var i = 0; i < touches.length; i++) { | |
let pos_x = touches[i].clientX - canvas_rect.left; | |
let pos_y = touches[i].clientY - canvas_rect.top; | |
Module.ASM.APP__Report_touch_event(touches[i].identifier, 3, pos_x, pos_y); | |
} | |
}, false); | |
// Setups the frame handler | |
function Application_frame() { | |
var result = Module.ASM.APP__Handle_frame(); | |
if (result == 0) { | |
window.requestAnimationFrame(Application_frame); | |
} | |
} | |
window.requestAnimationFrame(Application_frame); | |
Module.GLctx; | |
Module.GLlastError; | |
Module.GLcounter = 1; | |
Module.GLbuffers = []; | |
Module.GLprograms = []; | |
Module.GLframebuffers = []; | |
Module.GLtextures = []; | |
Module.GLuniforms = []; | |
Module.GLshaders = []; | |
Module.GLprogramInfos = {}; | |
Module.GLpackAlignment = 4; | |
Module.GLunpackAlignment = 4; | |
Module.GLMINI_TEMP_BUFFER_SIZE = 256; | |
Module.GLminiTempBuffer = null; | |
Module.GLminiTempBufferViews = [0]; | |
Module.GLminiTempBuffer = new Float32Array(Module.GLMINI_TEMP_BUFFER_SIZE); | |
for (var i = 0; i < Module.GLMINI_TEMP_BUFFER_SIZE; i++) Module.GLminiTempBufferViews[i] = Module.GLminiTempBuffer.subarray(0, i+1); | |
var attr = { majorVersion: 1, minorVersion: 0, antialias: true, alpha: false }; | |
var errorInfo = ""; | |
try | |
{ | |
let onContextCreationError = function(event) { errorInfo = event.statusMessage || errorInfo; }; | |
canvas.addEventListener('webglcontextcreationerror', onContextCreationError, false); | |
try | |
{ | |
Module.GLctx = canvas.getContext('webgl', attr) || canvas.getContext('experimental-webgl', attr); | |
} | |
finally { canvas.removeEventListener('webglcontextcreationerror', onContextCreationError, false); } | |
if (!Module.GLctx) throw 'Could not create context'; | |
} | |
catch (e) | |
{ | |
abort("WEBGL", e + (errorInfo ? " (" + errorInfo + ")" : "")); | |
} | |
var exts = Module.GLctx.getSupportedExtensions(); | |
if (exts && exts.length > 0) | |
{ | |
// These are the 'safe' feature-enabling extensions that don't add any performance impact related to e.g. debugging, and | |
// should be enabled by default so that client GLES2/GL code will not need to go through extra hoops to get its stuff working. | |
// As new extensions are ratified at http://www.khronos.org/registry/webgl/extensions/ , feel free to add your new extensions | |
// here, as long as they don't produce a performance impact for users that might not be using those extensions. | |
// E.g. debugging-related extensions should probably be off by default. | |
var W = 'WEBGL_', O = 'OES_', E = 'EXT_', T = 'texture_', C = 'compressed_'+T; | |
var automaticallyEnabledExtensions = [ // Khronos ratified WebGL extensions ordered by number (no debug extensions): | |
O+T+'float', O+T+'half_float', O+'standard_derivatives', | |
O+'vertex_array_object', W+C+'s3tc', W+'depth_texture', | |
O+'element_index_uint', E+T+'filter_anisotropic', E+'frag_depth', | |
W+'draw_buffers', 'ANGLE_instanced_arrays', O+T+'float_linear', | |
O+T+'half_float_linear', E+'blend_minmax', E+'shader_texture_lod', | |
// Community approved WebGL extensions ordered by number: | |
W+C+'pvrtc', E+'color_buffer_half_float', W+'color_buffer_float', | |
E+'sRGB', W+C+'etc1', E+'disjoint_timer_query', | |
W+C+'etc', W+C+'astc', E+'color_buffer_float', | |
W+C+'s3tc_srgb', E+'disjoint_timer_query_webgl2']; | |
exts.forEach(function(ext) | |
{ | |
if (automaticallyEnabledExtensions.indexOf(ext) != -1) | |
{ | |
// Calling .getExtension enables that extension permanently, no need to store the return value to be enabled. | |
Module.GLctx.getExtension(ext); | |
} | |
}); | |
} | |
Module.GLgetNewId = function(table) { | |
var ret = Module.GLcounter++; | |
for (var i = table.length; i < ret; i++) table[i] = null; | |
return ret; | |
}; | |
Module.GLgetSource = function(shader, count, string, length) { | |
var source = ""; | |
for (var i = 0; i < count; ++i) | |
{ | |
var frag; | |
if (length) | |
{ | |
var len = HEAP32[(((length)+(i*4))>>2)]; | |
if (len < 0) frag = Pointer_stringify(HEAP32[(((string)+(i*4))>>2)]); | |
else frag = Pointer_stringify(HEAP32[(((string)+(i*4))>>2)], len); | |
} | |
else frag = Pointer_stringify(HEAP32[(((string)+(i*4))>>2)]); | |
source += frag; | |
} | |
return source; | |
}; | |
Module.GLpopulateUniformTable = function(program) { | |
var p = Module.GLprograms[program]; | |
Module.GLprogramInfos[program] = | |
{ | |
uniforms: {}, | |
maxUniformLength: 0, // This is eagerly computed below, since we already enumerate all uniforms anyway. | |
maxAttributeLength: -1, // This is lazily computed and cached, computed when/if first asked, '-1' meaning not computed yet. | |
maxUniformBlockNameLength: -1 // Lazily computed as well | |
}; | |
var ptable = Module.GLprogramInfos[program]; | |
var utable = ptable.uniforms; | |
// A program's uniform table maps the string name of an uniform to an integer location of that uniform. | |
// The global GLuniforms map maps integer locations to WebGLUniformLocations. | |
var numUniforms = Module.GLctx.getProgramParameter(p, Module.GLctx.ACTIVE_UNIFORMS); | |
for (var i = 0; i < numUniforms; ++i) | |
{ | |
var u = Module.GLctx.getActiveUniform(p, i); | |
var name = u.name; | |
ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length+1); | |
// Strip off any trailing array specifier we might have got, e.g. '[0]'. | |
if (name.indexOf(']', name.length-1) !== -1) | |
{ | |
var ls = name.lastIndexOf('['); | |
name = name.slice(0, ls); | |
} | |
// Optimize memory usage slightly: If we have an array of uniforms, e.g. 'vec3 colors[3];', then | |
// only store the string 'colors' in utable, and 'colors[0]', 'colors[1]' and 'colors[2]' will be parsed as 'colors'+i. | |
// Note that for the GLuniforms table, we still need to fetch the all WebGLUniformLocations for all the indices. | |
var loc = Module.GLctx.getUniformLocation(p, name); | |
if (loc != null) | |
{ | |
var id = Module.GLgetNewId(Module.GLuniforms); | |
utable[name] = [u.size, id]; | |
Module.GLuniforms[id] = loc; | |
for (var j = 1; j < u.size; ++j) | |
{ | |
var n = name + '['+j+']'; | |
loc = Module.GLctx.getUniformLocation(p, n); | |
id = Module.GLgetNewId(Module.GLuniforms); | |
Module.GLuniforms[id] = loc; | |
} | |
} | |
} | |
}; | |
Module.GLrecordError = function(err) { | |
if (!GLlastError) GLlastError = err; | |
}; | |
Module.webGLGet = function(name_, p, type) { | |
checkHeap(); | |
// Guard against user passing a null pointer. | |
// Note that GLES2 spec does not say anything about how passing a null pointer should be treated. | |
// Testing on desktop core GL 3, the application crashes on glGetIntegerv to a null pointer, but | |
// better to report an error instead of doing anything random. | |
if (!p) { Module.GLrecordError(0x0501); return; } // GL_INVALID_VALUE | |
var ret = undefined; | |
switch(name_) { | |
// Handle a few trivial GLES values | |
case 0x8DFA: ret = 1; break; // GL_SHADER_COMPILER | |
case 0x8DF8: // GL_SHADER_BINARY_FORMATS | |
if (type !== 'Integer' && type !== 'Integer64') Module.GLrecordError(0x0500); // GL_INVALID_ENUM | |
return; // Do not write anything to the out pointer, since no binary formats are supported. | |
case 0x8DF9: ret = 0; break; // GL_NUM_SHADER_BINARY_FORMATS | |
case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS | |
// WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be queried for length), | |
// so implement it ourselves to allow C++ GLES2 code get the length. | |
var formats = Module.GLctx.getParameter(0x86A3); // GL_COMPRESSED_TEXTURE_FORMATS | |
ret = formats.length; | |
break; | |
} | |
if (ret === undefined) { | |
var result = Module.GLctx.getParameter(name_); | |
switch (typeof(result)) { | |
case 'number': | |
ret = result; | |
break; | |
case 'boolean': | |
ret = result ? 1 : 0; | |
break; | |
case 'string': | |
Module.GLrecordError(0x0500); // GL_INVALID_ENUM | |
return; | |
case 'object': | |
if (result === null) { | |
// null is a valid result for some (e.g., which buffer is bound - perhaps nothing is bound), but otherwise | |
// can mean an invalid name_, which we need to report as an error | |
switch(name_) | |
{ | |
case 0x8894: // ARRAY_BUFFER_BINDING | |
case 0x8B8D: // CURRENT_PROGRAM | |
case 0x8895: // ELEMENT_ARRAY_BUFFER_BINDING | |
case 0x8CA6: // FRAMEBUFFER_BINDING | |
case 0x8CA7: // RENDERBUFFER_BINDING | |
case 0x8069: // TEXTURE_BINDING_2D | |
case 0x8514: // TEXTURE_BINDING_CUBE_MAP | |
ret = 0; | |
break; | |
default: | |
Module.GLrecordError(0x0500); // GL_INVALID_ENUM | |
return; | |
} | |
} | |
else if (result instanceof Float32Array || result instanceof Uint32Array || result instanceof Int32Array || result instanceof Array) | |
{ | |
for (var i = 0; i < result.length; ++i) { | |
switch (type) | |
{ | |
case 'Integer': HEAP32[(((p)+(i*4))>>2)]=result[i]; break; | |
case 'Float': HEAPF32[(((p)+(i*4))>>2)]=result[i]; break; | |
case 'Boolean': HEAP8[(((p)+(i))>>0)]=result[i] ? 1 : 0; break; | |
default: abort('WEBGL', 'internal glGet error, bad type: ' + type); | |
} | |
} | |
return; | |
} | |
else if (result instanceof WebGLBuffer || result instanceof WebGLProgram || result instanceof WebGLFramebuffer || result instanceof WebGLRenderbuffer || result instanceof WebGLTexture) | |
{ | |
ret = result.name | 0; | |
} | |
else | |
{ | |
Module.GLrecordError(0x0500); // GL_INVALID_ENUM | |
return; | |
} | |
break; | |
default: | |
Module.GLrecordError(0x0500); // GL_INVALID_ENUM | |
return; | |
} | |
} | |
switch (type) | |
{ | |
case 'Integer64': (tempI64 = [ret>>>0,(tempDouble=ret,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? ((Math.min((+(Math.floor((tempDouble)/4294967296.0))), 4294967295.0))|0)>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)],HEAP32[((p)>>2)]=tempI64[0],HEAP32[(((p)+(4))>>2)]=tempI64[1]); break; | |
case 'Integer': HEAP32[((p)>>2)] = ret; break; | |
case 'Float': HEAPF32[((p)>>2)] = ret; break; | |
case 'Boolean': HEAP8[((p)>>0)] = ret ? 1 : 0; break; | |
default: abort('WEBGL', 'internal glGet error, bad type: ' + type); | |
} | |
}; | |
Module.webGLGetTexPixelData = function(type, format, width, height, pixels, internalFormat) { | |
checkHeap(); | |
var sizePerPixel; | |
var numChannels; | |
switch(format) { | |
case 0x1906: case 0x1909: case 0x1902: numChannels = 1; break; //GL_ALPHA, GL_LUMINANCE, GL_DEPTH_COMPONENT | |
case 0x190A: numChannels = 2; break; //GL_LUMINANCE_ALPHA | |
case 0x1907: case 0x8C40: numChannels = 3; break; //GL_RGB, GL_SRGB_EXT | |
case 0x1908: case 0x8C42: numChannels = 4; break; //GL_RGBA, GL_SRGB_ALPHA_EXT | |
default: throw new Error();//GLrecordError(0x0500); return null; //GL_INVALID_ENUM | |
} | |
switch (type) { | |
case 0x1401: sizePerPixel = numChannels*1; break; //GL_UNSIGNED_BYTE | |
case 0x1403: case 0x8D61: sizePerPixel = numChannels*2; break; //GL_UNSIGNED_SHORT, GL_HALF_FLOAT_OES | |
case 0x1405: case 0x1406: sizePerPixel = numChannels*4; break; //GL_UNSIGNED_INT, GL_FLOAT | |
case 0x84FA: sizePerPixel = 4; break; //GL_UNSIGNED_INT_24_8_WEBGL/GL_UNSIGNED_INT_24_8 | |
case 0x8363: case 0x8033: case 0x8034: sizePerPixel = 2; break; //GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1 | |
default: throw new Error();//GLrecordError(0x0500); return null; //GL_INVALID_ENUM | |
} | |
function roundedToNextMultipleOf(x, y) { return Math.floor((x + y - 1) / y) * y; } | |
var plainRowSize = width * sizePerPixel; | |
var alignedRowSize = roundedToNextMultipleOf(plainRowSize, Module.GLunpackAlignment); | |
var bytes = (height <= 0 ? 0 : ((height - 1) * alignedRowSize + plainRowSize)); | |
switch(type) | |
{ | |
case 0x1401: return HEAPU8.subarray((pixels),(pixels+bytes)); //GL_UNSIGNED_BYTE | |
case 0x1406: return HEAPF32.subarray((pixels)>>2,(pixels+bytes)>>2); //GL_FLOAT | |
case 0x1405: case 0x84FA: return HEAPU32.subarray((pixels)>>2,(pixels+bytes)>>2); //GL_UNSIGNED_INT, GL_UNSIGNED_INT_24_8_WEBGL/GL_UNSIGNED_INT_24_8 | |
case 0x1403: case 0x8363: case 0x8033: case 0x8034: case 0x8D61: return HEAPU16.subarray((pixels)>>1,(pixels+bytes)>>1); //GL_UNSIGNED_SHORT, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1, GL_HALF_FLOAT_OES | |
default: throw new Error();//GLrecordError(0x0500); return null; //GL_INVALID_ENUM | |
} | |
} | |
}); | |
APP__WA_JS(int32_t, APP__JS_Get_time, (void), { | |
return Date.now(); | |
}); | |
APP__WA_JS(void, APP__JS_Set_window_title, (const char *title, int len), { | |
const UTF8_decoder = new TextDecoder("utf-8"); | |
window.document.title = UTF8_decoder.decode(new Uint8Array(Module.ASM.memory.buffer, title, len)); | |
}); | |
APP__WA_JS(void, APP__JS_Set_fullscreen, (bool enable), { | |
if(enable) { | |
document.documentElement.requestFullscreen().catch(console.log); | |
}else { | |
document.exitFullscreen(); | |
} | |
}); | |
APP__WA_JS(void, APP__JS_Show_mouse, (bool show), { | |
if (show) { | |
canvas.style.cursor = 'auto'; | |
} | |
else { | |
canvas.style.cursor = 'none'; | |
} | |
}); | |
APP__WA_JS(bool, APP__JS_Window_has_focus, (void), { | |
return document.hasFocus(); | |
}); | |
APP_INTERNAL int | |
APP__wasm_Init(const char *title, int width, int height) { | |
APP__data.default_width = width; | |
APP__data.default_height = height; | |
APP__data.w_width = width; | |
APP__data.w_height = height; | |
APP__JS_Init(); | |
APP__wasm_Set_window_title(title); | |
int64_t MSECOND = 1000 * 1000; | |
APP__wasm_data.initial_time = MSECOND*(int64_t)APP__JS_Get_time(); | |
return 0; | |
} | |
APP_INTERNAL void | |
APP__wasm_Shutdown(void) { | |
// We set the viewport black | |
glClearColor(0.0, 0.0, 0.0, 1.0);// background color | |
glClear(GL_COLOR_BUFFER_BIT); | |
} | |
APP_INTERNAL void | |
APP__wasm_Set_window_title(const char *title) { | |
int len = 0; | |
while (title[len]) len += 1; | |
APP__JS_Set_window_title(title, len); | |
} | |
APP_INTERNAL void | |
APP__wasm_Set_fullscreen(bool enable) { | |
APP__JS_Set_fullscreen(enable); | |
} | |
APP_INTERNAL void | |
APP__wasm_Show_mouse(bool show) { | |
APP__JS_Show_mouse(show); | |
} | |
APP_INTERNAL int64_t | |
APP__wasm_Time(void) { | |
int64_t MSECOND = 1000 * 1000; | |
return (MSECOND*(int64_t)APP__JS_Get_time()) - APP__wasm_data.initial_time; | |
} | |
#define APP__MAX_KEYMAPPING 512 | |
static APP_KeyCode APP__key_mapping[APP__MAX_KEYMAPPING] = { | |
[8] = APP_KEY_BACKSPACE, | |
[9] = APP_KEY_TAB, | |
[13] = APP_KEY_ENTER, | |
[16] = APP_KEY_LEFT_SHIFT, | |
[17] = APP_KEY_LEFT_CONTROL, | |
[18] = APP_KEY_LEFT_ALT, | |
[19] = APP_KEY_PAUSE, | |
[27] = APP_KEY_ESCAPE, | |
[32] = APP_KEY_SPACE, | |
[33] = APP_KEY_PAGE_UP, | |
[34] = APP_KEY_PAGE_DOWN, | |
[35] = APP_KEY_END, | |
[36] = APP_KEY_HOME, | |
[37] = APP_KEY_LEFT, | |
[38] = APP_KEY_UP, | |
[39] = APP_KEY_RIGHT, | |
[40] = APP_KEY_DOWN, | |
[45] = APP_KEY_INSERT, | |
[46] = APP_KEY_DELETE, | |
[48] = APP_KEY_0, | |
[49] = APP_KEY_1, | |
[50] = APP_KEY_2, | |
[51] = APP_KEY_3, | |
[52] = APP_KEY_4, | |
[53] = APP_KEY_5, | |
[54] = APP_KEY_6, | |
[55] = APP_KEY_7, | |
[56] = APP_KEY_8, | |
[57] = APP_KEY_9, | |
[59] = APP_KEY_SEMICOLON, | |
[64] = APP_KEY_EQUAL, | |
[65] = APP_KEY_A, | |
[66] = APP_KEY_B, | |
[67] = APP_KEY_C, | |
[68] = APP_KEY_D, | |
[69] = APP_KEY_E, | |
[70] = APP_KEY_F, | |
[71] = APP_KEY_G, | |
[72] = APP_KEY_H, | |
[73] = APP_KEY_I, | |
[74] = APP_KEY_J, | |
[75] = APP_KEY_K, | |
[76] = APP_KEY_L, | |
[77] = APP_KEY_M, | |
[78] = APP_KEY_N, | |
[79] = APP_KEY_O, | |
[80] = APP_KEY_P, | |
[81] = APP_KEY_Q, | |
[82] = APP_KEY_R, | |
[83] = APP_KEY_S, | |
[84] = APP_KEY_T, | |
[85] = APP_KEY_U, | |
[86] = APP_KEY_V, | |
[87] = APP_KEY_W, | |
[88] = APP_KEY_X, | |
[89] = APP_KEY_Y, | |
[90] = APP_KEY_Z, | |
[91] = APP_KEY_LEFT_SUPER, | |
[93] = APP_KEY_MENU, | |
[96] = APP_KEY_KP_0, | |
[97] = APP_KEY_KP_1, | |
[98] = APP_KEY_KP_2, | |
[99] = APP_KEY_KP_3, | |
[100] = APP_KEY_KP_4, | |
[101] = APP_KEY_KP_5, | |
[102] = APP_KEY_KP_6, | |
[103] = APP_KEY_KP_7, | |
[104] = APP_KEY_KP_8, | |
[105] = APP_KEY_KP_9, | |
[106] = APP_KEY_KP_MULTIPLY, | |
[107] = APP_KEY_KP_ADD, | |
[109] = APP_KEY_KP_SUBTRACT, | |
[110] = APP_KEY_KP_DECIMAL, | |
[111] = APP_KEY_KP_DIVIDE, | |
[112] = APP_KEY_F1, | |
[113] = APP_KEY_F2, | |
[114] = APP_KEY_F3, | |
[115] = APP_KEY_F4, | |
[116] = APP_KEY_F5, | |
[117] = APP_KEY_F6, | |
[118] = APP_KEY_F7, | |
[119] = APP_KEY_F8, | |
[120] = APP_KEY_F9, | |
[121] = APP_KEY_F10, | |
[122] = APP_KEY_F11, | |
[123] = APP_KEY_F12, | |
[144] = APP_KEY_NUM_LOCK, | |
[145] = APP_KEY_SCROLL_LOCK, | |
[173] = APP_KEY_MINUS, | |
[186] = APP_KEY_SEMICOLON, | |
[187] = APP_KEY_EQUAL, | |
[188] = APP_KEY_COMMA, | |
[189] = APP_KEY_MINUS, | |
[190] = APP_KEY_PERIOD, | |
[191] = APP_KEY_SLASH, | |
[192] = APP_KEY_GRAVE_ACCENT, | |
[219] = APP_KEY_LEFT_BRACKET, | |
[220] = APP_KEY_BACKSLASH, | |
[221] = APP_KEY_RIGHT_BRACKET, | |
[222] = APP_KEY_APOSTROPHE, | |
[224] = APP_KEY_LEFT_SUPER, | |
}; | |
/* WAFN_report_resize | |
* ======================================== | |
* | |
* DESCRIPTIION | |
* ---------------------------------------- | |
* Binding to call from javascript on each frame | |
* | |
*/ | |
__attribute__((export_name("WAFN_report_resize"))) void | |
WAFN_report_resize(int w, int h) { | |
APP__data.w_width = w; | |
APP__data.w_height = h; | |
APP__data.window_resized = true; | |
} | |
// Buffer to put which character was send from javascript | |
__attribute__((export_name("WAFN_get_char_buffer"))) char * | |
WAFN_get_char_buffer(void){ | |
static char buffer[128] = ""; | |
return buffer; | |
} | |
/* WAFN_report_keydown | |
* ======================================== | |
* | |
* DESCRIPTIION | |
* ---------------------------------------- | |
* Binding to call from javascript on each keydown | |
* | |
*/ | |
__attribute__((export_name("WAFN_report_input_char"))) void | |
WAFN_report_input_char(char *utf8_char, int char_len) { | |
if (!APP__Is_char_filtered(utf8_char[0]) && | |
APP__data.text_input_bytes_count + char_len < APP__TEXT_INPUT_BUFFER_MAX) { | |
for (int i = 0; i < char_len; ++i) { | |
APP__data.text_input_buffer[APP__data.text_input_bytes_count+i] = utf8_char[i]; | |
} | |
APP__data.text_input_bytes_count = APP__data.text_input_bytes_count + char_len; | |
APP__data.text_input_buffer[APP__data.text_input_bytes_count] = '\0'; | |
} | |
} | |
/* WAFN_report_keydown | |
* ======================================== | |
* | |
* DESCRIPTIION | |
* ---------------------------------------- | |
* Binding to call from javascript on each keydown | |
* | |
*/ | |
__attribute__((export_name("WAFN_report_keydown"))) void | |
WAFN_report_keydown(int keycode) { | |
if (keycode >= 0 && keycode < APP__MAX_KEYMAPPING) { | |
int key_id = APP__key_mapping[keycode]; | |
if (APP__data.keyboard[key_id] & APP__KEY_STATUS_PRESSED || | |
APP__data.keyboard[key_id] & APP__KEY_STATUS_DOWN) { | |
APP__data.keyboard[key_id] = APP__KEY_STATUS_DOWN; | |
} | |
else { | |
APP__data.keyboard[key_id] = APP__KEY_STATUS_PRESSED; | |
} | |
APP__data.keyboard[key_id] |= APP__KEY_STATUS_REPEAT; | |
} | |
} | |
/* WAFN_report_keyup | |
* ======================================== | |
* | |
* DESCRIPTIION | |
* ---------------------------------------- | |
* Binding to call from javascript on each keyup | |
* | |
*/ | |
__attribute__((export_name("WAFN_report_keyup"))) void | |
WAFN_report_keyup(int keycode) { | |
if (keycode >= 0 && keycode < APP__MAX_KEYMAPPING) { | |
APP__data.keyboard[APP__key_mapping[keycode]] = APP__KEY_STATUS_RELEASED; | |
} | |
} | |
__attribute__((export_name("WAFN_set_wheel"))) void | |
WAFN_set_wheel(float delta_x, float delta_y) { | |
APP__data.mouse.wheel_x = delta_x; | |
APP__data.mouse.wheel_y = delta_y; | |
} | |
APP_INTERNAL void | |
APP__wasm_Process_events(void) { | |
APP__data.focus = APP__JS_Window_has_focus(); | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// SOUND API | |
// | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
#include <stdlib.h> | |
static struct SoundPlayerBackend { | |
float *buffer; | |
} APP__sound_player_backend = {0}; | |
extern void JS_start_audio(uint32_t sample_rate, uint32_t n_channels, uint32_t n_frames, float *buffer); | |
extern void JS_terminate_audio(); | |
__attribute__((export_name("WAFN_audio_cb"))) bool | |
WAFN_audio_cb(uint32_t n_frames, uint32_t n_channels, float *buffer) { | |
int result = APP__data.sound_player_info.stream_cb( | |
APP__data.sound_player_info.user_data, | |
buffer, | |
n_frames, | |
n_channels | |
); | |
return result == 0; | |
} | |
APP_PUBLIC int | |
APP__Launch_sound_player(APP_SoundPlayerDesc *desc) { | |
APP__sound_player_backend.buffer = (float *)malloc(sizeof(float) * desc->num_channels * desc->buffer_frames); | |
JS_start_audio( | |
desc->sample_rate, | |
desc->num_channels, | |
desc->buffer_frames, | |
APP__sound_player_backend.buffer | |
); | |
APP__data.sound_player_info.sample_rate = desc->sample_rate; | |
APP__data.sound_player_info.buffer_frames = desc->buffer_frames; | |
APP__data.sound_player_info.user_data = desc->user_data; | |
APP__data.sound_player_info.stream_cb = desc->stream_cb; | |
APP__data.sound_player_info.num_channels = desc->num_channels; | |
return 0; | |
} | |
APP_PUBLIC void | |
APP__Terminate_sound_player(void) { | |
if (APP__sound_player_backend.buffer != NULL) { | |
free(APP__sound_player_backend.buffer); | |
APP__sound_player_backend.buffer = NULL; | |
} | |
JS_terminate_audio(); | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
// | |
// THREAD API | |
// | |
// | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// We don't use threads on wasm, so this does nothing. | |
// | |
void | |
Mutex_init(Mutex *mutex) { | |
} | |
void | |
Mutex_deinit(Mutex *mutex) { | |
} | |
void | |
Mutex_lock(Mutex *mutex) { | |
} | |
void | |
Mutex_unlock(Mutex *mutex) { | |
} | |
#endif // defined (APP_WASM) | |
#if defined(BUNDLED_TREE) | |
extern const unsigned int BUNDLE_FILES_COUNT; | |
extern const char * const bundle_filenames[]; | |
extern char * const bundle_files_index[]; | |
extern const unsigned int bundle_file_sizes[]; | |
#endif | |
// Documented on the above, on the head section | |
#if defined(APP_WASM) | |
APP_PUBLIC int | |
APP_Run_application_loop(int(*application_frame)(void)) { | |
APP__data.frame_callback = application_frame; | |
return 0; | |
} | |
#else | |
APP_PUBLIC int | |
APP_Run_application_loop(int(*application_frame)(void)) { | |
assert(application_frame); | |
APP__data.frame_callback = application_frame; | |
int loop_result = 0; | |
for (;;) { | |
loop_result = APP__Handle_frame(); | |
if (loop_result != 0) {break;} | |
} | |
#if defined(APP_WINDOWS) | |
// I'm not sure if this can be done here or must be always inside the windowProc :/ Tano. | |
PostQuitMessage(0); | |
#endif | |
if (loop_result == 1) { | |
return 0; | |
} | |
return loop_result; | |
} | |
#endif | |
#include <string.h> | |
// Documented on the above, on the head section | |
uint8_t * | |
APP_Get_file_contentsz(const char *filename, uint32_t *size) { | |
#if defined(BUNDLED_TREE) | |
// This is a linear search, we may use a hashmap :/ | |
for (uint32_t i = 0; i < BUNDLE_FILES_COUNT; ++i) { | |
if (strcmp(filename, bundle_filenames[i]) == 0) { | |
if (size) { *size = bundle_file_sizes[i]; } | |
return (uint8_t *)bundle_files_index[i]; | |
} | |
} | |
return NULL; | |
#else | |
FILE *f = fopen(filename, "rb"); | |
if (f == NULL) { | |
return NULL; | |
} | |
fseek(f, 0L, SEEK_END); | |
uint32_t file_size = ftell(f); | |
fseek(f, 0L, SEEK_SET); | |
uint8_t *data = malloc(file_size + 1); // TODO(Tano) Check this?? | |
fread(data, 1, file_size, f); | |
fclose(f); | |
data[file_size] = '\0'; | |
if (size) {*size = file_size;} | |
return data; | |
#endif | |
} | |
// Documented on the above, on the head section | |
void | |
APP_Free_file_contents(uint8_t *data) { | |
if (data == NULL) {return;} | |
#if !defined(BUNDLED_TREE) | |
free(data); | |
#endif | |
} | |
// Documented above | |
#if defined(BUNDLED_TREE) | |
uint64_t | |
APP_Get_file_modify_time(const char *filename) { | |
return 0; | |
} | |
#endif | |
#endif // _APP_H_ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment