commit 6120807f92d13629fcdc05a1646bd317e8b9c320 Author: Brandon Dyck Date: Tue Nov 23 20:24:38 2021 -0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e73c965 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +zig-cache/ +zig-out/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..68177c7 --- /dev/null +++ b/build.zig @@ -0,0 +1,32 @@ +const std = @import("std"); +const minifb = @import("lib/minifb/build.zig"); + +pub fn build(b: *std.build.Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("minifb-zig", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + + exe.addPackagePath("minifb", "lib/minifb/src/minifb.zig"); + minifb.link(b, exe); + + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/lib/minifb/build.zig b/lib/minifb/build.zig new file mode 100644 index 0000000..64795f5 --- /dev/null +++ b/lib/minifb/build.zig @@ -0,0 +1,72 @@ +const std = @import("std"); + +// fn thisDir() []const u8 { +// return std.fs.path.dirname(@src().file) orelse "."; +// } + +pub fn build(b: *std.build.Builder) void { + // TODO Use mode to set debug flags in MiniFB + const mode = b.standardReleaseOptions(); + const target = b.standardTargetOptions(.{}); + + var main_tests = b.addTest("src/minifb.zig"); + main_tests.setBuildMode(mode); + main_tests.setTarget(target); + link(b, main_test); + + const test_step = b.step("test", "Run library tests"); + test_step.dependOn(&main_tests.step); +} + +pub fn link(b: *std.build.Builder, step: *std.build.LibExeObjStep) void { + const lib = buildLibrary(b, step); + step.linkLibrary(lib); +} + +fn fromHere(allocator: *std.mem.Allocator, path: []const u8) []const u8 { + const here = std.fs.path.dirname(@src().file) orelse "."; + return std.fs.path.join(allocator, &.{here, path}) catch unreachable; +} + +fn buildLibrary(b: *std.build.Builder, step: *std.build.LibExeObjStep) *std.build.LibExeObjStep { + // var main_abs = std.fs.path.join(b.allocator, &.{thisDir(), "src/minifb.zig"}) catch unreachable; + const lib = b.addStaticLibrary("minifb", fromHere(b.allocator, "src/minifb.zig")); + lib.setBuildMode(step.build_mode); + + var sources = std.ArrayList([]const u8) .init(b.allocator); + for ([_][]const u8{ + "upstream/src/MiniFB_common.c", + "upstream/src/MiniFB_internal.c", + //"upstream/src/MiniFB_internal.h", + "upstream/src/MiniFB_timer.c", + //"upstream/src/WindowData.h", + //"upstream/src/windows/WindowData_Win.h", + "upstream/src/windows/WinMiniFB.c", + }) |path| { + // const abs_path = std.fs.path.join(b.allocator, &.{ thisDir(), path}) catch unreachable; + sources.append(fromHere(b.allocator, path)) catch unreachable; + } + + // TODO More than Windows + lib.addCSourceFiles(sources.items, &[_][]const u8{ + "-Wall", + "-Wextra", + "-pedantic", + "-Wno-switch", + "-Wno-unused-function", + "-Wno-implicit-fallthrough", + "-std=c11", + }); + + const include_abs = fromHere(b.allocator, "upstream/include"); + const src_abs = fromHere(b.allocator, "upstream/src"); + lib.addIncludeDir(include_abs); + lib.addIncludeDir(src_abs); + step.addIncludeDir(include_abs); + step.addIncludeDir(src_abs); + + lib.linkSystemLibraryName("gdi32"); + lib.linkLibC(); + lib.install(); + return lib; +} \ No newline at end of file diff --git a/lib/minifb/src/minifb.zig b/lib/minifb/src/minifb.zig new file mode 100644 index 0000000..874149b --- /dev/null +++ b/lib/minifb/src/minifb.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const minifb_c = @cImport({ + @cInclude("MiniFB.h"); + @cInclude("MiniFB_enums.h"); +}); +const testing = std.testing; + +pub const Window = struct{ + cwin: *minifb_c.mfb_window, + + pub const UpdateError = error { + InvalidWindow, + InvalidBuffer, + InternalError, + }; + + pub const State = enum {ok, exit}; + + pub fn open(title: [*:0]const u8, width: u32, height: u32) !Window { + const cwin: ?*minifb_c.mfb_window = minifb_c.mfb_open(@as([*c]const u8, title), width, height); + if (cwin) |value| { + return Window {.cwin=value}; + } else { + return error.ItBroke; + } + + } + + pub fn waitSync(self: Window) bool { + return minifb_c.mfb_wait_sync(self.cwin); + } + + pub fn update(self:Window, buffer: []u32) UpdateError!State { + const rawState = minifb_c.mfb_update(self.cwin, buffer.ptr); + switch (rawState) { + .STATE_OK => return State.ok, + .STATE_EXIT => return State.exit, + .STATE_INVALID_WINDOW => return UpdateError.InvalidWindow, + .STATE_INVALID_BUFFER => return UpdateError.InvalidBuffer, + else => return UpdateError.InternalError, + } + } + + +}; diff --git a/lib/minifb/upstream/include/MiniFB.h b/lib/minifb/upstream/include/MiniFB.h new file mode 100644 index 0000000..c5c6120 --- /dev/null +++ b/lib/minifb/upstream/include/MiniFB.h @@ -0,0 +1,91 @@ +#ifndef _MINIFB_H_ +#define _MINIFB_H_ + +#include "MiniFB_enums.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#define MFB_RGB(r, g, b) (((uint32_t) r) << 16) | (((uint32_t) g) << 8) | ((uint32_t) b) + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Create a window that is used to display the buffer sent into the mfb_update function, returns 0 if fails +struct mfb_window * mfb_open(const char *title, unsigned width, unsigned height); +struct mfb_window * mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags); + +// Update the display +// Input buffer is assumed to be a 32-bit buffer of the size given in the open call +// Will return a negative status if something went wrong or the user want to exit +// Also updates the window events +mfb_update_state mfb_update(struct mfb_window *window, void *buffer); + +mfb_update_state mfb_update_ex(struct mfb_window *window, void *buffer, unsigned width, unsigned height); + +// Only updates the window events +mfb_update_state mfb_update_events(struct mfb_window *window); + +// Close the window +void mfb_close(struct mfb_window *window); + +// Set user data +void mfb_set_user_data(struct mfb_window *window, void *user_data); +void * mfb_get_user_data(struct mfb_window *window); + +// Set viewport (useful when resize) +bool mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height); + +// DPI +// [Deprecated]: Probably a better name will be mfb_get_monitor_scale +void mfb_get_monitor_dpi(struct mfb_window *window, float *dpi_x, float *dpi_y); +// Use this instead +void mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y); + +// Callbacks +void mfb_set_active_callback(struct mfb_window *window, mfb_active_func callback); +void mfb_set_resize_callback(struct mfb_window *window, mfb_resize_func callback); +void mfb_set_keyboard_callback(struct mfb_window *window, mfb_keyboard_func callback); +void mfb_set_char_input_callback(struct mfb_window *window, mfb_char_input_func callback); +void mfb_set_mouse_button_callback(struct mfb_window *window, mfb_mouse_button_func callback); +void mfb_set_mouse_move_callback(struct mfb_window *window, mfb_mouse_move_func callback); +void mfb_set_mouse_scroll_callback(struct mfb_window *window, mfb_mouse_scroll_func callback); + +// Getters +const char * mfb_get_key_name(mfb_key key); + +bool mfb_is_window_active(struct mfb_window *window); +unsigned mfb_get_window_width(struct mfb_window *window); +unsigned mfb_get_window_height(struct mfb_window *window); +int mfb_get_mouse_x(struct mfb_window *window); // Last mouse pos X +int mfb_get_mouse_y(struct mfb_window *window); // Last mouse pos Y +float mfb_get_mouse_scroll_x(struct mfb_window *window); // Mouse wheel X as a sum. When you call this function it resets. +float mfb_get_mouse_scroll_y(struct mfb_window *window); // Mouse wheel Y as a sum. When you call this function it resets. +const uint8_t * mfb_get_mouse_button_buffer(struct mfb_window *window); // One byte for every button. Press (1), Release 0. (up to 8 buttons) +const uint8_t * mfb_get_key_buffer(struct mfb_window *window); // One byte for every key. Press (1), Release 0. + +// FPS +void mfb_set_target_fps(uint32_t fps); +unsigned mfb_get_target_fps(); +bool mfb_wait_sync(struct mfb_window *window); + +// Timer +struct mfb_timer * mfb_timer_create(void); +void mfb_timer_destroy(struct mfb_timer *tmr); +void mfb_timer_reset(struct mfb_timer *tmr); +double mfb_timer_now(struct mfb_timer *tmr); +double mfb_timer_delta(struct mfb_timer *tmr); +double mfb_timer_get_frequency(void); +double mfb_timer_get_resolution(void); + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} + +#include "MiniFB_cpp.h" +#endif + +#endif diff --git a/lib/minifb/upstream/include/MiniFB_cpp.h b/lib/minifb/upstream/include/MiniFB_cpp.h new file mode 100644 index 0000000..8fa32ea --- /dev/null +++ b/lib/minifb/upstream/include/MiniFB_cpp.h @@ -0,0 +1,136 @@ +#pragma once + +#if defined(__cplusplus) + +#include +#include "MiniFB.h" + +template +void mfb_set_active_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, bool)); + +template +void mfb_set_resize_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, int, int)); + +template +void mfb_set_keyboard_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, mfb_key, mfb_key_mod, bool)); + +template +void mfb_set_char_input_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, unsigned int)); + +template +void mfb_set_mouse_button_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, mfb_mouse_button, mfb_key_mod, bool)); + +template +void mfb_set_mouse_move_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, int, int)); + +template +void mfb_set_mouse_scroll_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, mfb_key_mod, float, float)); + +//------------------------------------- +// To avoid clumsy hands +//------------------------------------- +class mfb_stub { + mfb_stub() : m_window(0x0) {} + + template + friend void mfb_set_active_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, bool)); + template + friend void mfb_set_resize_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, int, int)); + template + friend void mfb_set_mouse_button_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, mfb_mouse_button, mfb_key_mod, bool)); + template + friend void mfb_set_keyboard_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, mfb_key, mfb_key_mod, bool)); + template + friend void mfb_set_char_input_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, unsigned int)); + template + friend void mfb_set_mouse_button_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, mfb_mouse_button, mfb_key_mod, bool)); + template + friend void mfb_set_mouse_move_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, int, int)); + template + friend void mfb_set_mouse_scroll_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *, mfb_key_mod, float, float)); + + static mfb_stub *GetInstance(struct mfb_window *window); + + static void active_stub(struct mfb_window *window, bool isActive); + static void resize_stub(struct mfb_window *window, int width, int height); + static void keyboard_stub(struct mfb_window *window, mfb_key key, mfb_key_mod mod, bool isPressed); + static void char_input_stub(struct mfb_window *window, unsigned int); + static void mouse_btn_stub(struct mfb_window *window, mfb_mouse_button button, mfb_key_mod mod, bool isPressed); + static void mouse_move_stub(struct mfb_window *window, int x, int y); + static void scroll_stub(struct mfb_window *window, mfb_key_mod mod, float deltaX, float deltaY); + + struct mfb_window *m_window; + std::function m_active; + std::function m_resize; + std::function m_keyboard; + std::function m_char_input; + std::function m_mouse_btn; + std::function m_mouse_move; + std::function m_scroll; +}; + +//------------------------------------- +template +inline void mfb_set_active_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *window, bool)) { + using namespace std::placeholders; + + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_active = std::bind(method, obj, _1, _2); + mfb_set_active_callback(window, mfb_stub::active_stub); +} + +template +inline void mfb_set_resize_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *window, int, int)) { + using namespace std::placeholders; + + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_resize = std::bind(method, obj, _1, _2, _3); + mfb_set_resize_callback(window, mfb_stub::resize_stub); +} + +template +inline void mfb_set_keyboard_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *window, mfb_key, mfb_key_mod, bool)) { + using namespace std::placeholders; + + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_keyboard = std::bind(method, obj, _1, _2, _3, _4); + mfb_set_keyboard_callback(window, mfb_stub::keyboard_stub); +} + +template +inline void mfb_set_char_input_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *window, unsigned int)) { + using namespace std::placeholders; + + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_char_input = std::bind(method, obj, _1, _2); + mfb_set_char_input_callback(window, mfb_stub::char_input_stub); +} + +template +inline void mfb_set_mouse_button_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *window, mfb_mouse_button, mfb_key_mod, bool)) { + using namespace std::placeholders; + + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_mouse_btn = std::bind(method, obj, _1, _2, _3, _4); + mfb_set_mouse_button_callback(window, mfb_stub::mouse_btn_stub); +} + +template +inline void mfb_set_mouse_move_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *window, int, int)) { + using namespace std::placeholders; + + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_mouse_move = std::bind(method, obj, _1, _2, _3); + mfb_set_mouse_move_callback(window, mfb_stub::mouse_move_stub); +} + +template +inline void mfb_set_mouse_scroll_callback(struct mfb_window *window, T *obj, void (T::*method)(struct mfb_window *window, mfb_key_mod, float, float)) { + using namespace std::placeholders; + + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_scroll = std::bind(method, obj, _1, _2, _3, _4); + mfb_set_mouse_scroll_callback(window, mfb_stub::scroll_stub); +} + +#endif diff --git a/lib/minifb/upstream/include/MiniFB_enums.h b/lib/minifb/upstream/include/MiniFB_enums.h new file mode 100644 index 0000000..d3dca1a --- /dev/null +++ b/lib/minifb/upstream/include/MiniFB_enums.h @@ -0,0 +1,185 @@ +#pragma once + +#include +#include + +// Enums +typedef enum { + STATE_OK = 0, + STATE_EXIT = -1, + STATE_INVALID_WINDOW = -2, + STATE_INVALID_BUFFER = -3, + STATE_INTERNAL_ERROR = -4, +} mfb_update_state; + +typedef enum { + MOUSE_BTN_0, // No mouse button + MOUSE_BTN_1, + MOUSE_BTN_2, + MOUSE_BTN_3, + MOUSE_BTN_4, + MOUSE_BTN_5, + MOUSE_BTN_6, + MOUSE_BTN_7, +} mfb_mouse_button; +#define MOUSE_LEFT MOUSE_BTN_1 +#define MOUSE_RIGHT MOUSE_BTN_2 +#define MOUSE_MIDDLE MOUSE_BTN_3 + +typedef enum { + KB_KEY_UNKNOWN = -1, + + KB_KEY_SPACE = 32, + KB_KEY_APOSTROPHE = 39, + KB_KEY_COMMA = 44, + KB_KEY_MINUS = 45, + KB_KEY_PERIOD = 46, + KB_KEY_SLASH = 47, + KB_KEY_0 = 48, + KB_KEY_1 = 49, + KB_KEY_2 = 50, + KB_KEY_3 = 51, + KB_KEY_4 = 52, + KB_KEY_5 = 53, + KB_KEY_6 = 54, + KB_KEY_7 = 55, + KB_KEY_8 = 56, + KB_KEY_9 = 57, + KB_KEY_SEMICOLON = 59, + KB_KEY_EQUAL = 61, + KB_KEY_A = 65, + KB_KEY_B = 66, + KB_KEY_C = 67, + KB_KEY_D = 68, + KB_KEY_E = 69, + KB_KEY_F = 70, + KB_KEY_G = 71, + KB_KEY_H = 72, + KB_KEY_I = 73, + KB_KEY_J = 74, + KB_KEY_K = 75, + KB_KEY_L = 76, + KB_KEY_M = 77, + KB_KEY_N = 78, + KB_KEY_O = 79, + KB_KEY_P = 80, + KB_KEY_Q = 81, + KB_KEY_R = 82, + KB_KEY_S = 83, + KB_KEY_T = 84, + KB_KEY_U = 85, + KB_KEY_V = 86, + KB_KEY_W = 87, + KB_KEY_X = 88, + KB_KEY_Y = 89, + KB_KEY_Z = 90, + KB_KEY_LEFT_BRACKET = 91, + KB_KEY_BACKSLASH = 92, + KB_KEY_RIGHT_BRACKET = 93, + KB_KEY_GRAVE_ACCENT = 96, + KB_KEY_WORLD_1 = 161, + KB_KEY_WORLD_2 = 162, + + KB_KEY_ESCAPE = 256, + KB_KEY_ENTER = 257, + KB_KEY_TAB = 258, + KB_KEY_BACKSPACE = 259, + KB_KEY_INSERT = 260, + KB_KEY_DELETE = 261, + KB_KEY_RIGHT = 262, + KB_KEY_LEFT = 263, + KB_KEY_DOWN = 264, + KB_KEY_UP = 265, + KB_KEY_PAGE_UP = 266, + KB_KEY_PAGE_DOWN = 267, + KB_KEY_HOME = 268, + KB_KEY_END = 269, + KB_KEY_CAPS_LOCK = 280, + KB_KEY_SCROLL_LOCK = 281, + KB_KEY_NUM_LOCK = 282, + KB_KEY_PRINT_SCREEN = 283, + KB_KEY_PAUSE = 284, + KB_KEY_F1 = 290, + KB_KEY_F2 = 291, + KB_KEY_F3 = 292, + KB_KEY_F4 = 293, + KB_KEY_F5 = 294, + KB_KEY_F6 = 295, + KB_KEY_F7 = 296, + KB_KEY_F8 = 297, + KB_KEY_F9 = 298, + KB_KEY_F10 = 299, + KB_KEY_F11 = 300, + KB_KEY_F12 = 301, + KB_KEY_F13 = 302, + KB_KEY_F14 = 303, + KB_KEY_F15 = 304, + KB_KEY_F16 = 305, + KB_KEY_F17 = 306, + KB_KEY_F18 = 307, + KB_KEY_F19 = 308, + KB_KEY_F20 = 309, + KB_KEY_F21 = 310, + KB_KEY_F22 = 311, + KB_KEY_F23 = 312, + KB_KEY_F24 = 313, + KB_KEY_F25 = 314, + KB_KEY_KP_0 = 320, + KB_KEY_KP_1 = 321, + KB_KEY_KP_2 = 322, + KB_KEY_KP_3 = 323, + KB_KEY_KP_4 = 324, + KB_KEY_KP_5 = 325, + KB_KEY_KP_6 = 326, + KB_KEY_KP_7 = 327, + KB_KEY_KP_8 = 328, + KB_KEY_KP_9 = 329, + KB_KEY_KP_DECIMAL = 330, + KB_KEY_KP_DIVIDE = 331, + KB_KEY_KP_MULTIPLY = 332, + KB_KEY_KP_SUBTRACT = 333, + KB_KEY_KP_ADD = 334, + KB_KEY_KP_ENTER = 335, + KB_KEY_KP_EQUAL = 336, + KB_KEY_LEFT_SHIFT = 340, + KB_KEY_LEFT_CONTROL = 341, + KB_KEY_LEFT_ALT = 342, + KB_KEY_LEFT_SUPER = 343, + KB_KEY_RIGHT_SHIFT = 344, + KB_KEY_RIGHT_CONTROL = 345, + KB_KEY_RIGHT_ALT = 346, + KB_KEY_RIGHT_SUPER = 347, + KB_KEY_MENU = 348 +} mfb_key; +#define KB_KEY_LAST KB_KEY_MENU + +typedef enum { + KB_MOD_SHIFT = 0x0001, + KB_MOD_CONTROL = 0x0002, + KB_MOD_ALT = 0x0004, + KB_MOD_SUPER = 0x0008, + KB_MOD_CAPS_LOCK = 0x0010, + KB_MOD_NUM_LOCK = 0x0020 +} mfb_key_mod; + +typedef enum { + WF_RESIZABLE = 0x01, + WF_FULLSCREEN = 0x02, + WF_FULLSCREEN_DESKTOP = 0x04, + WF_BORDERLESS = 0x08, + WF_ALWAYS_ON_TOP = 0x10, +} mfb_window_flags; + +// Opaque pointer +struct mfb_window; +struct mfb_timer; + +// Event callbacks +typedef void(*mfb_active_func)(struct mfb_window *window, bool isActive); +typedef void(*mfb_resize_func)(struct mfb_window *window, int width, int height); +typedef void(*mfb_keyboard_func)(struct mfb_window *window, mfb_key key, mfb_key_mod mod, bool isPressed); +typedef void(*mfb_char_input_func)(struct mfb_window *window, unsigned int code); +typedef void(*mfb_mouse_button_func)(struct mfb_window *window, mfb_mouse_button button, mfb_key_mod mod, bool isPressed); +typedef void(*mfb_mouse_move_func)(struct mfb_window *window, int x, int y); +typedef void(*mfb_mouse_scroll_func)(struct mfb_window *window, mfb_key_mod mod, float deltaX, float deltaY); + diff --git a/lib/minifb/upstream/include/MiniFB_ios.h b/lib/minifb/upstream/include/MiniFB_ios.h new file mode 100644 index 0000000..5975c5d --- /dev/null +++ b/lib/minifb/upstream/include/MiniFB_ios.h @@ -0,0 +1,7 @@ +#pragma once + +#include "MiniFB_enums.h" + +void user_implemented_init(struct mfb_window *window); + +void user_implemented_update(struct mfb_window *window); diff --git a/lib/minifb/upstream/src/MiniFB_common.c b/lib/minifb/upstream/src/MiniFB_common.c new file mode 100644 index 0000000..f6c87e4 --- /dev/null +++ b/lib/minifb/upstream/src/MiniFB_common.c @@ -0,0 +1,598 @@ +#include +#include "WindowData.h" +#include "MiniFB_internal.h" + +//------------------------------------- +short int g_keycodes[512] = { 0 }; +//------------------------------------- + +//------------------------------------- +struct mfb_window * +mfb_open(const char *title, unsigned width, unsigned height) { + return mfb_open_ex(title, width, height, 0); +} + +//------------------------------------- +mfb_update_state +mfb_update(struct mfb_window *window, void *buffer) { + if (window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + + return mfb_update_ex(window, buffer, window_data->buffer_width, window_data->buffer_height); +} + +//------------------------------------- +void +mfb_set_active_callback(struct mfb_window *window, mfb_active_func callback) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->active_func = callback; + } +} + +//------------------------------------- +void +mfb_set_resize_callback(struct mfb_window *window, mfb_resize_func callback) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->resize_func = callback; + } +} + +//------------------------------------- +void +mfb_set_keyboard_callback(struct mfb_window *window, mfb_keyboard_func callback) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->keyboard_func = callback; + } +} + +//------------------------------------- +void +mfb_set_char_input_callback(struct mfb_window *window, mfb_char_input_func callback) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->char_input_func = callback; + } +} + +//------------------------------------- +void +mfb_set_mouse_button_callback(struct mfb_window *window, mfb_mouse_button_func callback) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->mouse_btn_func = callback; + } +} + +//------------------------------------- +void +mfb_set_mouse_move_callback(struct mfb_window *window, mfb_mouse_move_func callback) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->mouse_move_func = callback; + } +} + +//------------------------------------- +void +mfb_set_mouse_scroll_callback(struct mfb_window *window, mfb_mouse_scroll_func callback) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->mouse_wheel_func = callback; + } +} + +//------------------------------------- +void +mfb_set_user_data(struct mfb_window *window, void *user_data) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->user_data = user_data; + } +} + +//------------------------------------- +void * +mfb_get_user_data(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + return window_data->user_data; + } + + return 0x0; +} + +// [Deprecated] +//------------------------------------- +void +mfb_get_monitor_dpi(struct mfb_window *window, float *dpi_x, float *dpi_y) { + mfb_get_monitor_scale(window, dpi_x, dpi_y); +} + +//------------------------------------- +void +mfb_close(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->close = true; + } +} + +//------------------------------------- +void +keyboard_default(struct mfb_window *window, mfb_key key, mfb_key_mod mod, bool isPressed) { + kUnused(mod); + kUnused(isPressed); + if (key == KB_KEY_ESCAPE) { + SWindowData *window_data = (SWindowData *) window; + window_data->close = true; + } +} + +//------------------------------------- +bool +mfb_is_window_active(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + return window_data->is_active; + } + return false; +} + +//------------------------------------- +unsigned +mfb_get_window_width(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + return window_data->window_width; + } + return 0; +} + +//------------------------------------- +unsigned +mfb_get_window_height(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + return window_data->window_height; + } + return 0; +} + +//------------------------------------- +int +mfb_get_mouse_x(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + return window_data->mouse_pos_x; + } + return 0; +} + +//------------------------------------- +int +mfb_get_mouse_y(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + return window_data->mouse_pos_y; + } + return 0; +} + +//------------------------------------- +float +mfb_get_mouse_scroll_x(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + return window_data->mouse_wheel_x; + } + return 0; +} + +//------------------------------------- +float +mfb_get_mouse_scroll_y(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + return window_data->mouse_wheel_y; + } + return 0; +} + +//------------------------------------- +const uint8_t * +mfb_get_mouse_button_buffer(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + return window_data->mouse_button_status; + } + return 0; +} + +//------------------------------------- +const uint8_t * +mfb_get_key_buffer(struct mfb_window *window) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + return window_data->key_status; + } + return 0; +} + +//------------------------------------- +const char * +mfb_get_key_name(mfb_key key) { + + switch (key) + { + case KB_KEY_SPACE: + return "Space"; + + case KB_KEY_APOSTROPHE: + return "Apostrophe"; + + case KB_KEY_COMMA: + return "Comma"; + + case KB_KEY_MINUS: + return "Minus"; + + case KB_KEY_PERIOD: + return "Period"; + + case KB_KEY_SLASH: + return "Slash"; + + case KB_KEY_0: + return "0"; + + case KB_KEY_1: + return "1"; + + case KB_KEY_2: + return "2"; + + case KB_KEY_3: + return "3"; + + case KB_KEY_4: + return "4"; + + case KB_KEY_5: + return "5"; + + case KB_KEY_6: + return "6"; + + case KB_KEY_7: + return "7"; + + case KB_KEY_8: + return "8"; + + case KB_KEY_9: + return "9"; + + case KB_KEY_SEMICOLON: + return "Semicolon"; + + case KB_KEY_EQUAL: + return "Equal"; + + case KB_KEY_A: + return "A"; + + case KB_KEY_B: + return "B"; + + case KB_KEY_C: + return "C"; + + case KB_KEY_D: + return "D"; + + case KB_KEY_E: + return "E"; + + case KB_KEY_F: + return "F"; + + case KB_KEY_G: + return "G"; + + case KB_KEY_H: + return "H"; + + case KB_KEY_I: + return "I"; + + case KB_KEY_J: + return "J"; + + case KB_KEY_K: + return "K"; + + case KB_KEY_L: + return "L"; + + case KB_KEY_M: + return "M"; + + case KB_KEY_N: + return "N"; + + case KB_KEY_O: + return "O"; + + case KB_KEY_P: + return "P"; + + case KB_KEY_Q: + return "Q"; + + case KB_KEY_R: + return "R"; + + case KB_KEY_S: + return "S"; + + case KB_KEY_T: + return "T"; + + case KB_KEY_U: + return "U"; + + case KB_KEY_V: + return "V"; + + case KB_KEY_W: + return "W"; + + case KB_KEY_X: + return "X"; + + case KB_KEY_Y: + return "Y"; + + case KB_KEY_Z: + return "Z"; + + case KB_KEY_LEFT_BRACKET: + return "Left_Bracket"; + + case KB_KEY_BACKSLASH: + return "Backslash"; + + case KB_KEY_RIGHT_BRACKET: + return "Right_Bracket"; + + case KB_KEY_GRAVE_ACCENT: + return "Grave_Accent"; + + case KB_KEY_WORLD_1: + return "World_1"; + + case KB_KEY_WORLD_2: + return "World_2"; + + case KB_KEY_ESCAPE: + return "Escape"; + + case KB_KEY_ENTER: + return "Enter"; + + case KB_KEY_TAB: + return "Tab"; + + case KB_KEY_BACKSPACE: + return "Backspace"; + + case KB_KEY_INSERT: + return "Insert"; + + case KB_KEY_DELETE: + return "Delete"; + + case KB_KEY_RIGHT: + return "Right"; + + case KB_KEY_LEFT: + return "Left"; + + case KB_KEY_DOWN: + return "Down"; + + case KB_KEY_UP: + return "Up"; + + case KB_KEY_PAGE_UP: + return "Page_Up"; + + case KB_KEY_PAGE_DOWN: + return "Page_Down"; + + case KB_KEY_HOME: + return "Home"; + + case KB_KEY_END: + return "End"; + + case KB_KEY_CAPS_LOCK: + return "Caps_Lock"; + + case KB_KEY_SCROLL_LOCK: + return "Scroll_Lock"; + + case KB_KEY_NUM_LOCK: + return "Num_Lock"; + + case KB_KEY_PRINT_SCREEN: + return "Print_Screen"; + + case KB_KEY_PAUSE: + return "Pause"; + + case KB_KEY_F1: + return "F1"; + + case KB_KEY_F2: + return "F2"; + + case KB_KEY_F3: + return "F3"; + + case KB_KEY_F4: + return "F4"; + + case KB_KEY_F5: + return "F5"; + + case KB_KEY_F6: + return "F6"; + + case KB_KEY_F7: + return "F7"; + + case KB_KEY_F8: + return "F8"; + + case KB_KEY_F9: + return "F9"; + + case KB_KEY_F10: + return "F10"; + + case KB_KEY_F11: + return "F11"; + + case KB_KEY_F12: + return "F12"; + + case KB_KEY_F13: + return "F13"; + + case KB_KEY_F14: + return "F14"; + + case KB_KEY_F15: + return "F15"; + + case KB_KEY_F16: + return "F16"; + + case KB_KEY_F17: + return "F17"; + + case KB_KEY_F18: + return "F18"; + + case KB_KEY_F19: + return "F19"; + + case KB_KEY_F20: + return "F20"; + + case KB_KEY_F21: + return "F21"; + + case KB_KEY_F22: + return "F22"; + + case KB_KEY_F23: + return "F23"; + + case KB_KEY_F24: + return "F24"; + + case KB_KEY_F25: + return "F25"; + + case KB_KEY_KP_0: + return "KP_0"; + + case KB_KEY_KP_1: + return "KP_1"; + + case KB_KEY_KP_2: + return "KP_2"; + + case KB_KEY_KP_3: + return "KP_3"; + + case KB_KEY_KP_4: + return "KP_4"; + + case KB_KEY_KP_5: + return "KP_5"; + + case KB_KEY_KP_6: + return "KP_6"; + + case KB_KEY_KP_7: + return "KP_7"; + + case KB_KEY_KP_8: + return "KP_8"; + + case KB_KEY_KP_9: + return "KP_9"; + + case KB_KEY_KP_DECIMAL: + return "KP_Decimal"; + + case KB_KEY_KP_DIVIDE: + return "KP_Divide"; + + case KB_KEY_KP_MULTIPLY: + return "KP_Multiply"; + + case KB_KEY_KP_SUBTRACT: + return "KP_Subtract"; + + case KB_KEY_KP_ADD: + return "KP_Add"; + + case KB_KEY_KP_ENTER: + return "KP_Enter"; + + case KB_KEY_KP_EQUAL: + return "KP_Equal"; + + case KB_KEY_LEFT_SHIFT: + return "Left_Shift"; + + case KB_KEY_LEFT_CONTROL: + return "Left_Control"; + + case KB_KEY_LEFT_ALT: + return "Left_Alt"; + + case KB_KEY_LEFT_SUPER: + return "Left_Super"; + + case KB_KEY_RIGHT_SHIFT: + return "Right_Shift"; + + case KB_KEY_RIGHT_CONTROL: + return "Right_Control"; + + case KB_KEY_RIGHT_ALT: + return "Right_Alt"; + + case KB_KEY_RIGHT_SUPER: + return "Right_Super"; + + case KB_KEY_MENU: + return "Menu"; + + case KB_KEY_UNKNOWN: + return "Unknown"; + } + + return "Unknown"; +} diff --git a/lib/minifb/upstream/src/MiniFB_cpp.cpp b/lib/minifb/upstream/src/MiniFB_cpp.cpp new file mode 100644 index 0000000..b513803 --- /dev/null +++ b/lib/minifb/upstream/src/MiniFB_cpp.cpp @@ -0,0 +1,69 @@ +#include +#include +#include + +//------------------------------------- +mfb_stub * +mfb_stub::GetInstance(struct mfb_window *window) { + static std::vector s_instances; + + for(mfb_stub *instance : s_instances) { + if(instance->m_window == window) { + return instance; + } + } + + s_instances.push_back(new mfb_stub); + s_instances.back()->m_window = window; + + return s_instances.back(); +} + +//------------------------------------- +void +mfb_stub::active_stub(struct mfb_window *window, bool isActive) { + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_active(window, isActive); +} + +//------------------------------------- +void +mfb_stub::resize_stub(struct mfb_window *window, int width, int height) { + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_resize(window, width, height); +} + +//------------------------------------- +void +mfb_stub::keyboard_stub(struct mfb_window *window, mfb_key key, mfb_key_mod mod, bool isPressed) { + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_keyboard(window, key, mod, isPressed); +} + +//------------------------------------- +void +mfb_stub::char_input_stub(struct mfb_window *window, unsigned int code) { + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_char_input(window, code); +} + +//------------------------------------- +void +mfb_stub::mouse_btn_stub(struct mfb_window *window, mfb_mouse_button button, mfb_key_mod mod, bool isPressed) { + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_mouse_btn(window, button, mod, isPressed); +} + +//------------------------------------- +void +mfb_stub::mouse_move_stub(struct mfb_window *window, int x, int y) { + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_mouse_move(window, x, y); +} + +//------------------------------------- +void +mfb_stub::scroll_stub(struct mfb_window *window, mfb_key_mod mod, float deltaX, float deltaY) { + mfb_stub *stub = mfb_stub::GetInstance(window); + stub->m_scroll(window, mod, deltaX, deltaY); +} diff --git a/lib/minifb/upstream/src/MiniFB_internal.c b/lib/minifb/upstream/src/MiniFB_internal.c new file mode 100644 index 0000000..a34a734 --- /dev/null +++ b/lib/minifb/upstream/src/MiniFB_internal.c @@ -0,0 +1,113 @@ +#include "MiniFB_internal.h" +#include + +//#define kUseBilinearInterpolation + +#if defined(kUseBilinearInterpolation) +//------------------------------------- +static uint32_t +interpolate(uint32_t *srcImage, uint32_t x, uint32_t y, uint32_t srcOffsetX, uint32_t srcOffsetY, uint32_t srcWidth, uint32_t srcHeight, uint32_t srcPitch) { + uint32_t incX = x + 1 < srcWidth ? 1 : 0; + uint32_t incY = y + 1 < srcHeight ? srcPitch : 0; + uint8_t *p00 = (uint8_t *) &srcImage[(srcOffsetX >> 16)]; + uint8_t *p01 = (uint8_t *) &srcImage[(srcOffsetX >> 16) + incX]; + uint8_t *p10 = (uint8_t *) &srcImage[(srcOffsetX >> 16) + incY]; + uint8_t *p11 = (uint8_t *) &srcImage[(srcOffsetX >> 16) + incY + incX]; + + uint32_t wx2 = srcOffsetX & 0xffff; + uint32_t wy2 = srcOffsetY & 0xffff; + uint32_t wx1 = 0x10000 - wx2; + uint32_t wy1 = 0x10000 - wy2; + + uint32_t w1 = ((uint64_t) wx1 * wy1) >> 16; + uint32_t w2 = ((uint64_t) wx2 * wy1) >> 16; + uint32_t w3 = ((uint64_t) wx1 * wy2) >> 16; + uint32_t w4 = ((uint64_t) wx2 * wy2) >> 16; + + // If you don't have uint64_t + //uint32_t b = (((p00[0] * wx1 + p01[0] * wx2) >> 16) * wy1 + ((p10[0] * wx1 + p11[0] * wx2) >> 16) * wy2) >> 16; + //uint32_t g = (((p00[1] * wx1 + p01[1] * wx2) >> 16) * wy1 + ((p10[1] * wx1 + p11[1] * wx2) >> 16) * wy2) >> 16; + //uint32_t r = (((p00[2] * wx1 + p01[2] * wx2) >> 16) * wy1 + ((p10[2] * wx1 + p11[2] * wx2) >> 16) * wy2) >> 16; + //uint32_t a = (((p00[3] * wx1 + p01[3] * wx2) >> 16) * wy1 + ((p10[3] * wx1 + p11[3] * wx2) >> 16) * wy2) >> 16; + + uint32_t b = ((p00[0] * w1 + p01[0] * w2) + (p10[0] * w3 + p11[0] * w4)) >> 16; + uint32_t g = ((p00[1] * w1 + p01[1] * w2) + (p10[1] * w3 + p11[1] * w4)) >> 16; + uint32_t r = ((p00[2] * w1 + p01[2] * w2) + (p10[2] * w3 + p11[2] * w4)) >> 16; + uint32_t a = ((p00[3] * w1 + p01[3] * w2) + (p10[3] * w3 + p11[3] * w4)) >> 16; + + return (a << 24) + (r << 16) + (g << 8) + b; +} +#endif + +// Only for 32 bits images +//------------------------------------- +void +stretch_image(uint32_t *srcImage, uint32_t srcX, uint32_t srcY, uint32_t srcWidth, uint32_t srcHeight, uint32_t srcPitch, + uint32_t *dstImage, uint32_t dstX, uint32_t dstY, uint32_t dstWidth, uint32_t dstHeight, uint32_t dstPitch) { + + uint32_t x, y; + uint32_t srcOffsetX, srcOffsetY; + + if(srcImage == 0x0 || dstImage == 0x0) + return; + + srcImage += srcX + srcY * srcPitch; + dstImage += dstX + dstY * dstPitch; + + const uint32_t deltaX = (srcWidth << 16) / dstWidth; + const uint32_t deltaY = (srcHeight << 16) / dstHeight; + + srcOffsetY = 0; + for(y=0; y> 16]; +#endif + srcOffsetX += deltaX; + } + + srcOffsetY += deltaY; + if(srcOffsetY >= 0x10000) { + srcImage += (srcOffsetY >> 16) * srcPitch; + srcOffsetY &= 0xffff; + } + dstImage += dstPitch; + } +} + +//------------------------------------- +void +calc_dst_factor(SWindowData *window_data, uint32_t width, uint32_t height) { + if (window_data->dst_width == 0) { + window_data->dst_width = width; + } + window_data->factor_x = (float) window_data->dst_offset_x / (float) width; + window_data->factor_width = (float) window_data->dst_width / (float) width; + + if (window_data->dst_height == 0) { + window_data->dst_height = height; + } + window_data->factor_y = (float) window_data->dst_offset_y / (float) height; + window_data->factor_height = (float) window_data->dst_height / (float) height; +} + +//------------------------------------- +void +resize_dst(SWindowData *window_data, uint32_t width, uint32_t height) { + window_data->dst_offset_x = (uint32_t) (width * window_data->factor_x); + window_data->dst_offset_y = (uint32_t) (height * window_data->factor_y); + window_data->dst_width = (uint32_t) (width * window_data->factor_width); + window_data->dst_height = (uint32_t) (height * window_data->factor_height); +} + +#if !defined(USE_OPENGL_API) && !defined(USE_METAL_API) + +//------------------------------------- +void +set_target_fps_aux() { +} + +#endif diff --git a/lib/minifb/upstream/src/MiniFB_internal.h b/lib/minifb/upstream/src/MiniFB_internal.h new file mode 100644 index 0000000..3d73fe4 --- /dev/null +++ b/lib/minifb/upstream/src/MiniFB_internal.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include "WindowData.h" + +#define kCall(func, ...) if(window_data && window_data->func) window_data->func((struct mfb_window *) window_data, __VA_ARGS__); +#define kUnused(var) (void) var; + +typedef struct mfb_timer { + int64_t start_time; + int64_t delta_counter; + uint64_t time; +} mfb_timer; + +#if defined(__cplusplus) +extern "C" { +#endif + extern short int g_keycodes[512]; + void keyboard_default(struct mfb_window *window, mfb_key key, mfb_key_mod mod, bool isPressed); + + void calc_dst_factor(SWindowData *window_data, uint32_t width, uint32_t height); + void resize_dst(SWindowData *window_data, uint32_t width, uint32_t height); + void set_target_fps_aux(); + +#if defined(__cplusplus) +} +#endif diff --git a/lib/minifb/upstream/src/MiniFB_linux.c b/lib/minifb/upstream/src/MiniFB_linux.c new file mode 100644 index 0000000..5d6611d --- /dev/null +++ b/lib/minifb/upstream/src/MiniFB_linux.c @@ -0,0 +1,37 @@ +#if defined(__linux__) + +#include +#include + +extern double g_timer_frequency; +extern double g_timer_resolution; + +#define kClock CLOCK_MONOTONIC +//#define kClock CLOCK_REALTIME + +uint64_t +mfb_timer_tick() { + struct timespec time; + + if (clock_gettime(kClock, &time) != 0) { + return 0.0; + } + + return time.tv_sec * 1e+9 + time.tv_nsec; +} + +void +mfb_timer_init() { + struct timespec res; + + if (clock_getres(kClock, &res) != 0) { + g_timer_frequency = 1e+9; + } + else { + g_timer_frequency = res.tv_sec + res.tv_nsec * 1e+9; + } + g_timer_resolution = 1.0 / g_timer_frequency; +} + +#endif + diff --git a/lib/minifb/upstream/src/MiniFB_timer.c b/lib/minifb/upstream/src/MiniFB_timer.c new file mode 100644 index 0000000..6a1ca5b --- /dev/null +++ b/lib/minifb/upstream/src/MiniFB_timer.c @@ -0,0 +1,115 @@ +#include +#include "MiniFB_internal.h" +#include + +//------------------------------------- +double g_timer_frequency; +double g_timer_resolution; +double g_time_for_frame = 1.0 / 60.0; +bool g_use_hardware_sync = false; + +//------------------------------------- +extern uint64_t mfb_timer_tick(void); +extern void mfb_timer_init(void); + +//------------------------------------- +void +mfb_set_target_fps(uint32_t fps) { + if(fps == 0) { + g_time_for_frame = 0; + } + else { + g_time_for_frame = 1.0 / fps; + } + set_target_fps_aux(); +} + +//------------------------------------- +unsigned +mfb_get_target_fps() { + if (g_time_for_frame == 0) { + return 0; + } + else { + return 1.0 / g_time_for_frame; + } +} + +//------------------------------------- +struct mfb_timer * +mfb_timer_create() { + static int once = 1; + mfb_timer *tmr; + + if(once) { + once = 0; + mfb_timer_init(); + } + + tmr = malloc(sizeof(mfb_timer)); + mfb_timer_reset(tmr); + + return tmr; +} + +//------------------------------------- +void +mfb_timer_destroy(struct mfb_timer *tmr) { + if(tmr != 0x0) { + free(tmr); + } +} + +//------------------------------------- +void +mfb_timer_reset(struct mfb_timer *tmr) { + if(tmr == 0x0) + return; + + tmr->start_time = mfb_timer_tick(); + tmr->delta_counter = tmr->start_time; + tmr->time = 0; +} + +//------------------------------------- +double +mfb_timer_now(struct mfb_timer *tmr) { + uint64_t counter; + + if(tmr == 0x0) + return 0.0; + + counter = mfb_timer_tick(); + tmr->time += (counter - tmr->start_time); + tmr->start_time = counter; + + return tmr->time * g_timer_resolution; +} + +//------------------------------------- +double +mfb_timer_delta(struct mfb_timer *tmr) { + int64_t counter; + uint64_t delta; + + if(tmr == 0x0) + return 0.0; + + counter = mfb_timer_tick(); + delta = (counter - tmr->delta_counter); + tmr->delta_counter = counter; + + return delta * g_timer_resolution; +} + +//------------------------------------- +double +mfb_timer_get_frequency() { + return g_timer_frequency; +} + +//------------------------------------- +double +mfb_timer_get_resolution() { + return g_timer_resolution; +} diff --git a/lib/minifb/upstream/src/WindowData.h b/lib/minifb/upstream/src/WindowData.h new file mode 100644 index 0000000..13ed2dd --- /dev/null +++ b/lib/minifb/upstream/src/WindowData.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +//------------------------------------- +typedef struct { + void *specific; + void *user_data; + + mfb_active_func active_func; + mfb_resize_func resize_func; + mfb_keyboard_func keyboard_func; + mfb_char_input_func char_input_func; + mfb_mouse_button_func mouse_btn_func; + mfb_mouse_move_func mouse_move_func; + mfb_mouse_scroll_func mouse_wheel_func; + + uint32_t window_width; + uint32_t window_height; + + uint32_t dst_offset_x; + uint32_t dst_offset_y; + uint32_t dst_width; + uint32_t dst_height; + float factor_x; + float factor_y; + float factor_width; + float factor_height; + + void *draw_buffer; + uint32_t buffer_width; + uint32_t buffer_height; + uint32_t buffer_stride; + + int32_t mouse_pos_x; + int32_t mouse_pos_y; + float mouse_wheel_x; + float mouse_wheel_y; + uint8_t mouse_button_status[8]; + uint8_t key_status[512]; + uint32_t mod_keys; + + bool is_active; + bool is_initialized; + + bool close; +} SWindowData; diff --git a/lib/minifb/upstream/src/android/AndroidMiniFB.c b/lib/minifb/upstream/src/android/AndroidMiniFB.c new file mode 100644 index 0000000..f8c0523 --- /dev/null +++ b/lib/minifb/upstream/src/android/AndroidMiniFB.c @@ -0,0 +1,473 @@ +#include +#include +#include +//-- +#include +#include +#include +//-- +#include +#include +#include "WindowData_Android.h" + +#define LOG_TAG "MiniFB" +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) + +#define kCall(func, ...) if(window_data && window_data->func) window_data->func((struct mfb_window *) window_data, __VA_ARGS__); + +#define kUnused(var) (void) var; + +struct android_app *gApplication; + +//------------------------------------- +extern void +stretch_image(uint32_t *srcImage, uint32_t srcX, uint32_t srcY, uint32_t srcWidth, uint32_t srcHeight, uint32_t srcPitch, + uint32_t *dstImage, uint32_t dstX, uint32_t dstY, uint32_t dstWidth, uint32_t dstHeight, uint32_t dstPitch); + +//------------------------------------- +extern int +main(int argc, char *argv[]); + +//------------------------------------- +static void +draw(SWindowData *window_data, ANativeWindow_Buffer *window_buffer) { + if(window_data == 0x0 || window_data->draw_buffer == 0x0 || window_buffer == 0x0) + return; + + if((window_data->buffer_width == window_buffer->width) && (window_data->buffer_height == window_buffer->height)) { + if(window_data->buffer_stride == window_buffer->stride*4) { + memcpy(window_buffer->bits, window_data->draw_buffer, window_data->buffer_width * window_data->buffer_height * 4); + } + else { + uint8_t *src = window_data->draw_buffer; + uint32_t *dst = window_buffer->bits; + for(uint32_t y=0; ywindow_height; ++y) { + memcpy(dst, src, window_data->buffer_width * 4); + src += window_data->buffer_stride; + dst += window_buffer->stride; + } + } + } + else { + uint32_t *src = window_data->draw_buffer; + uint32_t *dst = window_buffer->bits; + stretch_image( + src, 0, 0, window_data->buffer_width, window_data->buffer_height, window_data->buffer_width, + dst, 0, 0, window_buffer->width, window_buffer->height, window_buffer->stride + ); + } +} + +//------------------------------------- +static int32_t +handle_input(struct android_app* app, AInputEvent* event) { + SWindowData *window_data = (SWindowData *) app->userData; + if (window_data->close) { + //destroy_window_data(window_data); + return 0; + } + + SWindowData_Android *window_data_android = (SWindowData_Android *) window_data->specific; + + int t = AInputEvent_getType(event); + int s = AInputEvent_getSource(event); + LOGV("Event: type= %d, source=%d", t, s); + if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { + //if (AInputEvent_getSource(event) == AINPUT_SOURCE_TOUCHSCREEN) { + int action = AMotionEvent_getAction(event); + int type = action & AMOTION_EVENT_ACTION_MASK; + switch(type) { + case AMOTION_EVENT_ACTION_POINTER_DOWN: + case AMOTION_EVENT_ACTION_POINTER_UP: + { + int idx = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + int id = AMotionEvent_getPointerId(event, idx); + int x = AMotionEvent_getX(event, idx); + int y = AMotionEvent_getY(event, idx); + window_data->mouse_pos_x = x | (id << 28); + window_data->mouse_pos_y = y | (id << 28); + window_data->mouse_button_status[id & 0x07] = (action == AMOTION_EVENT_ACTION_POINTER_DOWN); + kCall(mouse_btn_func, id, 0, action == AMOTION_EVENT_ACTION_POINTER_DOWN); + } + break; + + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_UP: + { + int count = AMotionEvent_getPointerCount(event); + for(int i=0; i < count; ++i) { + int id = AMotionEvent_getPointerId(event, i); + int x = AMotionEvent_getX(event, i); + int y = AMotionEvent_getY(event, i); + window_data->mouse_pos_x = x | (id << 28); + window_data->mouse_pos_y = y | (id << 28); + window_data->mouse_button_status[id & 0x07] = (action == AMOTION_EVENT_ACTION_POINTER_DOWN); + kCall(mouse_btn_func, id, 0, action == AMOTION_EVENT_ACTION_DOWN); + } + } + break; + + case AMOTION_EVENT_ACTION_MOVE: + { + int count = AMotionEvent_getPointerCount(event); + for(int i=0; i < count; ++i){ + int id = AMotionEvent_getPointerId(event, i); + int x = AMotionEvent_getX(event, i); + int y = AMotionEvent_getY(event, i); + window_data->mouse_pos_x = x | (id << 28); + window_data->mouse_pos_y = y | (id << 28); + window_data->mouse_button_status[id & 0x07] = true; + kCall(mouse_move_func, window_data->mouse_pos_x, window_data->mouse_pos_y); + } + } + break; + + default: + LOGV("Touch: event: action=%x, source=%x, type=%d", action, s, t); + break; + } + + if(window_data != 0x0) { + window_data->is_active = true; + } + return 1; + } + else + if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) { + LOGV("Key event: action=%d keyCode=%d metaState=0x%x", + AKeyEvent_getAction(event), + AKeyEvent_getKeyCode(event), + AKeyEvent_getMetaState(event)); + } + + return 0; +} + +//------------------------------------- +static void +handle_cmd(struct android_app* app, int32_t cmd) { + static int32_t format = WINDOW_FORMAT_RGBX_8888; + static int sCurrentState = -1; + + sCurrentState = cmd; + + SWindowData *window_data; + SWindowData_Android *window_data_android; + + window_data = (SWindowData *) app->userData; + if(window_data != 0x0) { + window_data_android = (SWindowData_Android *) window_data->specific; + } + + LOGV("cmd: %d", cmd); + // Init: 10, 11, 0, 1, 3, 5, 4, 6 + // START, RESUME, INPUT_CHANGED, INIT_WINDOW, WINDOW_RESIZED, CONTENT_RECT_CHANGED, WINDOW_REDRAW_NEEDED, GAINED_FOCUS + // Pause: 13, 7, 2, 14, 12 + // PAUSE, LOST_FOCUS, TERM_WINDOW, STOP, SAVE_STATE + // Resume: 10, 11, 1, 3, 4, 6 + // START, RESUME, INIT_WINDOW, WINDOW_RESIZED, WINDOW_REDRAW_NEEDED, GAINED_FOCUS + // Close: 0, 15 + // INPUT_CHANGED, DESTROY + // Lower the shutter: 7, 0 + // LOST_FOCUS, INPUT_CHANGED + // Raising the shutter: 6, 1 + // GAINED_FOCUS, INIT_WINDOW + // Rotate: 13, 2, 14, 12, 0, 15, 10, 11, 0, 1, 3, 5, 4, 6, 4 + // PAUSE, TERM_WINDOW, STOP, SAVE_STATE, (similar to Pause but LOST_FOCUS) + // INPUT_CHANGED, DESTROY, (like Close) + // START, RESUME, INPUT_CHANGED, INIT_WINDOW, WINDOW_RESIZED, CONTENT_RECT_CHANGED, WINDOW_REDRAW_NEEDED, GAINED_FOCUS (like Init) + switch (cmd) { + // The app's activity has been started. + case APP_CMD_START: + break; + + // The app's activity has been resumed. + case APP_CMD_RESUME: + break; + + // The AInputQueue has changed. + // Upon processing this command, android_app->inputQueue will be updated to the new queue (or NULL). + case APP_CMD_INPUT_CHANGED: + break; + + // A new ANativeWindow is ready for use. + // Upon receiving this command, android_app->window will contain the new window surface. + case APP_CMD_INIT_WINDOW: + if (app->window != NULL) { + format = ANativeWindow_getFormat(app->window); + ANativeWindow_setBuffersGeometry(app->window, + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window), + format + ); + //engine_draw_frame(window_data_android); + } + break; + + // The current ANativeWindow has been resized. Please redraw with its new size. + case APP_CMD_WINDOW_RESIZED: + break; + + // The content area of the window has changed, such as from the soft input window being shown or hidden. + // You can find the new content rect in android_app::contentRect. + case APP_CMD_CONTENT_RECT_CHANGED: + if(window_data_android != 0x0) { + // This does not work + //int32_t width = window_data_android->app->contentRect.right - window_data_android->app->contentRect.left; + //int32_t height = window_data_android->app->contentRect.bottom - window_data_android->app->contentRect.top; + // TODO: Check the DPI? + if(window_data != 0x0) { + window_data->window_width = ANativeWindow_getWidth(app->window); + window_data->window_height = ANativeWindow_getHeight(app->window); + kCall(resize_func, window_data->window_width, window_data->window_height); + } + } + break; + + // The system needs that the current ANativeWindow be redrawn. + // You should redraw the window before handing this to android_app_exec_cmd() in order to avoid transient drawing glitches. + case APP_CMD_WINDOW_REDRAW_NEEDED: + break; + + // The app's activity window has gained input focus. + case APP_CMD_GAINED_FOCUS: + if(window_data != 0x0) { + window_data->is_active = true; + } + kCall(active_func, true); + break; + + // The app's activity has been paused. + case APP_CMD_PAUSE: + break; + + // The app's activity window has lost input focus. + case APP_CMD_LOST_FOCUS: + if(window_data != 0x0) { + window_data->is_active = true; + //engine_draw_frame(window_data_android); + } + kCall(active_func, false); + break; + + // The existing ANativeWindow needs to be terminated. + // Upon receiving this command, android_app->window still contains the existing window; + // after calling android_app_exec_cmd it will be set to NULL. + case APP_CMD_TERM_WINDOW: + if(window_data != 0x0) { + window_data->is_active = false; + } + ANativeWindow_setBuffersGeometry(app->window, + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window), + format + ); + break; + + // The app's activity has been stopped. + case APP_CMD_STOP: + break; + + // The app should generate a new saved state for itself, to restore from later if needed. + // If you have saved state, allocate it with malloc and place it in android_app.savedState with + // the size in android_app.savedStateSize. + // The will be freed for you later. + case APP_CMD_SAVE_STATE: + break; + + // The app's activity is being destroyed, and waiting for the app thread to clean up and exit before proceeding. + case APP_CMD_DESTROY: + if(window_data != 0x0) { + window_data->close = true; + } + break; + + // The system is running low on memory. Try to reduce your memory use. + case APP_CMD_LOW_MEMORY: + break; + + // The current device configuration has changed. + case APP_CMD_CONFIG_CHANGED: + break; + } +} + +//------------------------------------- +void +android_main(struct android_app* app) { + app->onAppCmd = handle_cmd; + app->onInputEvent = handle_input; + gApplication = app; + + // Read all pending events. + int ident; + int events; + struct android_poll_source* source; + while(app->window == 0x0) { + while ((ident = ALooper_pollAll(0, NULL, &events, (void **) &source)) >= 0) { + // Process this event. + if (source != NULL) { + source->process(app, source); + } + + // Check if we are exiting. + if (app->destroyRequested != 0) { + LOGD("Engine thread destroy requested!"); + return; + } + } + } + + char cwd[1024]; + getcwd(cwd, sizeof(cwd)); + char *argv[] = { + cwd, + (char *) app + }; + main(2, argv); +} + +//------------------------------------- +struct mfb_window * +mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) { + kUnused(title); + kUnused(flags); + + SWindowData *window_data = malloc(sizeof(SWindowData)); + if (window_data == 0x0) { + return 0x0; + } + memset(window_data, 0, sizeof(SWindowData)); + + SWindowData_Android *window_data_android = malloc(sizeof(SWindowData_Android)); + if(window_data_android == 0x0) { + free(window_data); + return 0x0; + } + memset(window_data_android, 0, sizeof(SWindowData_Android)); + window_data->specific = window_data_android; + + window_data->is_active = true; + window_data_android->app = gApplication; + window_data_android->timer = mfb_timer_create(); + + window_data->buffer_width = width; + window_data->buffer_height = height; + window_data->buffer_stride = width * 4; + + gApplication->userData = window_data; + if(gApplication->window != 0x0) { + window_data->window_width = ANativeWindow_getWidth(gApplication->window); + window_data->window_height = ANativeWindow_getHeight(gApplication->window); + } + + window_data->is_initialized = true; + return (struct mfb_window *) window_data; +} + +//------------------------------------- +mfb_update_state +mfb_update_ex(struct mfb_window *window, void *buffer, unsigned width, unsigned height) { + if (window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + if (window_data->close) { + //destroy_window_data(window_data); + return STATE_EXIT; + } + + if (buffer == 0x0) { + return STATE_INVALID_BUFFER; + } + + window_data->draw_buffer = buffer; + window_data->buffer_width = width; + window_data->buffer_stride = width * 4; + window_data->buffer_height = height; + + SWindowData_Android *window_data_android = (SWindowData_Android *) window_data->specific; + + ANativeWindow_Buffer native_buffer; + if (ANativeWindow_lock(window_data_android->app->window, &native_buffer, NULL) < 0) { + LOGE("Unable to lock window buffer"); + return STATE_INTERNAL_ERROR; + } + + draw(window_data, &native_buffer); + + ANativeWindow_unlockAndPost(window_data_android->app->window); + + return STATE_OK; +} + +//------------------------------------- +extern double g_time_for_frame; + +bool +mfb_wait_sync(struct mfb_window *window) { + if (window == 0x0) { + return false; + } + + SWindowData *window_data = (SWindowData *) window; + if (window_data->close) { + //destroy_window_data(window_data); + return false; + } + + SWindowData_Android *window_data_android = (SWindowData_Android *) window_data->specific; + + // Read all pending events. + int ident; + int events; + struct android_poll_source *source; + double current; + + while(1) { + // If not animating, we will block forever waiting for events. + // If animating, we loop until all events are read, then continue + // to draw the next frame of animation. + while ((ident = ALooper_pollAll(window_data->is_active ? 0 : -1, NULL, &events, (void **) &source)) >= 0) { + // Process this event. + if (source != NULL) { + source->process(window_data_android->app, source); + } + + // Check if we are exiting. + if (window_data_android->app->destroyRequested != 0) { + LOGD("Engine thread destroy requested!"); + window_data->is_active = false; + window_data->close = true; + return false; + } + } + + current = mfb_timer_now(window_data_android->timer); + if (current >= g_time_for_frame) { + break; + } + } + mfb_timer_reset(window_data_android->timer); + + return true; +} + +//------------------------------------- +void +mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y) { + kUnused(window); + + if(scale_x != 0x0) { + *scale_x = 1.0f; + } + if(scale_y != 0x0) { + *scale_y = 1.0f; + } +} diff --git a/lib/minifb/upstream/src/android/WindowData_Android.h b/lib/minifb/upstream/src/android/WindowData_Android.h new file mode 100644 index 0000000..a5ec31c --- /dev/null +++ b/lib/minifb/upstream/src/android/WindowData_Android.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +//------------------------------------- +typedef struct { + struct android_app *app; + struct mfb_timer *timer; +} SWindowData_Android; diff --git a/lib/minifb/upstream/src/gl/MiniFB_GL.c b/lib/minifb/upstream/src/gl/MiniFB_GL.c new file mode 100644 index 0000000..4513b2b --- /dev/null +++ b/lib/minifb/upstream/src/gl/MiniFB_GL.c @@ -0,0 +1,368 @@ +#if defined(USE_OPENGL_API) + +#include "MiniFB_GL.h" +#include "MiniFB_internal.h" +#if defined(_WIN32) || defined(WIN32) + #include + #include +#elif defined(linux) + #include + #include + #include +#endif +#include +#include + +//#define kUse_Clean_UP +#if defined(kUse_Clean_UP) + #define UseCleanUp(x) x +#else + #define UseCleanUp(x) +#endif + +extern double g_time_for_frame; +extern bool g_use_hardware_sync; + + +//------------------------------------- +#if defined(_WIN32) || defined(WIN32) +bool +setup_pixel_format(HDC hDC) { + int pixelFormat; + + PIXELFORMATDESCRIPTOR pfd = { + sizeof(PIXELFORMATDESCRIPTOR), // size + 1, // version + PFD_SUPPORT_OPENGL | // + PFD_DRAW_TO_WINDOW | // + PFD_DOUBLEBUFFER, // support double-buffering + PFD_TYPE_RGBA, // color type + 24, // preferred color depth + 0, 0, 0, 0, 0, 0, // color and shift bits (ignored) + 0, // no alpha buffer + 0, // alpha bits (ignored) + 0, // no accumulation buffer + 0, 0, 0, 0, // accum bits (ignored) + 24, // depth buffer + 8, // no stencil buffer + 0, // no auxiliary buffers + PFD_MAIN_PLANE, // main layer + 0, // reserved + 0, 0, 0, // no layer, visible, damage masks + }; + + pixelFormat = ChoosePixelFormat(hDC, &pfd); + if (pixelFormat == 0) { + MessageBox(WindowFromDC(hDC), "ChoosePixelFormat failed.", "Error", MB_ICONERROR | MB_OK); + return false; + } + + if (SetPixelFormat(hDC, pixelFormat, &pfd) != TRUE) { + MessageBox(WindowFromDC(hDC), "SetPixelFormat failed.", "Error", MB_ICONERROR | MB_OK); + return false; + } + + return true; +} + +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); +typedef int (WINAPI * PFNWGLGETSWAPINTERVALEXTPROC)(void); +PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT = 0x0; +PFNWGLGETSWAPINTERVALEXTPROC GetSwapIntervalEXT = 0x0; + +#elif defined(linux) + +bool +setup_pixel_format(SWindowData_X11 *window_data_x11) { + GLint glxAttribs[] = { + GLX_RGBA, + GLX_DOUBLEBUFFER, + GLX_DEPTH_SIZE, 24, + GLX_STENCIL_SIZE, 8, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_DEPTH_SIZE, 24, + GLX_STENCIL_SIZE, 8, + GLX_SAMPLE_BUFFERS, 0, + GLX_SAMPLES, 0, + None + }; + + XVisualInfo* visualInfo = glXChooseVisual(window_data_x11->display, window_data_x11->screen, glxAttribs); + if (visualInfo == 0) { + fprintf(stderr, "Could not create correct visual window.\n"); + XCloseDisplay(window_data_x11->display); + return false; + } + window_data_x11->context = glXCreateContext(window_data_x11->display, visualInfo, NULL, GL_TRUE); + + return true; +} + +typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); +PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT = 0x0; + +#endif + +//------------------------------------- +bool +create_GL_context(SWindowData *window_data) { +#if defined(_WIN32) || defined(WIN32) + SWindowData_Win *window_data_win = (SWindowData_Win *) window_data->specific; + + if (setup_pixel_format(window_data_win->hdc) == false) + return false; + + window_data_win->hGLRC = wglCreateContext(window_data_win->hdc); + wglMakeCurrent(window_data_win->hdc, window_data_win->hGLRC); + init_GL(window_data); + + SwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT"); + GetSwapIntervalEXT = (PFNWGLGETSWAPINTERVALEXTPROC) wglGetProcAddress("wglGetSwapIntervalEXT"); + set_target_fps_aux(); + + return true; + +#elif defined(linux) + SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; + + GLint majorGLX, minorGLX = 0; + glXQueryVersion(window_data_x11->display, &majorGLX, &minorGLX); + if (majorGLX <= 1 && minorGLX < 2) { + fprintf(stderr, "GLX 1.2 or greater is required.\n"); + XCloseDisplay(window_data_x11->display); + return false; + } + else { + //fprintf(stdout, "GLX version: %d.%d\n", majorGLX, minorGLX); + } + + if (setup_pixel_format(window_data_x11) == false) + return false; + + glXMakeCurrent(window_data_x11->display, window_data_x11->window, window_data_x11->context); + + //fprintf(stdout, "GL Vendor: %s\n", glGetString(GL_VENDOR)); + //fprintf(stdout, "GL Renderer: %s\n", glGetString(GL_RENDERER)); + //fprintf(stdout, "GL Version: %s\n", glGetString(GL_VERSION)); + //fprintf(stdout, "GL Shading Language: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); + + init_GL(window_data); + + SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress("glXSwapIntervalEXT"); + set_target_fps_aux(); + + return true; +#endif +} + +//------------------------------------- +void +destroy_GL_context(SWindowData *window_data) { +#if defined(_WIN32) || defined(WIN32) + + SWindowData_Win *window_data_win = (SWindowData_Win *) window_data->specific; + if (window_data_win->hGLRC) { + wglMakeCurrent(NULL, NULL); + wglDeleteContext(window_data_win->hGLRC); + window_data_win->hGLRC = 0; + } + +#elif defined(linux) + + SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; + glXDestroyContext(window_data_x11->display, window_data_x11->context); + +#endif +} + +//------------------------------------- +#if defined(RGB) + #undef RGB +#endif + +#define TEXTURE0 0x84C0 // [ Core in gl 1.3, gles1 1.0, gles2 2.0, glsc2 2.0, Provided by GL_ARB_multitexture (gl) ] +#define RGB 0x1907 // [ Core in gl 1.0, gles1 1.0, gles2 2.0, glsc2 2.0 ] +#define RGBA 0x1908 // [ Core in gl 1.0, gles1 1.0, gles2 2.0, glsc2 2.0 ] +#define BGR 0x80E0 // [ Core in gl 1.2 ] +#define BGRA 0x80E1 // [ Core in gl 1.2, Provided by GL_ARB_vertex_array_bgra (gl|glcore) ] + +//------------------------------------- +void +init_GL(SWindowData *window_data) { +#if defined(_WIN32) || defined(WIN32) + + SWindowData_Win *window_data_ex = (SWindowData_Win *) window_data->specific; + +#elif defined(linux) + + SWindowData_X11 *window_data_ex = (SWindowData_X11 *) window_data->specific; + +#endif + + + glViewport(0, 0, window_data->window_width, window_data->window_height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, window_data->window_width, window_data->window_height, 0, 2048, -2048); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + + glEnable(GL_TEXTURE_2D); + + glGenTextures(1, &window_data_ex->text_id); + //glActiveTexture(TEXTURE0); + glBindTexture(GL_TEXTURE_2D, window_data_ex->text_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + UseCleanUp(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); + UseCleanUp(glDisableClientState(GL_VERTEX_ARRAY)); + UseCleanUp(glBindTexture(GL_TEXTURE_2D, 0)); +} + +//------------------------------------- +void +resize_GL(SWindowData *window_data) { + if (window_data->is_initialized) { + #if defined(_WIN32) || defined(WIN32) + + SWindowData_Win *window_data_ex = (SWindowData_Win *) window_data->specific; + wglMakeCurrent(window_data_ex->hdc, window_data_ex->hGLRC); + + #elif defined(linux) + + SWindowData_X11 *window_data_ex = (SWindowData_X11 *) window_data->specific; + glXMakeCurrent(window_data_ex->display, window_data_ex->window, window_data_ex->context); + + #endif + + glViewport(0, 0, window_data->window_width, window_data->window_height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, window_data->window_width, window_data->window_height, 0, 2048, -2048); + + glClear(GL_COLOR_BUFFER_BIT); + } +} + +//------------------------------------- +void +redraw_GL(SWindowData *window_data, const void *pixels) { +#if defined(_WIN32) || defined(WIN32) + + SWindowData_Win *window_data_ex = (SWindowData_Win *) window_data->specific; + GLenum format = BGRA; + + wglMakeCurrent(window_data_ex->hdc, window_data_ex->hGLRC); + +#elif defined(linux) + + SWindowData_X11 *window_data_ex = (SWindowData_X11 *) window_data->specific; + GLenum format = BGRA; + + glXMakeCurrent(window_data_ex->display, window_data_ex->window, window_data_ex->context); + +#endif + + float x, y, w, h; + + x = (float) window_data->dst_offset_x; + y = (float) window_data->dst_offset_y; + w = (float) window_data->dst_offset_x + window_data->dst_width; + h = (float) window_data->dst_offset_y + window_data->dst_height; + + float vertices[] = { + x, y, + 0, 0, + + w, y, + 1, 0, + + x, h, + 0, 1, + + w, h, + 1, 1, + }; + + glClear(GL_COLOR_BUFFER_BIT); + + UseCleanUp(glBindTexture(GL_TEXTURE_2D, window_data_ex->text_id)); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, window_data->buffer_width, window_data->buffer_height, 0, format, GL_UNSIGNED_BYTE, pixels); + //glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, window_data->buffer_width, window_data->buffer_height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + UseCleanUp(glEnableClientState(GL_VERTEX_ARRAY)); + UseCleanUp(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); + glVertexPointer(2, GL_FLOAT, 4 * sizeof(float), vertices); + glTexCoordPointer(2, GL_FLOAT, 4 * sizeof(float), vertices + 2); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + UseCleanUp(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); + UseCleanUp(glDisableClientState(GL_VERTEX_ARRAY)); + UseCleanUp(glBindTexture(GL_TEXTURE_2D, 0)); + +#if defined(_WIN32) || defined(WIN32) + SwapBuffers(window_data_ex->hdc); +#elif defined(linux) + glXSwapBuffers(window_data_ex->display, window_data_ex->window); +#endif +} + +//------------------------------------- +void +set_target_fps_aux() { + // Assuming the monitor refresh rate is 60 hz + int interval = (int) ((60.0 * g_time_for_frame) + 0.5); + +#if defined(_WIN32) || defined(WIN32) + + if (SwapIntervalEXT != 0x0) { + bool success = SwapIntervalEXT(interval); + if (GetSwapIntervalEXT != 0x0) { + int currentInterval = GetSwapIntervalEXT(); + if (interval != currentInterval) { + fprintf(stderr, "Cannot set target swap interval. Current swap interval is %d\n", currentInterval); + } + } + else if (success == false) { + fprintf(stderr, "Cannot set target swap interval.\n"); + } + g_use_hardware_sync = true; + } + +#elif defined(linux) + #define kGLX_SWAP_INTERVAL_EXT 0x20F1 + #define kGLX_MAX_SWAP_INTERVAL_EXT 0x20F2 + + if (SwapIntervalEXT != 0x0) { + Display *dpy = glXGetCurrentDisplay(); + GLXDrawable drawable = glXGetCurrentDrawable(); + unsigned int currentInterval, maxInterval; + + SwapIntervalEXT(dpy, drawable, interval); + glXQueryDrawable(dpy, drawable, kGLX_SWAP_INTERVAL_EXT, ¤tInterval); + if (interval != currentInterval) { + glXQueryDrawable(dpy, drawable, kGLX_MAX_SWAP_INTERVAL_EXT, &maxInterval); + fprintf(stderr, "Cannot set target swap interval. Current swap interval is %d (max: %d)\n", currentInterval, maxInterval); + } + g_use_hardware_sync = true; + } + +#endif +} + +#endif diff --git a/lib/minifb/upstream/src/gl/MiniFB_GL.h b/lib/minifb/upstream/src/gl/MiniFB_GL.h new file mode 100644 index 0000000..7eb5cd7 --- /dev/null +++ b/lib/minifb/upstream/src/gl/MiniFB_GL.h @@ -0,0 +1,13 @@ +#pragma once + +#if defined(USE_OPENGL_API) + + #include + + bool create_GL_context(SWindowData *window_data); + void destroy_GL_context(SWindowData *window_data); + void init_GL(SWindowData *window_data); + void redraw_GL(SWindowData *window_data, const void *pixels); + void resize_GL(SWindowData *window_data); + +#endif diff --git a/lib/minifb/upstream/src/ios/WindowData_IOS.h b/lib/minifb/upstream/src/ios/WindowData_IOS.h new file mode 100644 index 0000000..b4e31f9 --- /dev/null +++ b/lib/minifb/upstream/src/ios/WindowData_IOS.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +@class iOSViewDelegate; + +typedef struct Vertex { + float x, y, z, w; +} Vertex; + +typedef struct { + iOSViewDelegate *view_delegate; + Vertex vertices[4]; +} SWindowData_IOS; diff --git a/lib/minifb/upstream/src/ios/iOSMiniFB.m b/lib/minifb/upstream/src/ios/iOSMiniFB.m new file mode 100644 index 0000000..9e85736 --- /dev/null +++ b/lib/minifb/upstream/src/ios/iOSMiniFB.m @@ -0,0 +1,272 @@ +#import +#import +#include + +#include "iOSViewController.h" +#include "iOSViewDelegate.h" +#include "WindowData_IOS.h" +#include +#include +#include + +//------------------------------------- +SWindowData * +create_window_data(unsigned width, unsigned height) { + SWindowData *window_data; + + window_data = malloc(sizeof(SWindowData)); + if(window_data == 0x0) { + NSLog(@"Cannot allocate window data"); + return 0x0; + } + memset(window_data, 0, sizeof(SWindowData)); + + SWindowData_IOS *window_data_ios = malloc(sizeof(SWindowData_IOS)); + if(window_data_ios == 0x0) { + free(window_data); + NSLog(@"Cannot allocate ios window data"); + return 0x0; + } + memset((void *) window_data_ios, 0, sizeof(SWindowData_IOS)); + + window_data->specific = window_data_ios; + + float scale = [UIScreen mainScreen].scale; + + window_data->window_width = [UIScreen mainScreen].bounds.size.width * scale; + window_data->window_height = [UIScreen mainScreen].bounds.size.height * scale; + + calc_dst_factor(window_data, width, height); + + window_data->buffer_width = width; + window_data->buffer_height = height; + window_data->buffer_stride = width * 4; + + window_data->draw_buffer = malloc(width * height * 4); + if (!window_data->draw_buffer) { + free(window_data_ios); + free(window_data); + NSLog(@"Unable to create draw buffer"); + return 0x0; + } + + return window_data; +} + +//------------------------------------- +struct mfb_window * +mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) { + UIWindow *window; + NSArray *windows; + size_t numWindows; + + kUnused(title); + kUnused(flags); + + @autoreleasepool { + SWindowData *window_data = create_window_data(width, height); + if (window_data == 0x0) { + return 0x0; + } + + windows = [[UIApplication sharedApplication] windows]; + numWindows = [windows count]; + if(numWindows > 0) { + window = [windows objectAtIndex:0]; + } + else { + // Notice that you need to set "Launch Screen File" in: + // project > executable > general + // to get the real size with [UIScreen mainScreen].bounds]. + window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + NSLog(@"UIApplication has no window. We create one (%f, %f).", [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); + } + + if([window.rootViewController isKindOfClass:[iOSViewController class]] == false) { + iOSViewController *controller = [[iOSViewController alloc] initWithWindowData:window_data]; + [window setRootViewController:controller]; + #if !__has_feature(objc_arc) + [controller release]; + #endif + controller = (iOSViewController *) window.rootViewController; + } + else { + ((iOSViewController *) window.rootViewController)->window_data = window_data; + } + [window makeKeyAndVisible]; + + window_data->is_initialized = true; + return (struct mfb_window *) window_data; + } +} + +//------------------------------------- +static void +destroy_window_data(SWindowData *window_data) { + if(window_data == 0x0) + return; + + @autoreleasepool { + SWindowData_IOS *window_data_ios = (SWindowData_IOS *) window_data->specific; + if(window_data_ios != 0x0) { + memset((void *) window_data_ios, 0, sizeof(SWindowData_IOS)); + free(window_data_ios); + } + memset(window_data, 0, sizeof(SWindowData)); + free(window_data); + } +} + +//------------------------------------- +mfb_update_state +mfb_update_ex(struct mfb_window *window, void *buffer, unsigned width, unsigned height) { + if(window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + if(window_data->close) { + destroy_window_data(window_data); + return STATE_EXIT; + } + + if(buffer == 0x0) { + return STATE_INVALID_BUFFER; + } + + SWindowData_IOS *window_data_ios = (SWindowData_IOS *) window_data->specific; + + if(window_data->buffer_width != width || window_data->buffer_height != height) { + window_data->buffer_width = width; + window_data->buffer_stride = width * 4; + window_data->buffer_height = height; + window_data->draw_buffer = realloc(window_data->draw_buffer, window_data->buffer_stride * window_data->buffer_height); + + [window_data_ios->view_delegate resizeTextures]; + } + + memcpy(window_data->draw_buffer, buffer, window_data->buffer_width * window_data->buffer_height * 4); + + return STATE_OK; +} + +//------------------------------------- +mfb_update_state +mfb_update_events(struct mfb_window *window) { + if(window == 0x0) { + return STATE_INVALID_WINDOW; + } + + return STATE_OK; +} + +//------------------------------------- +extern double g_time_for_frame; + +bool +mfb_wait_sync(struct mfb_window *window) { + if(window == 0x0) { + return false; + } + + return true; +} + +//------------------------------------- +bool +mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height) { + if(window == 0x0) { + return false; + } + + SWindowData *window_data = (SWindowData *) window; + + if(offset_x + width > window_data->window_width) { + return false; + } + if(offset_y + height > window_data->window_height) { + return false; + } + + window_data->dst_offset_x = offset_x; + window_data->dst_offset_y = offset_y; + window_data->dst_width = width; + window_data->dst_height = height; + calc_dst_factor(window_data, window_data->window_width, window_data->window_height); + + float x1 = ((float) offset_x / window_data->window_width) * 2.0f - 1.0f; + float x2 = (((float) offset_x + width) / window_data->window_width) * 2.0f - 1.0f; + float y1 = ((float) offset_y / window_data->window_height) * 2.0f - 1.0f; + float y2 = (((float) offset_y + height) / window_data->window_height) * 2.0f - 1.0f; + + SWindowData_IOS *window_data_ios = (SWindowData_IOS *) window_data->specific; + + window_data_ios->vertices[0].x = x1; + window_data_ios->vertices[0].y = y1; + + window_data_ios->vertices[1].x = x1; + window_data_ios->vertices[1].y = y2; + + window_data_ios->vertices[2].x = x2; + window_data_ios->vertices[2].y = y1; + + window_data_ios->vertices[3].x = x2; + window_data_ios->vertices[3].y = y2; + + return true; +} + +//------------------------------------- +extern double g_timer_frequency; +extern double g_timer_resolution; + +uint64_t +mfb_timer_tick() { + static mach_timebase_info_data_t timebase = { 0 }; + + if (timebase.denom == 0) { + (void) mach_timebase_info(&timebase); + } + + uint64_t time = mach_absolute_time(); + + //return (time * s_timebase_info.numer) / s_timebase_info.denom; + + // Perform the arithmetic at 128-bit precision to avoid the overflow! + uint64_t high = (time >> 32) * timebase.numer; + uint64_t highRem = ((high % timebase.denom) << 32) / timebase.denom; + uint64_t low = (time & 0xFFFFFFFFull) * timebase.numer / timebase.denom; + high /= timebase.denom; + + return (high << 32) + highRem + low; +} + +//------------------------------------- +void +mfb_timer_init() { + g_timer_frequency = 1e+9; + g_timer_resolution = 1.0 / g_timer_frequency; +} + +//------------------------------------- +void +mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y) { + (void) window; + float scale = 1.0f; + + scale = [[UIScreen mainScreen] scale]; + + if (scale_x) { + *scale_x = scale; + if(*scale_x == 0) { + *scale_x = 1; + } + } + + if (scale_y) { + *scale_y = scale; + if (*scale_y == 0) { + *scale_y = 1; + } + } +} diff --git a/lib/minifb/upstream/src/ios/iOSView.h b/lib/minifb/upstream/src/ios/iOSView.h new file mode 100644 index 0000000..c9d925b --- /dev/null +++ b/lib/minifb/upstream/src/ios/iOSView.h @@ -0,0 +1,9 @@ +#import +#include "WindowData.h" + +@interface iOSView : MTKView +{ + @public SWindowData *window_data; +} + +@end diff --git a/lib/minifb/upstream/src/ios/iOSView.m b/lib/minifb/upstream/src/ios/iOSView.m new file mode 100644 index 0000000..2223f8e --- /dev/null +++ b/lib/minifb/upstream/src/ios/iOSView.m @@ -0,0 +1,84 @@ +#include "iOSView.h" +#include + +//------------------------------------- +@implementation iOSView + +//------------------------------------- +- (BOOL) canBecomeFirstResponder { + return YES; +} + +//------------------------------------- +- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + kUnused(event); + + if(window_data != 0x0) { + CGPoint point; + int buttonNumber = MOUSE_BTN_0; + for(UITouch *touch in touches) { + point = [touch locationInView:self]; + window_data->mouse_pos_x = point.x; + window_data->mouse_pos_y = point.y; + window_data->mouse_button_status[buttonNumber & 0x07] = true; + kCall(mouse_btn_func, buttonNumber, 0, true); + ++buttonNumber; + } + } +} + +//------------------------------------- +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + kUnused(event); + + if(window_data != 0x0) { + CGPoint point; + int buttonNumber = MOUSE_BTN_0; + for(UITouch *touch in touches) { + point = [touch locationInView:self]; + window_data->mouse_pos_x = point.x; + window_data->mouse_pos_y = point.y; + window_data->mouse_button_status[buttonNumber & 0x07] = true; + kCall(mouse_move_func, point.x, point.y); + ++buttonNumber; + } + } +} + +//------------------------------------- +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + kUnused(event); + + if(window_data != 0x0) { + CGPoint point; + int buttonNumber = MOUSE_BTN_0; + for(UITouch *touch in touches) { + point = [touch locationInView:self]; + window_data->mouse_pos_x = point.x; + window_data->mouse_pos_y = point.y; + window_data->mouse_button_status[buttonNumber & 0x07] = false; + kCall(mouse_btn_func, buttonNumber, 0, false); + ++buttonNumber; + } + } +} + +//------------------------------------- +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + kUnused(event); + + if(window_data != 0x0) { + CGPoint point; + int buttonNumber = MOUSE_BTN_0; + for(UITouch *touch in touches) { + point = [touch locationInView:self]; + window_data->mouse_pos_x = point.x; + window_data->mouse_pos_y = point.y; + window_data->mouse_button_status[buttonNumber & 0x07] = false; + kCall(mouse_btn_func, buttonNumber, 0, false); + ++buttonNumber; + } + } +} + +@end diff --git a/lib/minifb/upstream/src/ios/iOSViewController.h b/lib/minifb/upstream/src/ios/iOSViewController.h new file mode 100644 index 0000000..8e175cf --- /dev/null +++ b/lib/minifb/upstream/src/ios/iOSViewController.h @@ -0,0 +1,19 @@ +// +// iOSViewController.h +// MiniFB +// +// Created by Carlos Aragones on 22/04/2020. +// Copyright © 2020 Carlos Aragones. All rights reserved. +// + +#import +#include "WindowData.h" + +@interface iOSViewController : UIViewController +{ + @public SWindowData *window_data; +} + +- (id) initWithWindowData:(SWindowData *) windowData; + +@end diff --git a/lib/minifb/upstream/src/ios/iOSViewController.m b/lib/minifb/upstream/src/ios/iOSViewController.m new file mode 100644 index 0000000..23c85c6 --- /dev/null +++ b/lib/minifb/upstream/src/ios/iOSViewController.m @@ -0,0 +1,70 @@ +// +// iOSViewController.m +// MiniFB +// +// Created by Carlos Aragones on 22/04/2020. +// Copyright © 2020 Carlos Aragones. All rights reserved. +// + +#import +#import +#import "iOSViewController.h" +#import "iOSViewDelegate.h" +#import "iOSView.h" +#include "WindowData_IOS.h" + +//------------------------------------- +@implementation iOSViewController +{ + iOSView *metal_view; + //iOSViewDelegate *view_delegate; +} + +//------------------------------------- +- (id) initWithWindowData:(SWindowData *) windowData { + self = [super init]; + if (self) { + window_data = windowData; + } + return self; +} + +//------------------------------------- +- (void) loadView { + iOSView *view = [[iOSView alloc] initWithFrame:[UIScreen mainScreen].bounds]; + // Probably the window was created automatically by an storyboard or similar + if(window_data == 0x0) { + NSLog(@"WindowData is null!"); + } + view->window_data = window_data; + view.userInteractionEnabled = true; + + [self setView:view]; +#if !__has_feature(objc_arc) + [view release]; +#endif +} + +//------------------------------------- +- (void) viewDidLoad +{ + [super viewDidLoad]; + + metal_view = (iOSView *) self.view; + metal_view.device = MTLCreateSystemDefaultDevice(); + metal_view.backgroundColor = UIColor.blackColor; + + if(!metal_view.device) { + NSLog(@"Metal is not supported on this device"); + self.view = [[UIView alloc] initWithFrame:self.view.frame]; + return; + } + + SWindowData_IOS *window_data_ios = (SWindowData_IOS *) window_data->specific; + window_data_ios->view_delegate = [[iOSViewDelegate alloc] initWithMetalKitView:metal_view windowData:window_data]; + [window_data_ios->view_delegate mtkView:metal_view drawableSizeWillChange:metal_view.bounds.size]; + + metal_view.delegate = window_data_ios->view_delegate; +} + +@end diff --git a/lib/minifb/upstream/src/ios/iOSViewDelegate.h b/lib/minifb/upstream/src/ios/iOSViewDelegate.h new file mode 100644 index 0000000..4387626 --- /dev/null +++ b/lib/minifb/upstream/src/ios/iOSViewDelegate.h @@ -0,0 +1,23 @@ +// +// Renderer.h +// MiniFB +// +// Created by Carlos Aragones on 22/04/2020. +// Copyright © 2020 Carlos Aragones. All rights reserved. +// + +#import +#include "WindowData.h" + +// Our platform independent renderer class. +// Implements the MTKViewDelegate protocol which allows it to accept per-frame +// update and drawable resize callbacks. +@interface iOSViewDelegate : NSObject +{ +} + +-(nonnull instancetype) initWithMetalKitView:(nonnull MTKView *) view windowData:(nonnull SWindowData *) windowData; +- (void) resizeTextures; + +@end + diff --git a/lib/minifb/upstream/src/ios/iOSViewDelegate.m b/lib/minifb/upstream/src/ios/iOSViewDelegate.m new file mode 100644 index 0000000..1e6fb65 --- /dev/null +++ b/lib/minifb/upstream/src/ios/iOSViewDelegate.m @@ -0,0 +1,254 @@ +// +// Renderer.m +// MiniFB +// +// Created by Carlos Aragones on 22/04/2020. +// Copyright © 2020 Carlos Aragones. All rights reserved. +// + +#import +#import + +#import "iOSViewDelegate.h" +#include "WindowData_IOS.h" +#include +#include +#include + +//------------------------------------- +#define kShader(inc, src) @inc#src + +//------------------------------------- +enum { MaxBuffersInFlight = 3 }; // Number of textures in flight (tripple buffered) + +//-- +NSString *g_shader_src = kShader( + "#include \n", + using namespace metal; + + //------------- + struct VertexOutput { + float4 pos [[position]]; + float2 texcoord; + }; + + //------------- + struct Vertex { + float4 position [[position]]; + }; + + //------------- + vertex VertexOutput + vertFunc(unsigned int vID[[vertex_id]], const device Vertex *pos [[ buffer(0) ]]) { + VertexOutput out; + + out.pos = pos[vID].position; + + out.texcoord.x = (float) (vID / 2); + out.texcoord.y = 1.0 - (float) (vID % 2); + + return out; + } + + //------------- + fragment float4 + fragFunc(VertexOutput input [[stage_in]], texture2d colorTexture [[ texture(0) ]]) { + constexpr sampler textureSampler(mag_filter::nearest, min_filter::nearest); + + // Sample the texture to obtain a color + const half4 colorSample = colorTexture.sample(textureSampler, input.texcoord); + + // We return the color of the texture + return float4(colorSample); + }; +); + +//------------------------------------- +@implementation iOSViewDelegate { + SWindowData *window_data; + SWindowData_IOS *window_data_ios; + + id metal_device; + id metal_library; + + dispatch_semaphore_t semaphore; + id command_queue; + + id pipeline_state; + id texture_buffer; + + uint8_t current_buffer; +} + +//------------------------------------- +-(nonnull instancetype) initWithMetalKitView:(nonnull MTKView *) view windowData:(nonnull SWindowData *) windowData { + self = [super init]; + if (self) { + window_data = windowData; + window_data_ios = (SWindowData_IOS *) windowData->specific; + + view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + view.sampleCount = 1; + + metal_device = view.device; + + // Used for syncing the CPU and GPU + semaphore = dispatch_semaphore_create(MaxBuffersInFlight); + + // Setup command queue + command_queue = [metal_device newCommandQueue]; + + [self _createShaders]; + [self _createAssets]; + } + + return self; +} + +//------------------------------------- +- (bool) _createShaders { + NSError *error = 0x0; + + metal_library = [metal_device newLibraryWithSource:g_shader_src + options:[[MTLCompileOptions alloc] init] + error:&error + ]; + if (error || !metal_library) { + NSLog(@"Unable to create shaders %@", error); + return false; + } + + id vertex_shader_func = [metal_library newFunctionWithName:@"vertFunc"]; + id fragment_shader_func = [metal_library newFunctionWithName:@"fragFunc"]; + + if (!vertex_shader_func) { + NSLog(@"Unable to get vertFunc!\n"); + return false; + } + + if (!fragment_shader_func) { + NSLog(@"Unable to get fragFunc!\n"); + return false; + } + + // Create a reusable pipeline state + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"MiniFB_pipeline"; + pipelineStateDescriptor.vertexFunction = vertex_shader_func; + pipelineStateDescriptor.fragmentFunction = fragment_shader_func; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = 80; //bgra8Unorm; + + pipeline_state = [metal_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error]; + if (!pipeline_state) { + NSLog(@"Failed to created pipeline state, error %@", error); + return false; + } + + return true; +} + +//------------------------------------- +- (void) _createAssets { + static Vertex s_vertices[4] = { + {-1.0, -1.0, 0, 1}, + {-1.0, 1.0, 0, 1}, + { 1.0, -1.0, 0, 1}, + { 1.0, 1.0, 0, 1}, + }; + memcpy(window_data_ios->vertices, s_vertices, sizeof(s_vertices)); + + MTLTextureDescriptor *td; + td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm + width:window_data->buffer_width + height:window_data->buffer_height + mipmapped:false]; + + // Create the texture from the device by using the descriptor + texture_buffer = [metal_device newTextureWithDescriptor:td]; +} + +//------------------------------------- +- (void) resizeTextures { + MTLTextureDescriptor *td; + td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm + width:window_data->buffer_width + height:window_data->buffer_height + mipmapped:false]; + + // Create the texture from the device by using the descriptor + [texture_buffer release]; + texture_buffer = [metal_device newTextureWithDescriptor:td]; +} + +//------------------------------------- +- (void) drawInMTKView:(nonnull MTKView *) view { + // Wait to ensure only MaxBuffersInFlight number of frames are getting proccessed + // by any stage in the Metal pipeline (App, Metal, Drivers, GPU, etc) + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + + current_buffer = (current_buffer + 1) % MaxBuffersInFlight; + + // Create a new command buffer for each render pass to the current drawable + id commandBuffer = [command_queue commandBuffer]; + commandBuffer.label = @"minifb_command_buffer"; + + // Add completion hander which signals semaphore when Metal and the GPU has fully + // finished processing the commands we're encoding this frame. This indicates when the + // dynamic buffers filled with our vertices, that we're writing to this frame, will no longer + // be needed by Metal and the GPU, meaning we can overwrite the buffer contents without + // corrupting the rendering. + __block dispatch_semaphore_t block_sema = semaphore; + [commandBuffer addCompletedHandler:^(id buffer) { + (void)buffer; + dispatch_semaphore_signal(block_sema); + }]; + + // Copy the bytes from our data object into the texture + MTLRegion region = { { 0, 0, 0 }, { window_data->buffer_width, window_data->buffer_height, 1 } }; + [texture_buffer replaceRegion:region mipmapLevel:0 withBytes:window_data->draw_buffer bytesPerRow:window_data->buffer_stride]; + + // Delay getting the currentRenderPassDescriptor until absolutely needed. This avoids + // holding onto the drawable and blocking the display pipeline any longer than necessary + MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor; + if (renderPassDescriptor != nil) { + //renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0); + + // Create a render command encoder so we can render into something + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + renderEncoder.label = @"minifb_command_encoder"; + + // Set render command encoder state + [renderEncoder setRenderPipelineState:pipeline_state]; + [renderEncoder setVertexBytes:window_data_ios->vertices length:sizeof(window_data_ios->vertices) atIndex:0]; + + //[renderEncoder setFragmentTexture:texture_buffers[current_buffer] atIndex:0]; + [renderEncoder setFragmentTexture:texture_buffer atIndex:0]; + + // Draw the vertices of our quads + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; + + // We're done encoding commands + [renderEncoder endEncoding]; + + // Schedule a present once the framebuffer is complete using the current drawable + [commandBuffer presentDrawable:view.currentDrawable]; + } + + // Finalize rendering here & push the command buffer to the GPU + [commandBuffer commit]; +} + +//------------------------------------- +- (void) mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { + (void) view; + // Respond to drawable size or orientation changes here + float scale = [UIScreen mainScreen].scale; + + window_data->window_width = size.width * scale; + window_data->window_height = size.height * scale; + resize_dst(window_data, size.width, size.height); + + kCall(resize_func, size.width, size.height); +} + +@end diff --git a/lib/minifb/upstream/src/macosx/MacMiniFB.m b/lib/minifb/upstream/src/macosx/MacMiniFB.m new file mode 100644 index 0000000..581e354 --- /dev/null +++ b/lib/minifb/upstream/src/macosx/MacMiniFB.m @@ -0,0 +1,589 @@ +#include +#if defined(USE_METAL_API) +#include +#include +#endif +#include +#include +#include + +#include "OSXWindow.h" +#include "OSXView.h" +#include "OSXViewDelegate.h" +#include "WindowData_OSX.h" +#include +#include +#include + +//------------------------------------- +void init_keycodes(); + +//------------------------------------- +SWindowData * +create_window_data(unsigned width, unsigned height) { + SWindowData *window_data; + + window_data = malloc(sizeof(SWindowData)); + if(window_data == 0x0) { + NSLog(@"Cannot allocate window data"); + return 0x0; + } + memset(window_data, 0, sizeof(SWindowData)); + + SWindowData_OSX *window_data_osx = malloc(sizeof(SWindowData_OSX)); + if(window_data_osx == 0x0) { + free(window_data); + NSLog(@"Cannot allocate osx window data"); + return 0x0; + } + memset(window_data_osx, 0, sizeof(SWindowData_OSX)); + + window_data->specific = window_data_osx; + + calc_dst_factor(window_data, width, height); + + window_data->buffer_width = width; + window_data->buffer_height = height; + window_data->buffer_stride = width * 4; + +#if defined(USE_METAL_API) + window_data->draw_buffer = malloc(width * height * 4); + if (!window_data->draw_buffer) { + free(window_data_osx); + free(window_data); + NSLog(@"Unable to create draw buffer"); + return 0x0; + } +#endif + + return window_data; +} + +//------------------------------------- +struct mfb_window * +mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) { + @autoreleasepool { + SWindowData *window_data = create_window_data(width, height); + if (window_data == 0x0) { + return 0x0; + } + SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window_data->specific; + + init_keycodes(); + + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + + NSRect rectangle, frameRect; + NSWindowStyleMask styles = 0; + + if (flags & WF_BORDERLESS) { + styles |= NSWindowStyleMaskBorderless; + } + else { + styles |= NSWindowStyleMaskClosable | NSWindowStyleMaskTitled; + } + + if (flags & WF_RESIZABLE) + styles |= NSWindowStyleMaskResizable; + + if (flags & WF_FULLSCREEN) { + styles = NSWindowStyleMaskFullScreen; + NSScreen *mainScreen = [NSScreen mainScreen]; + NSRect screenRect = [mainScreen frame]; + window_data->window_width = screenRect.size.width; + window_data->window_height = screenRect.size.height; + rectangle = NSMakeRect(0, 0, window_data->window_width, window_data->window_height); + frameRect = rectangle; + } + else if (flags & WF_FULLSCREEN_DESKTOP) { + NSScreen *mainScreen = [NSScreen mainScreen]; + NSRect screenRect = [mainScreen visibleFrame]; + window_data->window_width = screenRect.size.width; + window_data->window_height = screenRect.size.height; + rectangle = NSMakeRect(0, 0, window_data->window_width, window_data->window_height); + frameRect = rectangle; + } + else { + window_data->window_width = width; + window_data->window_height = height; + rectangle = NSMakeRect(0, 0, window_data->window_width, window_data->window_height); + frameRect = [NSWindow frameRectForContentRect:rectangle styleMask:styles]; + } + + window_data_osx->window = [[OSXWindow alloc] initWithContentRect:frameRect styleMask:styles backing:NSBackingStoreBuffered defer:NO windowData:window_data]; + if (!window_data_osx->window) { + NSLog(@"Cannot create window"); + if(window_data->draw_buffer != 0x0) { + free(window_data->draw_buffer); + window_data->draw_buffer = 0x0; + } + free(window_data_osx); + free(window_data); + return 0x0; + } + + #if defined(USE_METAL_API) + window_data_osx->viewController = [[OSXViewDelegate alloc] initWithWindowData:window_data]; + + MTKView* view = [[MTKView alloc] initWithFrame:rectangle]; + view.device = window_data_osx->viewController->metal_device; + view.delegate = window_data_osx->viewController; + view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + [window_data_osx->window.contentView addSubview:view]; + + //[window_data->window updateSize]; + #endif + + [window_data_osx->window setTitle:[NSString stringWithUTF8String:title]]; + [window_data_osx->window setReleasedWhenClosed:NO]; + [window_data_osx->window performSelectorOnMainThread:@selector(makeKeyAndOrderFront:) withObject:nil waitUntilDone:YES]; + [window_data_osx->window setAcceptsMouseMovedEvents:YES]; + + [window_data_osx->window center]; + window_data_osx->timer = mfb_timer_create(); + + [NSApp activateIgnoringOtherApps:YES]; + + #if defined(USE_METAL_API) + [NSApp finishLaunching]; + #endif + + mfb_set_keyboard_callback((struct mfb_window *) window_data, keyboard_default); + +#if defined(_DEBUG) || defined(DEBUG) + #if defined(USE_METAL_API) + NSLog(@"Window created using Metal API"); + #else + NSLog(@"Window created using Cocoa API"); + #endif +#endif + + window_data->is_initialized = true; + return (struct mfb_window *) window_data; + } +} + +//------------------------------------- +static void +destroy_window_data(SWindowData *window_data) { + if(window_data == 0x0) + return; + + @autoreleasepool { + SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window_data->specific; + if(window_data_osx != 0x0) { + OSXWindow *window = window_data_osx->window; + [window performClose:nil]; + + // Flush events! + NSEvent* event; + do { + event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; + if (event) { + [NSApp sendEvent:event]; + } + } while (event); + [window removeWindowData]; + + mfb_timer_destroy(window_data_osx->timer); + + memset(window_data_osx, 0, sizeof(SWindowData_OSX)); + free(window_data_osx); + } + +#if defined(USE_METAL_API) + if(window_data->draw_buffer != 0x0) { + free(window_data->draw_buffer); + window_data->draw_buffer = 0x0; + } +#endif + + memset(window_data, 0, sizeof(SWindowData)); + free(window_data); + } +} + +//------------------------------------- +static void +update_events(SWindowData *window_data) { + NSEvent* event; + + @autoreleasepool { + do { + event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; + if (event) { + [NSApp sendEvent:event]; + } + } while ((window_data->close == false) && event); + } +} + +//------------------------------------- +mfb_update_state +mfb_update_ex(struct mfb_window *window, void *buffer, unsigned width, unsigned height) { + if(window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + if(window_data->close) { + destroy_window_data(window_data); + return STATE_EXIT; + } + + if(buffer == 0x0) { + return STATE_INVALID_BUFFER; + } + + SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window_data->specific; + +#if defined(USE_METAL_API) + if(window_data->buffer_width != width || window_data->buffer_height != height) { + window_data->buffer_width = width; + window_data->buffer_stride = width * 4; + window_data->buffer_height = height; + window_data->draw_buffer = realloc(window_data->draw_buffer, window_data->buffer_stride * window_data->buffer_height); + + [window_data_osx->viewController resizeTextures]; + } + + memcpy(window_data->draw_buffer, buffer, window_data->buffer_stride * window_data->buffer_height); +#else + if(window_data->buffer_width != width || window_data->buffer_height != height) { + window_data->buffer_width = width; + window_data->buffer_stride = width * 4; + window_data->buffer_height = height; + } + + window_data->draw_buffer = buffer; +#endif + + update_events(window_data); + if(window_data->close) { + destroy_window_data(window_data); + return STATE_EXIT; + } + + [[window_data_osx->window contentView] setNeedsDisplay:YES]; + + return STATE_OK; +} + +//------------------------------------- +mfb_update_state +mfb_update_events(struct mfb_window *window) { + if(window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + if(window_data->close) { + destroy_window_data(window_data); + return STATE_EXIT; + } + + update_events(window_data); + if(window_data->close) { + destroy_window_data(window_data); + return STATE_EXIT; + } + + SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window_data->specific; + [[window_data_osx->window contentView] setNeedsDisplay:YES]; + + return STATE_OK; +} + +//------------------------------------- +extern double g_time_for_frame; +extern bool g_use_hardware_sync; + +bool +mfb_wait_sync(struct mfb_window *window) { + NSEvent* event; + + if(window == 0x0) { + return false; + } + + SWindowData *window_data = (SWindowData *) window; + if(window_data->close) { + destroy_window_data(window_data); + return false; + } + + if(g_use_hardware_sync) { + return true; + } + + @autoreleasepool { + SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window_data->specific; + if(window_data_osx == 0x0) { + return false; + } + + double current; + uint32_t millis = 1; + while(1) { + current = mfb_timer_now(window_data_osx->timer); + if (current >= g_time_for_frame * 0.96) { + mfb_timer_reset(window_data_osx->timer); + return true; + } + else if(current >= g_time_for_frame * 0.8) { + millis = 0; + } + + usleep(millis * 1000); + //sched_yield(); + + if(millis == 1) { + event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; + if (event) { + [NSApp sendEvent:event]; + + if(window_data->close) { + destroy_window_data(window_data); + return false; + } + } + } + } + } + + return true; +} + +//------------------------------------- +bool +mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height) { + if(window == 0x0) { + return false; + } + + SWindowData *window_data = (SWindowData *) window; + + if(offset_x + width > window_data->window_width) { + return false; + } + if(offset_y + height > window_data->window_height) { + return false; + } + + window_data->dst_offset_x = offset_x; + window_data->dst_offset_y = offset_y; + window_data->dst_width = width; + window_data->dst_height = height; + calc_dst_factor(window_data, window_data->window_width, window_data->window_height); + +#if defined(USE_METAL_API) + float x1 = ((float) offset_x / window_data->window_width) * 2.0f - 1.0f; + float x2 = (((float) offset_x + width) / window_data->window_width) * 2.0f - 1.0f; + float y1 = ((float) offset_y / window_data->window_height) * 2.0f - 1.0f; + float y2 = (((float) offset_y + height) / window_data->window_height) * 2.0f - 1.0f; + + SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window_data->specific; + + window_data_osx->metal.vertices[0].x = x1; + window_data_osx->metal.vertices[0].y = y1; + + window_data_osx->metal.vertices[1].x = x1; + window_data_osx->metal.vertices[1].y = y2; + + window_data_osx->metal.vertices[2].x = x2; + window_data_osx->metal.vertices[2].y = y1; + + window_data_osx->metal.vertices[3].x = x2; + window_data_osx->metal.vertices[3].y = y2; +#endif + + return true; +} + +//------------------------------------- +extern short int g_keycodes[512]; + +void +init_keycodes() { + // Clear keys + for (unsigned int i = 0; i < sizeof(g_keycodes) / sizeof(g_keycodes[0]); ++i) + g_keycodes[i] = 0; + + g_keycodes[0x1D] = KB_KEY_0; + g_keycodes[0x12] = KB_KEY_1; + g_keycodes[0x13] = KB_KEY_2; + g_keycodes[0x14] = KB_KEY_3; + g_keycodes[0x15] = KB_KEY_4; + g_keycodes[0x17] = KB_KEY_5; + g_keycodes[0x16] = KB_KEY_6; + g_keycodes[0x1A] = KB_KEY_7; + g_keycodes[0x1C] = KB_KEY_8; + g_keycodes[0x19] = KB_KEY_9; + g_keycodes[0x00] = KB_KEY_A; + g_keycodes[0x0B] = KB_KEY_B; + g_keycodes[0x08] = KB_KEY_C; + g_keycodes[0x02] = KB_KEY_D; + g_keycodes[0x0E] = KB_KEY_E; + g_keycodes[0x03] = KB_KEY_F; + g_keycodes[0x05] = KB_KEY_G; + g_keycodes[0x04] = KB_KEY_H; + g_keycodes[0x22] = KB_KEY_I; + g_keycodes[0x26] = KB_KEY_J; + g_keycodes[0x28] = KB_KEY_K; + g_keycodes[0x25] = KB_KEY_L; + g_keycodes[0x2E] = KB_KEY_M; + g_keycodes[0x2D] = KB_KEY_N; + g_keycodes[0x1F] = KB_KEY_O; + g_keycodes[0x23] = KB_KEY_P; + g_keycodes[0x0C] = KB_KEY_Q; + g_keycodes[0x0F] = KB_KEY_R; + g_keycodes[0x01] = KB_KEY_S; + g_keycodes[0x11] = KB_KEY_T; + g_keycodes[0x20] = KB_KEY_U; + g_keycodes[0x09] = KB_KEY_V; + g_keycodes[0x0D] = KB_KEY_W; + g_keycodes[0x07] = KB_KEY_X; + g_keycodes[0x10] = KB_KEY_Y; + g_keycodes[0x06] = KB_KEY_Z; + + g_keycodes[0x27] = KB_KEY_APOSTROPHE; + g_keycodes[0x2A] = KB_KEY_BACKSLASH; + g_keycodes[0x2B] = KB_KEY_COMMA; + g_keycodes[0x18] = KB_KEY_EQUAL; + g_keycodes[0x32] = KB_KEY_GRAVE_ACCENT; + g_keycodes[0x21] = KB_KEY_LEFT_BRACKET; + g_keycodes[0x1B] = KB_KEY_MINUS; + g_keycodes[0x2F] = KB_KEY_PERIOD; + g_keycodes[0x1E] = KB_KEY_RIGHT_BRACKET; + g_keycodes[0x29] = KB_KEY_SEMICOLON; + g_keycodes[0x2C] = KB_KEY_SLASH; + g_keycodes[0x0A] = KB_KEY_WORLD_1; + + g_keycodes[0x33] = KB_KEY_BACKSPACE; + g_keycodes[0x39] = KB_KEY_CAPS_LOCK; + g_keycodes[0x75] = KB_KEY_DELETE; + g_keycodes[0x7D] = KB_KEY_DOWN; + g_keycodes[0x77] = KB_KEY_END; + g_keycodes[0x24] = KB_KEY_ENTER; + g_keycodes[0x35] = KB_KEY_ESCAPE; + g_keycodes[0x7A] = KB_KEY_F1; + g_keycodes[0x78] = KB_KEY_F2; + g_keycodes[0x63] = KB_KEY_F3; + g_keycodes[0x76] = KB_KEY_F4; + g_keycodes[0x60] = KB_KEY_F5; + g_keycodes[0x61] = KB_KEY_F6; + g_keycodes[0x62] = KB_KEY_F7; + g_keycodes[0x64] = KB_KEY_F8; + g_keycodes[0x65] = KB_KEY_F9; + g_keycodes[0x6D] = KB_KEY_F10; + g_keycodes[0x67] = KB_KEY_F11; + g_keycodes[0x6F] = KB_KEY_F12; + g_keycodes[0x69] = KB_KEY_F13; + g_keycodes[0x6B] = KB_KEY_F14; + g_keycodes[0x71] = KB_KEY_F15; + g_keycodes[0x6A] = KB_KEY_F16; + g_keycodes[0x40] = KB_KEY_F17; + g_keycodes[0x4F] = KB_KEY_F18; + g_keycodes[0x50] = KB_KEY_F19; + g_keycodes[0x5A] = KB_KEY_F20; + g_keycodes[0x73] = KB_KEY_HOME; + g_keycodes[0x72] = KB_KEY_INSERT; + g_keycodes[0x7B] = KB_KEY_LEFT; + g_keycodes[0x3A] = KB_KEY_LEFT_ALT; + g_keycodes[0x3B] = KB_KEY_LEFT_CONTROL; + g_keycodes[0x38] = KB_KEY_LEFT_SHIFT; + g_keycodes[0x37] = KB_KEY_LEFT_SUPER; + g_keycodes[0x6E] = KB_KEY_MENU; + g_keycodes[0x47] = KB_KEY_NUM_LOCK; + g_keycodes[0x79] = KB_KEY_PAGE_DOWN; + g_keycodes[0x74] = KB_KEY_PAGE_UP; + g_keycodes[0x7C] = KB_KEY_RIGHT; + g_keycodes[0x3D] = KB_KEY_RIGHT_ALT; + g_keycodes[0x3E] = KB_KEY_RIGHT_CONTROL; + g_keycodes[0x3C] = KB_KEY_RIGHT_SHIFT; + g_keycodes[0x36] = KB_KEY_RIGHT_SUPER; + g_keycodes[0x31] = KB_KEY_SPACE; + g_keycodes[0x30] = KB_KEY_TAB; + g_keycodes[0x7E] = KB_KEY_UP; + + g_keycodes[0x52] = KB_KEY_KP_0; + g_keycodes[0x53] = KB_KEY_KP_1; + g_keycodes[0x54] = KB_KEY_KP_2; + g_keycodes[0x55] = KB_KEY_KP_3; + g_keycodes[0x56] = KB_KEY_KP_4; + g_keycodes[0x57] = KB_KEY_KP_5; + g_keycodes[0x58] = KB_KEY_KP_6; + g_keycodes[0x59] = KB_KEY_KP_7; + g_keycodes[0x5B] = KB_KEY_KP_8; + g_keycodes[0x5C] = KB_KEY_KP_9; + g_keycodes[0x45] = KB_KEY_KP_ADD; + g_keycodes[0x41] = KB_KEY_KP_DECIMAL; + g_keycodes[0x4B] = KB_KEY_KP_DIVIDE; + g_keycodes[0x4C] = KB_KEY_KP_ENTER; + g_keycodes[0x51] = KB_KEY_KP_EQUAL; + g_keycodes[0x43] = KB_KEY_KP_MULTIPLY; + g_keycodes[0x4E] = KB_KEY_KP_SUBTRACT; +} + +//------------------------------------- +extern double g_timer_frequency; +extern double g_timer_resolution; + +uint64_t +mfb_timer_tick() { + static mach_timebase_info_data_t timebase = { 0 }; + + if (timebase.denom == 0) { + (void) mach_timebase_info(&timebase); + } + + uint64_t time = mach_absolute_time(); + + //return (time * s_timebase_info.numer) / s_timebase_info.denom; + + // Perform the arithmetic at 128-bit precision to avoid the overflow! + uint64_t high = (time >> 32) * timebase.numer; + uint64_t highRem = ((high % timebase.denom) << 32) / timebase.denom; + uint64_t low = (time & 0xFFFFFFFFull) * timebase.numer / timebase.denom; + high /= timebase.denom; + + return (high << 32) + highRem + low; +} + +//------------------------------------- +void +mfb_timer_init() { + g_timer_frequency = 1e+9; + g_timer_resolution = 1.0 / g_timer_frequency; +} + +//------------------------------------- +void +mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y) { + float scale = 1.0f; + + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window_data->specific; + + scale = [window_data_osx->window backingScaleFactor]; + } + else { + scale = [[NSScreen mainScreen] backingScaleFactor]; + } + + if (scale_x) { + *scale_x = scale; + if(*scale_x == 0) { + *scale_x = 1; + } + } + + if (scale_y) { + *scale_y = scale; + if (*scale_y == 0) { + *scale_y = 1; + } + } +} diff --git a/lib/minifb/upstream/src/macosx/OSXView.h b/lib/minifb/upstream/src/macosx/OSXView.h new file mode 100644 index 0000000..73ed1f4 --- /dev/null +++ b/lib/minifb/upstream/src/macosx/OSXView.h @@ -0,0 +1,14 @@ +#import + +#include "WindowData.h" + +@interface OSXView : NSView +{ + @public SWindowData *window_data; +#if defined(USE_METAL_API) + @private NSTrackingArea *tracking_area; +#endif +} + +@end + diff --git a/lib/minifb/upstream/src/macosx/OSXView.m b/lib/minifb/upstream/src/macosx/OSXView.m new file mode 100644 index 0000000..b00fca3 --- /dev/null +++ b/lib/minifb/upstream/src/macosx/OSXView.m @@ -0,0 +1,240 @@ +#import "OSXView.h" +#import "OSXWindow.h" +#import "WindowData_OSX.h" +#include + +//------------------------------------- +@implementation OSXView + +#if defined(USE_METAL_API) + +//------------------------------------- +- (void)updateTrackingAreas { + if(tracking_area != nil) { + [self removeTrackingArea:tracking_area]; + [tracking_area release]; + } + + int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways); + tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] + options:opts + owner:self + userInfo:nil]; + [self addTrackingArea:tracking_area]; +} + +#else + +//------------------------------------- +- (NSRect)resizeRect { + const CGFloat resizeBoxSize = 16.0; + const CGFloat contentViewPadding = 5.5; + + NSRect contentViewRect = [[self window] contentRectForFrameRect:[[self window] frame]]; + NSRect resizeRect = NSMakeRect( + NSMaxX(contentViewRect) + contentViewPadding, + NSMinY(contentViewRect) - resizeBoxSize - contentViewPadding, + resizeBoxSize, + resizeBoxSize + ); + + return resizeRect; +} + +//------------------------------------- +- (void)drawRect:(NSRect)rect { + (void)rect; + + if(window_data == 0x0) + return; + + SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window_data->specific; + if (!window_data_osx || !window_data_osx->window || !window_data->draw_buffer) + return; + + CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; + + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGDataProviderRef provider = CGDataProviderCreateWithData(0x0, + window_data->draw_buffer, + window_data->buffer_width * window_data->buffer_height * 4, + 0x0 + ); + + CGImageRef img = CGImageCreate(window_data->buffer_width + , window_data->buffer_height + , 8 + , 32 + , window_data->buffer_width * 4 + , space + , kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little + , provider + , 0x0 + , false + , kCGRenderingIntentDefault + ); + + const CGFloat components[] = {0.0f, 0.0f, 0.0f, 1.0f}; + const CGColorRef black = CGColorCreate(space, components); + + CGColorSpaceRelease(space); + CGDataProviderRelease(provider); + + if(window_data->dst_offset_x != 0 || window_data->dst_offset_y != 0 || window_data->dst_width != window_data->window_width || window_data->dst_height != window_data->window_height) { + CGContextSetFillColorWithColor(context, black); + CGContextFillRect(context, rect); + } + + // TODO: Sometimes there is a crash here + CGContextDrawImage(context, + CGRectMake(window_data->dst_offset_x, window_data->dst_offset_y, window_data->dst_width, window_data->dst_height), + img + ); + + CGImageRelease(img); +} + +#endif + +//------------------------------------- +- (BOOL)acceptsFirstMouse:(NSEvent *)event { + (void)event; + return YES; +} + +//------------------------------------- +- (void)mouseDown:(NSEvent*)event { + (void)event; + if(window_data != 0x0) { + window_data->mouse_button_status[MOUSE_BTN_1] = true; + kCall(mouse_btn_func, MOUSE_BTN_1, window_data->mod_keys, true); + } +} + +//------------------------------------- +- (void)mouseUp:(NSEvent*)event { + (void)event; + if(window_data != 0x0) { + window_data->mouse_button_status[MOUSE_BTN_1] = false; + kCall(mouse_btn_func, MOUSE_BTN_1, window_data->mod_keys, false); + } +} + +//------------------------------------- +- (void)rightMouseDown:(NSEvent*)event { + (void)event; + if(window_data != 0x0) { + window_data->mouse_button_status[MOUSE_BTN_2] = true; + kCall(mouse_btn_func, MOUSE_BTN_2, window_data->mod_keys, true); + } +} + +//------------------------------------- +- (void)rightMouseUp:(NSEvent*)event { + (void)event; + if(window_data != 0x0) { + window_data->mouse_button_status[MOUSE_BTN_2] = false; + kCall(mouse_btn_func, MOUSE_BTN_2, window_data->mod_keys, false); + } +} + +//------------------------------------- +- (void)otherMouseDown:(NSEvent *)event { + (void)event; + if(window_data != 0x0) { + window_data->mouse_button_status[[event buttonNumber] & 0x07] = true; + kCall(mouse_btn_func, [event buttonNumber], window_data->mod_keys, true); + } +} + +//------------------------------------- +- (void)otherMouseUp:(NSEvent *)event { + (void)event; + if(window_data != 0x0) { + window_data->mouse_button_status[[event buttonNumber] & 0x07] = false; + kCall(mouse_btn_func, [event buttonNumber], window_data->mod_keys, false); + } +} + +//------------------------------------- +- (void)scrollWheel:(NSEvent *)event { + if(window_data != 0x0) { + window_data->mouse_wheel_x = [event deltaX]; + window_data->mouse_wheel_y = [event deltaY]; + kCall(mouse_wheel_func, window_data->mod_keys, window_data->mouse_wheel_x, window_data->mouse_wheel_y); + } +} + +//------------------------------------- +- (void)mouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +//------------------------------------- +- (void)rightMouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +//------------------------------------- +- (void)otherMouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +//------------------------------------- +- (void)mouseMoved:(NSEvent *)event { + if(window_data != 0x0) { + NSPoint point = [event locationInWindow]; + //NSPoint localPoint = [self convertPoint:point fromView:nil]; + window_data->mouse_pos_x = point.x; +#if defined(USE_INVERTED_Y_ON_MACOS) + window_data->mouse_pos_y = point.y; +#else + window_data->mouse_pos_y = window_data->window_height - point.y; +#endif + kCall(mouse_move_func, window_data->mouse_pos_x, window_data->mouse_pos_y); + } +} + +//------------------------------------- +- (void)mouseExited:(NSEvent *)event { + (void)event; + //printf("mouse exit\n"); +} + +//------------------------------------- +- (void)mouseEntered:(NSEvent *)event { + (void)event; + //printf("mouse enter\n"); +} + +//------------------------------------- +- (BOOL)canBecomeKeyView { + return YES; +} + +//------------------------------------- +- (NSView *)nextValidKeyView { + return self; +} + +//------------------------------------- +- (NSView *)previousValidKeyView { + return self; +} + +//------------------------------------- +- (BOOL)acceptsFirstResponder { + return YES; +} + +//------------------------------------- +- (void)viewDidMoveToWindow { +} + +//------------------------------------- +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +@end diff --git a/lib/minifb/upstream/src/macosx/OSXViewDelegate.h b/lib/minifb/upstream/src/macosx/OSXViewDelegate.h new file mode 100644 index 0000000..33943fd --- /dev/null +++ b/lib/minifb/upstream/src/macosx/OSXViewDelegate.h @@ -0,0 +1,35 @@ +#pragma once + +#if defined(USE_METAL_API) + +#import +#include "WindowData_OSX.h" + +// Number of textures in flight (tripple buffered) +enum { MaxBuffersInFlight = 3 }; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface OSXViewDelegate : NSViewController +{ + @public SWindowData *window_data; + @public SWindowData_OSX *window_data_osx; + + id metal_device; + id metal_library; + + dispatch_semaphore_t semaphore; // Used for syncing with CPU/GPU + id command_queue; + + id pipeline_state; + id texture_buffers[MaxBuffersInFlight]; + + int current_buffer; +} + +- (id) initWithWindowData:(SWindowData *) windowData; +- (void) resizeTextures; + +@end + +#endif diff --git a/lib/minifb/upstream/src/macosx/OSXViewDelegate.m b/lib/minifb/upstream/src/macosx/OSXViewDelegate.m new file mode 100644 index 0000000..a6aeb2e --- /dev/null +++ b/lib/minifb/upstream/src/macosx/OSXViewDelegate.m @@ -0,0 +1,261 @@ +#include "OSXViewDelegate.h" + +#if defined(USE_METAL_API) + +#import + +extern double g_time_for_frame; +extern bool g_use_hardware_sync; +//-- +bool g_target_fps_changed = true; + +//------------------------------------- +void +set_target_fps_aux() { + g_target_fps_changed = true; +} + +//------------------------------------- +#define kShader(inc, src) @inc#src + +NSString *g_shader_src = kShader( + "#include \n", + using namespace metal; + + //------------- + struct VertexOutput { + float4 pos [[position]]; + float2 texcoord; + }; + + //------------- + struct Vertex { + float4 position [[position]]; + }; + + //------------- + vertex VertexOutput + vertFunc(unsigned int vID[[vertex_id]], const device Vertex *pos [[ buffer(0) ]]) { + VertexOutput out; + + out.pos = pos[vID].position; + + out.texcoord.x = (float) (vID / 2); + out.texcoord.y = 1.0 - (float) (vID % 2); + + return out; + } + + //------------- + fragment float4 + fragFunc(VertexOutput input [[stage_in]], texture2d colorTexture [[ texture(0) ]]) { + constexpr sampler textureSampler(mag_filter::nearest, min_filter::nearest); + + // Sample the texture to obtain a color + const half4 colorSample = colorTexture.sample(textureSampler, input.texcoord); + + // We return the color of the texture + return float4(colorSample); + }; +); + +//------------------------------------- +@implementation OSXViewDelegate + +//------------------------------------- +- (id) initWithWindowData:(SWindowData *) windowData { + self = [super init]; + if (self) { + window_data = windowData; + window_data_osx = (SWindowData_OSX *) windowData->specific; + + metal_device = MTLCreateSystemDefaultDevice(); + if (!metal_device) { + NSLog(@"Metal is not supported on this device"); + return 0x0; + } + + // Used for syncing the CPU and GPU + semaphore = dispatch_semaphore_create(MaxBuffersInFlight); + + // Setup command queue + command_queue = [metal_device newCommandQueue]; + + // MacOS Mojave is ignoring view.preferredFramesPerSecond + // MacOS Big Sur is ignoring commandBuffer:presentDrawable:afterMinimumDuration: + //id commandBuffer = [command_queue commandBuffer]; + //if ([commandBuffer respondsToSelector:@selector(presentDrawable:afterMinimumDuration:)]) { + // g_use_hardware_sync = true; + //} + + [self _createShaders]; + [self _createAssets]; + } + return self; +} + +//------------------------------------- +- (bool) _createShaders { + NSError *error = 0x0; + + metal_library = [metal_device newLibraryWithSource:g_shader_src + options:[[MTLCompileOptions alloc] init] + error:&error + ]; + if (error || !metal_library) { + NSLog(@"Unable to create shaders %@", error); + return false; + } + + id vertex_shader_func = [metal_library newFunctionWithName:@"vertFunc"]; + id fragment_shader_func = [metal_library newFunctionWithName:@"fragFunc"]; + + if (!vertex_shader_func) { + NSLog(@"Unable to get vertFunc!\n"); + return false; + } + + if (!fragment_shader_func) { + NSLog(@"Unable to get fragFunc!\n"); + return false; + } + + // Create a reusable pipeline state + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"MiniFB_pipeline"; + pipelineStateDescriptor.vertexFunction = vertex_shader_func; + pipelineStateDescriptor.fragmentFunction = fragment_shader_func; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = 80; //bgra8Unorm; + + pipeline_state = [metal_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error]; + if (!pipeline_state) { + NSLog(@"Failed to created pipeline state, error %@", error); + return false; + } + + return true; +} + +//------------------------------------- +- (void) _createAssets { + static Vertex s_vertices[4] = { + {-1.0, -1.0, 0, 1}, + {-1.0, 1.0, 0, 1}, + { 1.0, -1.0, 0, 1}, + { 1.0, 1.0, 0, 1}, + }; + memcpy(window_data_osx->metal.vertices, s_vertices, sizeof(s_vertices)); + + MTLTextureDescriptor *td; + td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm + width:window_data->buffer_width + height:window_data->buffer_height + mipmapped:false]; + + // Create the texture from the device by using the descriptor + for (size_t i = 0; i < MaxBuffersInFlight; ++i) { + texture_buffers[i] = [metal_device newTextureWithDescriptor:td]; + } +} + +//------------------------------------- +- (void) resizeTextures { + MTLTextureDescriptor *td; + td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm + width:window_data->buffer_width + height:window_data->buffer_height + mipmapped:false]; + + // Create the texture from the device by using the descriptor + for (size_t i = 0; i < MaxBuffersInFlight; ++i) { + [texture_buffers[i] release]; + texture_buffers[i] = [metal_device newTextureWithDescriptor:td]; + } +} + +//------------------------------------- +- (void) drawInMTKView:(nonnull MTKView *) view { + if (g_target_fps_changed) { + // MacOS is ignoring this :( + if (g_time_for_frame == 0) { + // Contrary to what is stated in the documentation, + // 0 means that it does not update. Like pause. + view.preferredFramesPerSecond = 9999; + } + else { + view.preferredFramesPerSecond = (int) (1.0 / g_time_for_frame); + } + g_target_fps_changed = false; + } + + // Wait to ensure only MaxBuffersInFlight number of frames are getting proccessed + // by any stage in the Metal pipeline (App, Metal, Drivers, GPU, etc) + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + + current_buffer = (current_buffer + 1) % MaxBuffersInFlight; + + // Create a new command buffer for each render pass to the current drawable + id commandBuffer = [command_queue commandBuffer]; + commandBuffer.label = @"minifb_command_buffer"; + + // Add completion hander which signals semaphore when Metal and the GPU has fully + // finished processing the commands we're encoding this frame. This indicates when the + // dynamic buffers filled with our vertices, that we're writing to this frame, will no longer + // be needed by Metal and the GPU, meaning we can overwrite the buffer contents without + // corrupting the rendering. + __block dispatch_semaphore_t block_sema = semaphore; + [commandBuffer addCompletedHandler:^(id buffer) { + (void)buffer; + dispatch_semaphore_signal(block_sema); + }]; + + // Copy the bytes from our data object into the texture + MTLRegion region = { { 0, 0, 0 }, { window_data->buffer_width, window_data->buffer_height, 1 } }; + [texture_buffers[current_buffer] replaceRegion:region mipmapLevel:0 withBytes:window_data->draw_buffer bytesPerRow:window_data->buffer_stride]; + + // Delay getting the currentRenderPassDescriptor until absolutely needed. This avoids + // holding onto the drawable and blocking the display pipeline any longer than necessary + MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor; + if (renderPassDescriptor != nil) { + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0); + + // Create a render command encoder so we can render into something + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + renderEncoder.label = @"minifb_command_encoder"; + + // Set render command encoder state + [renderEncoder setRenderPipelineState:pipeline_state]; + [renderEncoder setVertexBytes:window_data_osx->metal.vertices length:sizeof(window_data_osx->metal.vertices) atIndex:0]; + + [renderEncoder setFragmentTexture:texture_buffers[current_buffer] atIndex:0]; + + // Draw the vertices of our quads + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; + + // We're done encoding commands + [renderEncoder endEncoding]; + + // Schedule a present once the framebuffer is complete using the current drawable + //if ([commandBuffer respondsToSelector:@selector(presentDrawable:afterMinimumDuration:)]) { + // // MacOS Big Sur is ignoring this + // [commandBuffer presentDrawable:view.currentDrawable afterMinimumDuration:g_time_for_frame]; + //} + //else { + [commandBuffer presentDrawable:view.currentDrawable]; + //} + } + + // Finalize rendering here & push the command buffer to the GPU + [commandBuffer commit]; +} + +//------------------------------------- +- (void) mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { + (void)view; + (void)size; + // resize +} + +@end + +#endif diff --git a/lib/minifb/upstream/src/macosx/OSXWindow.h b/lib/minifb/upstream/src/macosx/OSXWindow.h new file mode 100644 index 0000000..4f77ac7 --- /dev/null +++ b/lib/minifb/upstream/src/macosx/OSXWindow.h @@ -0,0 +1,17 @@ +#import +#include + +@interface OSXWindow : NSWindow +{ + NSView *childContentView; + @public SWindowData *window_data; +} + +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(NSWindowStyleMask)windowStyle + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)deferCreation + windowData:(SWindowData *) windowData; + +- (void) removeWindowData; +@end diff --git a/lib/minifb/upstream/src/macosx/OSXWindow.m b/lib/minifb/upstream/src/macosx/OSXWindow.m new file mode 100644 index 0000000..b1eaf02 --- /dev/null +++ b/lib/minifb/upstream/src/macosx/OSXWindow.m @@ -0,0 +1,315 @@ +#import "OSXWindow.h" +#import "OSXView.h" +#include "WindowData_OSX.h" +#include +#include + +@implementation OSXWindow + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(NSWindowStyleMask)windowStyle + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)deferCreation + windowData:(SWindowData *) windowData +{ + self = [super + initWithContentRect:contentRect + styleMask:windowStyle + backing:bufferingType + defer:deferCreation]; + + if (self) + { + [self setOpaque:YES]; + [self setBackgroundColor:[NSColor clearColor]]; + + self.delegate = self; + + self->window_data = windowData; + OSXView *view = (OSXView *) self->childContentView.superview; + view->window_data = windowData; + } + return self; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void) removeWindowData { + self->window_data = 0x0; + OSXView *view = (OSXView *) self->childContentView.superview; + view->window_data = 0x0; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] + removeObserver:self]; + [super dealloc]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)setContentSize:(NSSize)newSize +{ + NSSize sizeDelta = newSize; + NSSize childBoundsSize = [childContentView bounds].size; + sizeDelta.width -= childBoundsSize.width; + sizeDelta.height -= childBoundsSize.height; + + OSXView *frameView = [super contentView]; + NSSize newFrameSize = [frameView bounds].size; + newFrameSize.width += sizeDelta.width; + newFrameSize.height += sizeDelta.height; + + [super setContentSize:newFrameSize]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)flagsChanged:(NSEvent *)event +{ + if(window_data == 0x0) + return; + + const uint32_t flags = [event modifierFlags]; + uint32_t mod_keys = 0, mod_keys_aux = 0; + + //NSEventModifierFlagHelp = 1 << 22, + //NSEventModifierFlagFunction = 1 << 23, + if(flags & NSEventModifierFlagCapsLock) { + mod_keys |= KB_MOD_CAPS_LOCK; + } + if(flags & NSEventModifierFlagShift) { + mod_keys |= KB_MOD_SHIFT; + } + if(flags & NSEventModifierFlagControl) { + mod_keys |= KB_MOD_CONTROL; + } + if(flags & NSEventModifierFlagOption) { + mod_keys |= KB_MOD_ALT; + } + if(flags & NSEventModifierFlagCommand) { + mod_keys |= KB_MOD_SUPER; + } + if(flags & NSEventModifierFlagNumericPad) { + mod_keys |= KB_MOD_NUM_LOCK; + } + + if(mod_keys != window_data->mod_keys) { + short int key_code = g_keycodes[[event keyCode] & 0x1ff]; + if(key_code != KB_KEY_UNKNOWN) { + mod_keys_aux = mod_keys ^ window_data->mod_keys; + if(mod_keys_aux & KB_MOD_CAPS_LOCK) { + window_data->key_status[key_code] = (mod_keys & KB_MOD_CAPS_LOCK) != 0; + kCall(keyboard_func, key_code, mod_keys, window_data->key_status[key_code]); + } + if(mod_keys_aux & KB_MOD_SHIFT) { + window_data->key_status[key_code] = (mod_keys & KB_MOD_SHIFT) != 0; + kCall(keyboard_func, key_code, mod_keys, window_data->key_status[key_code]); + } + if(mod_keys_aux & KB_MOD_CONTROL) { + window_data->key_status[key_code] = (mod_keys & KB_MOD_CONTROL) != 0; + kCall(keyboard_func, key_code, mod_keys, window_data->key_status[key_code]); + } + if(mod_keys_aux & KB_MOD_ALT) { + window_data->key_status[key_code] = (mod_keys & KB_MOD_ALT) != 0; + kCall(keyboard_func, key_code, mod_keys, window_data->key_status[key_code]); + } + if(mod_keys_aux & KB_MOD_SUPER) { + window_data->key_status[key_code] = (mod_keys & KB_MOD_SUPER) != 0; + kCall(keyboard_func, key_code, mod_keys, window_data->key_status[key_code]); + } + if(mod_keys_aux & KB_MOD_NUM_LOCK) { + window_data->key_status[key_code] = (mod_keys & KB_MOD_NUM_LOCK) != 0; + kCall(keyboard_func, key_code, mod_keys, window_data->key_status[key_code]); + } + } + } + window_data->mod_keys = mod_keys; + + [super flagsChanged:event]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)keyDown:(NSEvent *)event +{ + if(window_data != 0x0) { + short int key_code = g_keycodes[[event keyCode] & 0x1ff]; + window_data->key_status[key_code] = true; + kCall(keyboard_func, key_code, window_data->mod_keys, true); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)keyUp:(NSEvent *)event +{ + if(window_data != 0x0) { + short int key_code = g_keycodes[[event keyCode] & 0x1ff]; + window_data->key_status[key_code] = false; + kCall(keyboard_func, key_code, window_data->mod_keys, false); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)insertText:(id)string replacementRange:(NSRange)replacementRange +{ + kUnused(replacementRange); + + if(window_data != 0x0) { + NSString *characters; + NSUInteger length; + + if ([string isKindOfClass:[NSAttributedString class]]) + characters = [string string]; + else + characters = (NSString*) string; + + length = [characters length]; + for (NSUInteger i = 0; i < length; i++) + { + const unichar code = [characters characterAtIndex:i]; + if ((code & 0xff00) == 0xf700) + continue; + + kCall(char_input_func, code); + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)mainWindowChanged:(NSNotification *)notification +{ + kUnused(notification); + + if(window_data != 0x0) { + if(window_data->is_active == true) { + window_data->is_active = false; + kCall(active_func, false); + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)setContentView:(NSView *)aView +{ + if ([childContentView isEqualTo:aView]) { + return; + } + + NSRect bounds = [self frame]; + bounds.origin = NSZeroPoint; + + OSXView *frameView = [super contentView]; + if (!frameView) + { + frameView = [[[OSXView alloc] initWithFrame:bounds] autorelease]; + + [super setContentView:frameView]; + } + + if (childContentView) + { + [childContentView removeFromSuperview]; + } + childContentView = aView; + [childContentView setFrame:[self contentRectForFrameRect:bounds]]; + [childContentView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [frameView addSubview:childContentView]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSView *)contentView +{ + return childContentView; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)canBecomeKeyWindow +{ + return YES; +} + +- (void)windowDidBecomeKey:(NSNotification *)notification +{ + kUnused(notification); + if(window_data != 0x0) { + window_data->is_active = true; + kCall(active_func, true); + } +} + +- (void)windowDidResignKey:(NSNotification *)notification +{ + kUnused(notification); + if(window_data) { + window_data->is_active = false; + kCall(active_func, false); + } +} + +- (void)windowWillClose:(NSNotification *)notification { + kUnused(notification); + if(window_data) { + window_data->close = true; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)canBecomeMainWindow +{ + return YES; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSRect)contentRectForFrameRect:(NSRect)windowFrame +{ + windowFrame.origin = NSZeroPoint; + return NSInsetRect(windowFrame, 0, 0); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (NSRect)frameRectForContentRect:(NSRect)windowContentRect styleMask:(NSWindowStyleMask)windowStyle +{ + kUnused(windowStyle); + return NSInsetRect(windowContentRect, 0, 0); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)willClose +{ + if(window_data != 0x0) { + window_data->close = true; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)windowDidResize:(NSNotification *)notification { + kUnused(notification); + if(window_data != 0x0) { + CGSize size = [self contentRectForFrameRect:[self frame]].size; + + window_data->window_width = size.width; + window_data->window_height = size.height; + resize_dst(window_data, size.width, size.height); + + kCall(resize_func, size.width, size.height); + } +} + +@end diff --git a/lib/minifb/upstream/src/macosx/WindowData_OSX.h b/lib/minifb/upstream/src/macosx/WindowData_OSX.h new file mode 100644 index 0000000..d3dee7b --- /dev/null +++ b/lib/minifb/upstream/src/macosx/WindowData_OSX.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#if defined(USE_METAL_API) + #include +#endif + +@class OSXWindow; +@class OSXViewDelegate; + +typedef struct Vertex { + float x, y, z, w; +} Vertex; + +typedef struct { + OSXWindow *window; + OSXViewDelegate *viewController; + struct mfb_timer *timer; + +#if defined(USE_METAL_API) + struct { + Vertex vertices[4]; + } metal; +#endif +} SWindowData_OSX; diff --git a/lib/minifb/upstream/src/wayland/WaylandMiniFB.c b/lib/minifb/upstream/src/wayland/WaylandMiniFB.c new file mode 100644 index 0000000..b6387b7 --- /dev/null +++ b/lib/minifb/upstream/src/wayland/WaylandMiniFB.c @@ -0,0 +1,1030 @@ +#include +#include "MiniFB_internal.h" +#include "MiniFB_enums.h" +#include "WindowData.h" +#include "WindowData_Way.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +void init_keycodes(); + +static void +destroy_window_data(SWindowData *window_data) +{ + if(window_data == 0x0) + return; + + SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific; + if(window_data_way != 0x0) { + mfb_timer_destroy(window_data_way->timer); + memset(window_data_way, 0, sizeof(SWindowData_Way)); + free(window_data_way); + } + memset(window_data, 0, sizeof(SWindowData)); + free(window_data); +} + +static void +destroy(SWindowData *window_data) +{ + if(window_data == 0x0) + return; + + SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific; + if (window_data_way == 0x0 || window_data_way->display == 0x0) { + destroy_window_data(window_data); + return; + } + +#define KILL(NAME) \ + do \ + { \ + if (window_data_way->NAME) \ + wl_##NAME##_destroy(window_data_way->NAME); \ + } while (0); \ + window_data_way->NAME = 0x0; + + KILL(shell_surface); + KILL(shell); + KILL(surface); + //KILL(buffer); + if(window_data->draw_buffer) { + wl_buffer_destroy(window_data->draw_buffer); + window_data->draw_buffer = 0x0; + } + KILL(shm_pool); + KILL(shm); + KILL(compositor); + KILL(keyboard); + KILL(seat); + KILL(registry); +#undef KILL + wl_display_disconnect(window_data_way->display); + + destroy_window_data(window_data); + close(window_data_way->fd); +} + +// This event provides a file descriptor to the client which can be memory-mapped +// to provide a keyboard mapping description. +// format: keymap format +// fd: keymap file descriptor +// size: keymap size, in bytes +static void +keyboard_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) +{ + kUnused(data); + kUnused(keyboard); + kUnused(format); + kUnused(fd); + kUnused(size); +} + +// Notification that this seat's keyboard focus is on a certain surface. +// serial: serial number of the enter event +// surface: surface gaining keyboard focus +// keys: the currently pressed keys +static void +keyboard_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) +{ + kUnused(keyboard); + kUnused(serial); + kUnused(surface); + kUnused(keys); + + SWindowData *window_data = (SWindowData *) data; + window_data->is_active = true; + kCall(active_func, true); +} + +// The leave notification is sent before the enter notification for the new focus. +// serial: serial number of the leave event +// surface: surface that lost keyboard focus +static void +keyboard_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) +{ + kUnused(keyboard); + kUnused(serial); + kUnused(surface); + + SWindowData *window_data = (SWindowData *) data; + window_data->is_active = false; + kCall(active_func, false); +} + +// A key was pressed or released. The time argument is a timestamp with +// millisecond granularity, with an undefined base. +// serial: serial number of the key event +// time: timestamp with millisecond granularity +// key: key that produced the event +// state: physical state of the key +static void +keyboard_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + kUnused(keyboard); + kUnused(serial); + kUnused(time); + + SWindowData *window_data = (SWindowData *) data; + if(key < 512) { + mfb_key key_code = (mfb_key) g_keycodes[key]; + bool is_pressed = (bool) (state == WL_KEYBOARD_KEY_STATE_PRESSED); + switch (key_code) + { + case KB_KEY_LEFT_SHIFT: + case KB_KEY_RIGHT_SHIFT: + if(is_pressed) + window_data->mod_keys |= KB_MOD_SHIFT; + else + window_data->mod_keys &= ~KB_MOD_SHIFT; + break; + + case KB_KEY_LEFT_CONTROL: + case KB_KEY_RIGHT_CONTROL: + if(is_pressed) + window_data->mod_keys |= KB_MOD_CONTROL; + else + window_data->mod_keys &= ~KB_MOD_CONTROL; + break; + + case KB_KEY_LEFT_ALT: + case KB_KEY_RIGHT_ALT: + if(is_pressed) + window_data->mod_keys |= KB_MOD_ALT; + else + window_data->mod_keys &= ~KB_MOD_ALT; + break; + + case KB_KEY_LEFT_SUPER: + case KB_KEY_RIGHT_SUPER: + if(is_pressed) + window_data->mod_keys |= KB_MOD_SUPER; + else + window_data->mod_keys &= ~KB_MOD_SUPER; + break; + } + + window_data->key_status[key_code] = is_pressed; + kCall(keyboard_func, key_code, (mfb_key_mod) window_data->mod_keys, is_pressed); + } +} + +// Notifies clients that the modifier and/or group state has changed, +// and it should update its local state. +// serial: serial number of the modifiers event +// mods_depressed: depressed modifiers +// mods_latched: latched modifiers +// mods_locked: locked modifiers +// group: keyboard layout +static void +keyboard_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) +{ + kUnused(data); + kUnused(keyboard); + kUnused(serial); + kUnused(mods_depressed); + kUnused(mods_latched); + kUnused(mods_locked); + kUnused(group); + // it is not easy to identify them here :( +} + +// Informs the client about the keyboard's repeat rate and delay. +// rate: the rate of repeating keys in characters per second +// delay: delay in milliseconds since key down until repeating starts +static void +keyboard_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay) +{ + kUnused(data); + kUnused(keyboard); + kUnused(rate); + kUnused(delay); +} + +static const struct +wl_keyboard_listener keyboard_listener = { + .keymap = keyboard_keymap, + .enter = keyboard_enter, + .leave = keyboard_leave, + .key = keyboard_key, + .modifiers = keyboard_modifiers, + .repeat_info = 0x0, +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Notification that this seat's pointer is focused on a certain surface. +// +// When a seat's focus enters a surface, the pointer image is +// undefined and a client should respond to this event by setting +// an appropriate pointer image with the set_cursor request. +// +// serial: serial number of the enter event +// surface: surface entered by the pointer +// sx: surface-local x coordinate +// sy: surface-local y coordinate +static void +pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) +{ + //kUnused(pointer); + //kUnused(serial); + kUnused(surface); + kUnused(sx); + kUnused(sy); + struct wl_buffer *buffer; + struct wl_cursor_image *image; + + SWindowData *window_data = (SWindowData *) data; + SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific; + + image = window_data_way->default_cursor->images[0]; + buffer = wl_cursor_image_get_buffer(image); + + wl_pointer_set_cursor(pointer, serial, window_data_way->cursor_surface, image->hotspot_x, image->hotspot_y); + wl_surface_attach(window_data_way->cursor_surface, buffer, 0, 0); + wl_surface_damage(window_data_way->cursor_surface, 0, 0, image->width, image->height); + wl_surface_commit(window_data_way->cursor_surface); + //fprintf(stderr, "Pointer entered surface %p at %d %d\n", surface, sx, sy); +} + +// Notification that this seat's pointer is no longer focused on a certain surface. +// +// The leave notification is sent before the enter notification for the new focus. +// +// serial: serial number of the leave event +// surface: surface left by the pointer +static void +pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) +{ + kUnused(data); + kUnused(pointer); + kUnused(serial); + kUnused(surface); + + //fprintf(stderr, "Pointer left surface %p\n", surface); +} + +// Notification of pointer location change. +// +// The arguments sx and sy are the location relative to the focused surface. +// +// time: timestamp with millisecond granularity +// sx: surface-local x coordinate +// sy: surface-local y coordinate +static void +pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) +{ + kUnused(pointer); + kUnused(time); + + //printf("Pointer moved at %f %f\n", sx / 256.0f, sy / 256.0f); + SWindowData *window_data = (SWindowData *) data; + window_data->mouse_pos_x = sx >> 24; + window_data->mouse_pos_y = sy >> 24; + kCall(mouse_move_func, window_data->mouse_pos_x, window_data->mouse_pos_y); +} + +// Mouse button click and release notifications. +// +// The location of the click is given by the last motion or enter +// event. The time argument is a timestamp with millisecond +// granularity, with an undefined base. +// +// The button is a button code as defined in the Linux kernel's +// linux/input-event-codes.h header file, e.g. BTN_LEFT. +// +// Any 16-bit button code value is reserved for future additions to +// the kernel's event code list. All other button codes above +// 0xFFFF are currently undefined but may be used in future +// versions of this protocol. +// +// serial: serial number of the button event +// time: timestamp with millisecond granularity +// button: button that produced the event +// state: physical state of the button +static void +pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) +{ + kUnused(pointer); + kUnused(serial); + kUnused(time); + + //printf("Pointer button '%d'(%d)\n", button, state); + SWindowData *window_data = (SWindowData *) data; + window_data->mouse_button_status[(button - BTN_MOUSE + 1) & 0x07] = (state == 1); + kCall(mouse_btn_func, (mfb_mouse_button) (button - BTN_MOUSE + 1), (mfb_key_mod) window_data->mod_keys, state == 1); +} + +// Scroll and other axis notifications. +// +// For scroll events (vertical and horizontal scroll axes), the +// value parameter is the length of a vector along the specified +// axis in a coordinate space identical to those of motion events, +// representing a relative movement along the specified axis. +// +// For devices that support movements non-parallel to axes multiple +// axis events will be emitted. +// +// When applicable, for example for touch pads, the server can +// choose to emit scroll events where the motion vector is +// equivalent to a motion event vector. +// +// When applicable, a client can transform its content relative to +// the scroll distance. +// +// time: timestamp with millisecond granularity +// axis: axis type +// value: length of vector in surface-local coordinate space +static void +pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) +{ + kUnused(pointer); + kUnused(time); + kUnused(axis); + + //printf("Pointer handle axis: axis: %d (0x%x)\n", axis, value); + SWindowData *window_data = (SWindowData *) data; + if(axis == 0) { + window_data->mouse_wheel_y = -(value / 256.0f); + kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, 0.0f, window_data->mouse_wheel_y); + } + else if(axis == 1) { + window_data->mouse_wheel_x = -(value / 256.0f); + kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, window_data->mouse_wheel_x, 0.0f); + } +} + +static void +frame(void *data, struct wl_pointer *pointer) { + kUnused(data); + kUnused(pointer); +} + +static void +axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source) { + kUnused(data); + kUnused(pointer); + kUnused(axis_source); +} + +static void +axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis) { + kUnused(data); + kUnused(pointer); + kUnused(time); + kUnused(axis); +} + +static void +axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete) { + kUnused(data); + kUnused(pointer); + kUnused(axis); + kUnused(discrete); +} + +static const struct +wl_pointer_listener pointer_listener = { + .enter = pointer_enter, + .leave = pointer_leave, + .motion = pointer_motion, + .button = pointer_button, + .axis = pointer_axis, + .frame = 0x0, + .axis_source = 0x0, + .axis_stop = 0x0, + .axis_discrete = 0x0, +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void +seat_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) +{ + kUnused(data); + + SWindowData *window_data = (SWindowData *) data; + SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific; + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !window_data_way->keyboard) + { + window_data_way->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(window_data_way->keyboard, &keyboard_listener, window_data); + } + else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && window_data_way->keyboard) + { + wl_keyboard_destroy(window_data_way->keyboard); + window_data_way->keyboard = 0x0; + } + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !window_data_way->pointer) + { + window_data_way->pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(window_data_way->pointer, &pointer_listener, window_data); + } + else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && window_data_way->pointer) + { + wl_pointer_destroy(window_data_way->pointer); + window_data_way->pointer = 0x0; + } +} + +static void +seat_name(void *data, struct wl_seat *seat, const char *name) { + kUnused(data); + kUnused(seat); + printf("Seat '%s'n", name); +} + +static const struct +wl_seat_listener seat_listener = { + .capabilities = seat_capabilities, + .name = 0x0, +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// pixel format description +// +// Informs the client about a valid pixel format that can be used +// for buffers. Known formats include argb8888 and xrgb8888. +// +// format: buffer pixel format +static void +shm_format(void *data, struct wl_shm *shm, uint32_t format) +{ + kUnused(shm); + + SWindowData *window_data = (SWindowData *) data; + SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific; + if (window_data_way->shm_format == -1u) + { + switch (format) + { + // We could do RGBA, but that would not be what is expected from minifb... + // case WL_SHM_FORMAT_ARGB8888: + case WL_SHM_FORMAT_XRGB8888: + window_data_way->shm_format = format; + break; + + default: + break; + } + } +} + +static const struct +wl_shm_listener shm_listener = { + .format = shm_format +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void +registry_global(void *data, struct wl_registry *registry, uint32_t id, char const *iface, uint32_t version) +{ + kUnused(version); + + SWindowData *window_data = (SWindowData *) data; + SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific; + if (strcmp(iface, "wl_compositor") == 0) + { + window_data_way->compositor = (struct wl_compositor *) wl_registry_bind(registry, id, &wl_compositor_interface, 1); + } + else if (strcmp(iface, "wl_shm") == 0) + { + window_data_way->shm = (struct wl_shm *) wl_registry_bind(registry, id, &wl_shm_interface, 1); + if (window_data_way->shm) { + wl_shm_add_listener(window_data_way->shm, &shm_listener, window_data); + window_data_way->cursor_theme = wl_cursor_theme_load(0x0, 32, window_data_way->shm); + window_data_way->default_cursor = wl_cursor_theme_get_cursor(window_data_way->cursor_theme, "left_ptr"); + } + } + else if (strcmp(iface, "wl_shell") == 0) + { + window_data_way->shell = (struct wl_shell *) wl_registry_bind(registry, id, &wl_shell_interface, 1); + } + else if (strcmp(iface, "wl_seat") == 0) + { + window_data_way->seat = (struct wl_seat *) wl_registry_bind(registry, id, &wl_seat_interface, 1); + if (window_data_way->seat) + { + wl_seat_add_listener(window_data_way->seat, &seat_listener, window_data); + } + } +} + +static const struct +wl_registry_listener registry_listener = { + .global = registry_global, + .global_remove = 0x0, +}; + +static void +handle_ping(void *data, struct wl_shell_surface *shell_surface, uint32_t serial) +{ + kUnused(data); + wl_shell_surface_pong(shell_surface, serial); +} + +static void +handle_configure(void *data, struct wl_shell_surface *shell_surface, uint32_t edges, int32_t width, int32_t height) +{ + kUnused(data); + kUnused(shell_surface); + kUnused(edges); + kUnused(width); + kUnused(height); +} + +static void +handle_popup_done(void *data, struct wl_shell_surface *shell_surface) +{ + kUnused(data); + kUnused(shell_surface); +} + +static const struct wl_shell_surface_listener shell_surface_listener = { + handle_ping, + handle_configure, + handle_popup_done +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +struct mfb_window * +mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) +{ + SWindowData *window_data = (SWindowData *) malloc(sizeof(SWindowData)); + if(window_data == 0x0) { + return 0x0; + } + memset(window_data, 0, sizeof(SWindowData)); + + SWindowData_Way *window_data_way = (SWindowData_Way *) malloc(sizeof(SWindowData_Way)); + if(window_data_way == 0x0) { + free(window_data); + return 0x0; + } + memset(window_data_way, 0, sizeof(SWindowData_Way)); + window_data->specific = window_data_way; + + window_data_way->shm_format = -1u; + + window_data_way->display = wl_display_connect(0x0); + if (!window_data_way->display) { + free(window_data); + free(window_data_way); + return 0x0; + } + window_data_way->registry = wl_display_get_registry(window_data_way->display); + wl_registry_add_listener(window_data_way->registry, ®istry_listener, window_data); + + init_keycodes(); + + if (wl_display_dispatch(window_data_way->display) == -1 || + wl_display_roundtrip(window_data_way->display) == -1) { + return 0x0; + } + + // did not get a format we want... meh + if (window_data_way->shm_format == -1u) + goto out; + if (!window_data_way->compositor) + goto out; + + char const *xdg_rt_dir = getenv("XDG_RUNTIME_DIR"); + char shmfile[PATH_MAX]; + uint32_t ret = snprintf(shmfile, sizeof(shmfile), "%s/WaylandMiniFB-SHM-XXXXXX", xdg_rt_dir); + if (ret >= sizeof(shmfile)) + goto out; + + window_data_way->fd = mkstemp(shmfile); + if (window_data_way->fd == -1) + goto out; + unlink(shmfile); + + uint32_t length = sizeof(uint32_t) * width * height; + + if (ftruncate(window_data_way->fd, length) == -1) + goto out; + + window_data_way->shm_ptr = (uint32_t *) mmap(0x0, length, PROT_WRITE, MAP_SHARED, window_data_way->fd, 0); + if (window_data_way->shm_ptr == MAP_FAILED) + goto out; + + window_data->window_width = width; + window_data->window_height = height; + window_data->buffer_width = width; + window_data->buffer_height = height; + window_data->buffer_stride = width * sizeof(uint32_t); + calc_dst_factor(window_data, width, height); + + window_data_way->shm_pool = wl_shm_create_pool(window_data_way->shm, window_data_way->fd, length); + window_data->draw_buffer = wl_shm_pool_create_buffer(window_data_way->shm_pool, 0, + window_data->buffer_width, window_data->buffer_height, + window_data->buffer_stride, window_data_way->shm_format); + + window_data_way->surface = wl_compositor_create_surface(window_data_way->compositor); + if (!window_data_way->surface) + goto out; + + window_data_way->cursor_surface = wl_compositor_create_surface(window_data_way->compositor); + + // There should always be a shell, right? + if (window_data_way->shell) + { + window_data_way->shell_surface = wl_shell_get_shell_surface(window_data_way->shell, window_data_way->surface); + if (!window_data_way->shell_surface) + goto out; + + wl_shell_surface_set_title(window_data_way->shell_surface, title); + wl_shell_surface_add_listener(window_data_way->shell_surface, &shell_surface_listener, 0x0); + wl_shell_surface_set_toplevel(window_data_way->shell_surface); + } + + wl_surface_attach(window_data_way->surface, (struct wl_buffer *) window_data->draw_buffer, window_data->dst_offset_x, window_data->dst_offset_y); + wl_surface_damage(window_data_way->surface, window_data->dst_offset_x, window_data->dst_offset_y, window_data->dst_width, window_data->dst_height); + wl_surface_commit(window_data_way->surface); + + window_data_way->timer = mfb_timer_create(); + + mfb_set_keyboard_callback((struct mfb_window *) window_data, keyboard_default); + +#if defined(_DEBUG) || defined(DEBUG) + printf("Window created using Wayland API\n"); +#endif + + window_data->is_initialized = true; + return (struct mfb_window *) window_data; + +out: + close(window_data_way->fd); + destroy(window_data); + + return 0x0; +} + +// done event +// +// Notify the client when the related request is done. +// +// callback_data: request-specific data for the callback +static void +frame_done(void *data, struct wl_callback *callback, uint32_t cookie) +{ + kUnused(cookie); + wl_callback_destroy(callback); + + *(uint32_t *)data = 1; +} + +static const struct +wl_callback_listener frame_listener = { + .done = frame_done, +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +mfb_update_state +mfb_update_ex(struct mfb_window *window, void *buffer, unsigned width, unsigned height) +{ + uint32_t done = 0; + + if(window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + if(window_data->close) { + destroy(window_data); + return STATE_EXIT; + } + + if(buffer == 0x0) { + return STATE_INVALID_BUFFER; + } + + SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific; + if (!window_data_way->display || wl_display_get_error(window_data_way->display) != 0) + return STATE_INTERNAL_ERROR; + + if(window_data->buffer_width != width || window_data->buffer_height != height) { + uint32_t oldLength = sizeof(uint32_t) * window_data->buffer_width * window_data->buffer_height; + uint32_t length = sizeof(uint32_t) * width * height; + + // For some reason it crash when you make it smaller + if(oldLength < length) { + if (ftruncate(window_data_way->fd, length) == -1) + return STATE_INTERNAL_ERROR; + + //munmap(window_data_way->shm_ptr, sizeof(uint32_t) * window_data->buffer_width * window_data->buffer_height); + window_data_way->shm_ptr = (uint32_t *) mmap(0x0, length, PROT_WRITE, MAP_SHARED, window_data_way->fd, 0); + if (window_data_way->shm_ptr == MAP_FAILED) + return STATE_INTERNAL_ERROR; + + wl_shm_pool_resize(window_data_way->shm_pool, length); + } + + window_data->buffer_width = width; + window_data->buffer_height = height; + window_data->buffer_stride = width * sizeof(uint32_t); + + // This must be in the resize event but we don't have it for Wayland :( + resize_dst(window_data, width, height); + + wl_buffer_destroy(window_data->draw_buffer); + window_data->draw_buffer = wl_shm_pool_create_buffer(window_data_way->shm_pool, 0, + window_data->buffer_width, window_data->buffer_height, + window_data->buffer_stride, window_data_way->shm_format); + } + + // update shm buffer + memcpy(window_data_way->shm_ptr, buffer, window_data->buffer_stride * window_data->buffer_height); + + wl_surface_attach(window_data_way->surface, (struct wl_buffer *) window_data->draw_buffer, window_data->dst_offset_x, window_data->dst_offset_y); + wl_surface_damage(window_data_way->surface, window_data->dst_offset_x, window_data->dst_offset_y, window_data->dst_width, window_data->dst_height); + struct wl_callback *frame_callback = wl_surface_frame(window_data_way->surface); + if (!frame_callback) { + return STATE_INTERNAL_ERROR; + } + wl_callback_add_listener(frame_callback, &frame_listener, &done); + wl_surface_commit(window_data_way->surface); + + while (!done && window_data->close == false) { + if (wl_display_dispatch(window_data_way->display) == -1 || wl_display_roundtrip(window_data_way->display) == -1) { + wl_callback_destroy(frame_callback); + return STATE_INTERNAL_ERROR; + } + } + + return STATE_OK; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +mfb_update_state +mfb_update_events(struct mfb_window *window) +{ + if(window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + if(window_data->close) { + destroy(window_data); + return STATE_EXIT; + } + + SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific; + if (!window_data_way->display || wl_display_get_error(window_data_way->display) != 0) + return STATE_INTERNAL_ERROR; + + if (wl_display_dispatch_pending(window_data_way->display) == -1) { + return STATE_INTERNAL_ERROR; + } + + return STATE_OK; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +extern double g_time_for_frame; + +bool +mfb_wait_sync(struct mfb_window *window) { + if(window == 0x0) { + return false; + } + + SWindowData *window_data = (SWindowData *) window; + if(window_data->close) { + destroy(window_data); + return false; + } + + SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific; + double current; + uint32_t millis = 1; + while(1) { + current = mfb_timer_now(window_data_way->timer); + if (current >= g_time_for_frame * 0.96) { + mfb_timer_reset(window_data_way->timer); + return true; + } + else if(current >= g_time_for_frame * 0.8) { + millis = 0; + } + + usleep(millis * 1000); + //sched_yield(); + + if(millis == 1) { + if (wl_display_dispatch_pending(window_data_way->display) == -1) { + return false; + } + + if(window_data->close) { + destroy_window_data(window_data); + return false; + } + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +extern short int g_keycodes[512]; + +void +init_keycodes(void) +{ + // Clear keys + for (size_t i = 0; i < sizeof(g_keycodes) / sizeof(g_keycodes[0]); ++i) + g_keycodes[i] = 0; + + g_keycodes[KEY_GRAVE] = KB_KEY_GRAVE_ACCENT; + g_keycodes[KEY_1] = KB_KEY_1; + g_keycodes[KEY_2] = KB_KEY_2; + g_keycodes[KEY_3] = KB_KEY_3; + g_keycodes[KEY_4] = KB_KEY_4; + g_keycodes[KEY_5] = KB_KEY_5; + g_keycodes[KEY_6] = KB_KEY_6; + g_keycodes[KEY_7] = KB_KEY_7; + g_keycodes[KEY_8] = KB_KEY_8; + g_keycodes[KEY_9] = KB_KEY_9; + g_keycodes[KEY_0] = KB_KEY_0; + g_keycodes[KEY_SPACE] = KB_KEY_SPACE; + g_keycodes[KEY_MINUS] = KB_KEY_MINUS; + g_keycodes[KEY_EQUAL] = KB_KEY_EQUAL; + g_keycodes[KEY_Q] = KB_KEY_Q; + g_keycodes[KEY_W] = KB_KEY_W; + g_keycodes[KEY_E] = KB_KEY_E; + g_keycodes[KEY_R] = KB_KEY_R; + g_keycodes[KEY_T] = KB_KEY_T; + g_keycodes[KEY_Y] = KB_KEY_Y; + g_keycodes[KEY_U] = KB_KEY_U; + g_keycodes[KEY_I] = KB_KEY_I; + g_keycodes[KEY_O] = KB_KEY_O; + g_keycodes[KEY_P] = KB_KEY_P; + g_keycodes[KEY_LEFTBRACE] = KB_KEY_LEFT_BRACKET; + g_keycodes[KEY_RIGHTBRACE] = KB_KEY_RIGHT_BRACKET; + g_keycodes[KEY_A] = KB_KEY_A; + g_keycodes[KEY_S] = KB_KEY_S; + g_keycodes[KEY_D] = KB_KEY_D; + g_keycodes[KEY_F] = KB_KEY_F; + g_keycodes[KEY_G] = KB_KEY_G; + g_keycodes[KEY_H] = KB_KEY_H; + g_keycodes[KEY_J] = KB_KEY_J; + g_keycodes[KEY_K] = KB_KEY_K; + g_keycodes[KEY_L] = KB_KEY_L; + g_keycodes[KEY_SEMICOLON] = KB_KEY_SEMICOLON; + g_keycodes[KEY_APOSTROPHE] = KB_KEY_APOSTROPHE; + g_keycodes[KEY_Z] = KB_KEY_Z; + g_keycodes[KEY_X] = KB_KEY_X; + g_keycodes[KEY_C] = KB_KEY_C; + g_keycodes[KEY_V] = KB_KEY_V; + g_keycodes[KEY_B] = KB_KEY_B; + g_keycodes[KEY_N] = KB_KEY_N; + g_keycodes[KEY_M] = KB_KEY_M; + g_keycodes[KEY_COMMA] = KB_KEY_COMMA; + g_keycodes[KEY_DOT] = KB_KEY_PERIOD; + g_keycodes[KEY_SLASH] = KB_KEY_SLASH; + g_keycodes[KEY_BACKSLASH] = KB_KEY_BACKSLASH; + g_keycodes[KEY_ESC] = KB_KEY_ESCAPE; + g_keycodes[KEY_TAB] = KB_KEY_TAB; + g_keycodes[KEY_LEFTSHIFT] = KB_KEY_LEFT_SHIFT; + g_keycodes[KEY_RIGHTSHIFT] = KB_KEY_RIGHT_SHIFT; + g_keycodes[KEY_LEFTCTRL] = KB_KEY_LEFT_CONTROL; + g_keycodes[KEY_RIGHTCTRL] = KB_KEY_RIGHT_CONTROL; + g_keycodes[KEY_LEFTALT] = KB_KEY_LEFT_ALT; + g_keycodes[KEY_RIGHTALT] = KB_KEY_RIGHT_ALT; + g_keycodes[KEY_LEFTMETA] = KB_KEY_LEFT_SUPER; + g_keycodes[KEY_RIGHTMETA] = KB_KEY_RIGHT_SUPER; + g_keycodes[KEY_MENU] = KB_KEY_MENU; + g_keycodes[KEY_NUMLOCK] = KB_KEY_NUM_LOCK; + g_keycodes[KEY_CAPSLOCK] = KB_KEY_CAPS_LOCK; + g_keycodes[KEY_PRINT] = KB_KEY_PRINT_SCREEN; + g_keycodes[KEY_SCROLLLOCK] = KB_KEY_SCROLL_LOCK; + g_keycodes[KEY_PAUSE] = KB_KEY_PAUSE; + g_keycodes[KEY_DELETE] = KB_KEY_DELETE; + g_keycodes[KEY_BACKSPACE] = KB_KEY_BACKSPACE; + g_keycodes[KEY_ENTER] = KB_KEY_ENTER; + g_keycodes[KEY_HOME] = KB_KEY_HOME; + g_keycodes[KEY_END] = KB_KEY_END; + g_keycodes[KEY_PAGEUP] = KB_KEY_PAGE_UP; + g_keycodes[KEY_PAGEDOWN] = KB_KEY_PAGE_DOWN; + g_keycodes[KEY_INSERT] = KB_KEY_INSERT; + g_keycodes[KEY_LEFT] = KB_KEY_LEFT; + g_keycodes[KEY_RIGHT] = KB_KEY_RIGHT; + g_keycodes[KEY_DOWN] = KB_KEY_DOWN; + g_keycodes[KEY_UP] = KB_KEY_UP; + g_keycodes[KEY_F1] = KB_KEY_F1; + g_keycodes[KEY_F2] = KB_KEY_F2; + g_keycodes[KEY_F3] = KB_KEY_F3; + g_keycodes[KEY_F4] = KB_KEY_F4; + g_keycodes[KEY_F5] = KB_KEY_F5; + g_keycodes[KEY_F6] = KB_KEY_F6; + g_keycodes[KEY_F7] = KB_KEY_F7; + g_keycodes[KEY_F8] = KB_KEY_F8; + g_keycodes[KEY_F9] = KB_KEY_F9; + g_keycodes[KEY_F10] = KB_KEY_F10; + g_keycodes[KEY_F11] = KB_KEY_F11; + g_keycodes[KEY_F12] = KB_KEY_F12; + g_keycodes[KEY_F13] = KB_KEY_F13; + g_keycodes[KEY_F14] = KB_KEY_F14; + g_keycodes[KEY_F15] = KB_KEY_F15; + g_keycodes[KEY_F16] = KB_KEY_F16; + g_keycodes[KEY_F17] = KB_KEY_F17; + g_keycodes[KEY_F18] = KB_KEY_F18; + g_keycodes[KEY_F19] = KB_KEY_F19; + g_keycodes[KEY_F20] = KB_KEY_F20; + g_keycodes[KEY_F21] = KB_KEY_F21; + g_keycodes[KEY_F22] = KB_KEY_F22; + g_keycodes[KEY_F23] = KB_KEY_F23; + g_keycodes[KEY_F24] = KB_KEY_F24; + g_keycodes[KEY_KPSLASH] = KB_KEY_KP_DIVIDE; + g_keycodes[KEY_KPDOT] = KB_KEY_KP_MULTIPLY; + g_keycodes[KEY_KPMINUS] = KB_KEY_KP_SUBTRACT; + g_keycodes[KEY_KPPLUS] = KB_KEY_KP_ADD; + g_keycodes[KEY_KP0] = KB_KEY_KP_0; + g_keycodes[KEY_KP1] = KB_KEY_KP_1; + g_keycodes[KEY_KP2] = KB_KEY_KP_2; + g_keycodes[KEY_KP3] = KB_KEY_KP_3; + g_keycodes[KEY_KP4] = KB_KEY_KP_4; + g_keycodes[KEY_KP5] = KB_KEY_KP_5; + g_keycodes[KEY_KP6] = KB_KEY_KP_6; + g_keycodes[KEY_KP7] = KB_KEY_KP_7; + g_keycodes[KEY_KP8] = KB_KEY_KP_8; + g_keycodes[KEY_KP9] = KB_KEY_KP_9; + g_keycodes[KEY_KPCOMMA] = KB_KEY_KP_DECIMAL; + g_keycodes[KEY_KPEQUAL] = KB_KEY_KP_EQUAL; + g_keycodes[KEY_KPENTER] = KB_KEY_KP_ENTER; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +bool +mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height) { + + SWindowData *window_data = (SWindowData *) window; + + if(offset_x + width > window_data->window_width) { + return false; + } + if(offset_y + height > window_data->window_height) { + return false; + } + + // TODO: Not yet + window_data->dst_offset_x = offset_x; + window_data->dst_offset_y = offset_y; + window_data->dst_width = width; + window_data->dst_height = height; + resize_dst(window_data, width, height); + + return false; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void +mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y) { + float x = 96.0, y = 96.0; + + if(window != 0x0) { + //SWindowData *window_data = (SWindowData *) window; + //SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; + + // I cannot find a way to get dpi under VirtualBox + } + + if (scale_x) { + *scale_x = x / 96.0f; + if(*scale_x == 0) { + *scale_x = 1.0f; + } + } + + if (scale_y) { + *scale_y = y / 96.0f; + if (*scale_y == 0) { + *scale_y = 1.0f; + } + } +} diff --git a/lib/minifb/upstream/src/wayland/WindowData_Way.h b/lib/minifb/upstream/src/wayland/WindowData_Way.h new file mode 100644 index 0000000..ed1a5ba --- /dev/null +++ b/lib/minifb/upstream/src/wayland/WindowData_Way.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +struct wl_display; +struct wl_registry; +struct wl_compositor; +struct wl_shell; +struct wl_seat; +struct wl_keyboard; +struct wl_pointer; +struct wl_callback; +struct wl_shm; +struct wl_shm_pool; +struct wl_surface; +struct wl_shell_surface; +struct wl_buffer; + +typedef struct +{ + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct wl_seat *seat; + struct wl_keyboard *keyboard; + + struct wl_pointer *pointer; + struct wl_cursor_theme *cursor_theme; + struct wl_cursor *default_cursor; + struct wl_surface *cursor_surface; + + struct wl_shm *shm; + struct wl_shm_pool *shm_pool; + struct wl_surface *surface; + struct wl_shell_surface *shell_surface; + + uint32_t seat_version; + uint32_t shm_format; + uint32_t *shm_ptr; + + int fd; + + struct mfb_timer *timer; +} SWindowData_Way; diff --git a/lib/minifb/upstream/src/windows/WinMiniFB.c b/lib/minifb/upstream/src/windows/WinMiniFB.c new file mode 100644 index 0000000..5fcc5c1 --- /dev/null +++ b/lib/minifb/upstream/src/windows/WinMiniFB.c @@ -0,0 +1,973 @@ +#include +#include +#include +#include "WindowData_Win.h" +#if defined(USE_OPENGL_API) + #include "gl/MiniFB_GL.h" +#endif +#include +#include +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Copied (and modified) from Windows Kit 10 to avoid setting _WIN32_WINNT to a higher version +typedef enum mfb_PROCESS_DPI_AWARENESS { + mfb_PROCESS_DPI_UNAWARE = 0, + mfb_PROCESS_SYSTEM_DPI_AWARE = 1, + mfb_PROCESS_PER_MONITOR_DPI_AWARE = 2 +} mfb_PROCESS_DPI_AWARENESS; + +typedef enum mfb_MONITOR_DPI_TYPE { + mfb_MDT_EFFECTIVE_DPI = 0, + mfb_MDT_ANGULAR_DPI = 1, + mfb_MDT_RAW_DPI = 2, + mfb_MDT_DEFAULT = mfb_MDT_EFFECTIVE_DPI +} mfb_MONITOR_DPI_TYPE; + +#define mfb_DPI_AWARENESS_CONTEXT_UNAWARE ((HANDLE) -1) +#define mfb_DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((HANDLE) -2) +#define mfb_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((HANDLE) -3) +#define mfb_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((HANDLE) -4) +#define mfb_DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((HANDLE) -5) + +// user32.dll +typedef BOOL(WINAPI *PFN_SetProcessDPIAware)(void); +typedef BOOL(WINAPI *PFN_SetProcessDpiAwarenessContext)(HANDLE); +typedef UINT(WINAPI *PFN_GetDpiForWindow)(HWND); +typedef BOOL(WINAPI *PFN_EnableNonClientDpiScaling)(HWND); + +HMODULE mfb_user32_dll = 0x0; +PFN_SetProcessDPIAware mfb_SetProcessDPIAware = 0x0; +PFN_SetProcessDpiAwarenessContext mfb_SetProcessDpiAwarenessContext = 0x0; +PFN_GetDpiForWindow mfb_GetDpiForWindow = 0x0; +PFN_EnableNonClientDpiScaling mfb_EnableNonClientDpiScaling = 0x0; + +// shcore.dll +typedef HRESULT(WINAPI *PFN_SetProcessDpiAwareness)(mfb_PROCESS_DPI_AWARENESS); +typedef HRESULT(WINAPI *PFN_GetDpiForMonitor)(HMONITOR, mfb_MONITOR_DPI_TYPE, UINT *, UINT *); + +HMODULE mfb_shcore_dll = 0x0; +PFN_SetProcessDpiAwareness mfb_SetProcessDpiAwareness = 0x0; +PFN_GetDpiForMonitor mfb_GetDpiForMonitor = 0x0; + +//-- +void +load_functions() { + if(mfb_user32_dll == 0x0) { + mfb_user32_dll = LoadLibraryA("user32.dll"); + if (mfb_user32_dll != 0x0) { + mfb_SetProcessDPIAware = (PFN_SetProcessDPIAware) GetProcAddress(mfb_user32_dll, "SetProcessDPIAware"); + mfb_SetProcessDpiAwarenessContext = (PFN_SetProcessDpiAwarenessContext) GetProcAddress(mfb_user32_dll, "SetProcessDpiAwarenessContext"); + mfb_GetDpiForWindow = (PFN_GetDpiForWindow) GetProcAddress(mfb_user32_dll, "GetDpiForWindow"); + mfb_EnableNonClientDpiScaling = (PFN_EnableNonClientDpiScaling) GetProcAddress(mfb_user32_dll, "EnableNonClientDpiScaling"); + } + } + + if(mfb_shcore_dll == 0x0) { + mfb_shcore_dll = LoadLibraryA("shcore.dll"); + if (mfb_shcore_dll != 0x0) { + mfb_SetProcessDpiAwareness = (PFN_SetProcessDpiAwareness) GetProcAddress(mfb_shcore_dll, "SetProcessDpiAwareness"); + mfb_GetDpiForMonitor = (PFN_GetDpiForMonitor) GetProcAddress(mfb_shcore_dll, "GetDpiForMonitor"); + } + } +} + +//-- +// NOT Thread safe. Just convenient (Don't do this at home guys) +char * +GetErrorMessage() { + static char buffer[256]; + + buffer[0] = 0; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, // Not used with FORMAT_MESSAGE_FROM_SYSTEM + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, + sizeof(buffer), + NULL); + + return buffer; +} + +//-- +void +dpi_aware() { + if (mfb_SetProcessDpiAwarenessContext != 0x0) { + if(mfb_SetProcessDpiAwarenessContext(mfb_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) == false) { + uint32_t error = GetLastError(); + if(error == ERROR_INVALID_PARAMETER) { + error = NO_ERROR; + if(mfb_SetProcessDpiAwarenessContext(mfb_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE) == false) { + error = GetLastError(); + } + } + if(error != NO_ERROR) { + fprintf(stderr, "Error (SetProcessDpiAwarenessContext): %s\n", GetErrorMessage()); + } + } + } + else if (mfb_SetProcessDpiAwareness != 0x0) { + if(mfb_SetProcessDpiAwareness(mfb_PROCESS_PER_MONITOR_DPI_AWARE) != S_OK) { + fprintf(stderr, "Error (SetProcessDpiAwareness): %s\n", GetErrorMessage()); + } + } + else if (mfb_SetProcessDPIAware != 0x0) { + if(mfb_SetProcessDPIAware() == false) { + fprintf(stderr, "Error (SetProcessDPIAware): %s\n", GetErrorMessage()); + } + } +} + +//-- +void +get_monitor_scale(HWND hWnd, float *scale_x, float *scale_y) { + UINT x, y; + + if(mfb_GetDpiForMonitor != 0x0) { + HMONITOR monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); + mfb_GetDpiForMonitor(monitor, mfb_MDT_EFFECTIVE_DPI, &x, &y); + } + else { + const HDC dc = GetDC(hWnd); + x = GetDeviceCaps(dc, LOGPIXELSX); + y = GetDeviceCaps(dc, LOGPIXELSY); + ReleaseDC(NULL, dc); + } + + if (scale_x) { + *scale_x = x / (float) USER_DEFAULT_SCREEN_DPI; + if(*scale_x == 0) { + *scale_x = 1; + } + } + + if (scale_y) { + *scale_y = y / (float) USER_DEFAULT_SCREEN_DPI; + if (*scale_y == 0) { + *scale_y = 1; + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void +mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y) { + HWND hWnd = 0x0; + + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + SWindowData_Win *window_data_win = (SWindowData_Win *) window_data->specific; + hWnd = window_data_win->window; + } + get_monitor_scale(hWnd, scale_x, scale_y); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +long s_window_style = WS_POPUP | WS_SYSMENU | WS_CAPTION; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void init_keycodes(); + +uint32_t translate_mod(); +mfb_key translate_key(unsigned int wParam, unsigned long lParam); +void destroy_window_data(SWindowData *window_data); + +LRESULT CALLBACK +WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + LRESULT res = 0; + + SWindowData *window_data = (SWindowData *) GetWindowLongPtr(hWnd, GWLP_USERDATA); + SWindowData_Win *window_data_win = 0x0; + if (window_data != 0x0) { + window_data_win = (SWindowData_Win *) window_data->specific; + } + + switch (message) + { + case WM_NCCREATE: + { + if(mfb_EnableNonClientDpiScaling) + mfb_EnableNonClientDpiScaling(hWnd); + + return DefWindowProc(hWnd, message, wParam, lParam); + } + + // TODO + //case 0x02E4://WM_GETDPISCALEDSIZE: + //{ + // SIZE* size = (SIZE*) lParam; + // WORD dpi = LOWORD(wParam); + // return true; + // break; + //} + + // TODO + //case WM_DPICHANGED: + //{ + // const float xscale = HIWORD(wParam); + // const float yscale = LOWORD(wParam); + // break; + //} + +#if !defined(USE_OPENGL_API) + case WM_PAINT: + { + if (window_data && window_data->draw_buffer && window_data_win) { + StretchDIBits(window_data_win->hdc, window_data->dst_offset_x, window_data->dst_offset_y, window_data->dst_width, window_data->dst_height, 0, 0, window_data->buffer_width, window_data->buffer_height, window_data->draw_buffer, + window_data_win->bitmapInfo, DIB_RGB_COLORS, SRCCOPY); + } + ValidateRect(hWnd, 0x0); + break; + } +#endif + + case WM_DESTROY: + case WM_CLOSE: + { + if (window_data) { + window_data->close = true; + } + break; + } + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + { + if (window_data) { + mfb_key key_code = translate_key((unsigned int)wParam, (unsigned long)lParam); + int is_pressed = !((lParam >> 31) & 1); + window_data->mod_keys = translate_mod(); + + if (key_code == KB_KEY_UNKNOWN) + return FALSE; + + window_data->key_status[key_code] = (uint8_t) is_pressed; + kCall(keyboard_func, key_code, window_data->mod_keys, is_pressed); + } + break; + } + + case WM_CHAR: + case WM_SYSCHAR: + case WM_UNICHAR: + { + + if (window_data) { + if (message == WM_UNICHAR && wParam == UNICODE_NOCHAR) { + // WM_UNICHAR is not sent by Windows, but is sent by some third-party input method engine + // Returning TRUE here announces support for this message + return TRUE; + } + + kCall(char_input_func, (unsigned int) wParam); + } + break; + } + + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_XBUTTONUP: + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONDBLCLK: + case WM_XBUTTONDOWN: + case WM_XBUTTONDBLCLK: + { + if (window_data) { + mfb_mouse_button button = MOUSE_BTN_0; + window_data->mod_keys = translate_mod(); + int is_pressed = 0; + switch(message) { + case WM_LBUTTONDOWN: + is_pressed = 1; + case WM_LBUTTONUP: + button = MOUSE_BTN_1; + break; + case WM_RBUTTONDOWN: + is_pressed = 1; + case WM_RBUTTONUP: + button = MOUSE_BTN_2; + break; + case WM_MBUTTONDOWN: + is_pressed = 1; + case WM_MBUTTONUP: + button = MOUSE_BTN_3; + break; + + default: + button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? MOUSE_BTN_5 : MOUSE_BTN_6); + if (message == WM_XBUTTONDOWN) { + is_pressed = 1; + } + } + window_data->mouse_button_status[button & 0x07] = is_pressed; + kCall(mouse_btn_func, button, window_data->mod_keys, is_pressed); + } + break; + } + + case WM_MOUSEWHEEL: + if (window_data) { + window_data->mouse_wheel_y = (SHORT)HIWORD(wParam) / (float)WHEEL_DELTA; + kCall(mouse_wheel_func, translate_mod(), 0.0f, window_data->mouse_wheel_y); + } + break; + + case WM_MOUSEHWHEEL: + // This message is only sent on Windows Vista and later + // NOTE: The X-axis is inverted for consistency with macOS and X11 + if (window_data) { + window_data->mouse_wheel_x = -((SHORT)HIWORD(wParam) / (float)WHEEL_DELTA); + kCall(mouse_wheel_func, translate_mod(), window_data->mouse_wheel_x, 0.0f); + } + break; + + case WM_MOUSEMOVE: + if (window_data) { + if (window_data_win->mouse_inside == false) { + window_data_win->mouse_inside = true; + TRACKMOUSEEVENT tme; + ZeroMemory(&tme, sizeof(tme)); + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hWnd; + TrackMouseEvent(&tme); + } + window_data->mouse_pos_x = (int)(short) LOWORD(lParam); + window_data->mouse_pos_y = (int)(short) HIWORD(lParam); + kCall(mouse_move_func, window_data->mouse_pos_x, window_data->mouse_pos_y); + } + break; + + case WM_MOUSELEAVE: + if (window_data) { + window_data_win->mouse_inside = false; + } + break; + + case WM_SIZE: + if (window_data) { + float scale_x, scale_y; + uint32_t width, height; + + if(wParam == SIZE_MINIMIZED) { + return res; + } + + get_monitor_scale(hWnd, &scale_x, &scale_y); + window_data->window_width = LOWORD(lParam); + window_data->window_height = HIWORD(lParam); + resize_dst(window_data, window_data->window_width, window_data->window_height); + +#if !defined(USE_OPENGL_API) + BitBlt(window_data_win->hdc, 0, 0, window_data->window_width, window_data->window_height, 0, 0, 0, BLACKNESS); +#else + resize_GL(window_data); +#endif + if(window_data->window_width != 0 && window_data->window_height != 0) { + width = (uint32_t) (window_data->window_width / scale_x); + height = (uint32_t) (window_data->window_height / scale_y); + kCall(resize_func, width, height); + } + } + break; + + case WM_SETFOCUS: + if (window_data) { + window_data->is_active = true; + kCall(active_func, true); + } + break; + + case WM_KILLFOCUS: + if (window_data) { + window_data->is_active = false; + kCall(active_func, false); + } + break; + + default: + { + res = DefWindowProc(hWnd, message, wParam, lParam); + } + } + + return res; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +struct mfb_window * +mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) { + RECT rect = { 0 }; + int x = 0, y = 0; + + load_functions(); + dpi_aware(); + init_keycodes(); + + SWindowData *window_data = malloc(sizeof(SWindowData)); + if (window_data == 0x0) { + return 0x0; + } + memset(window_data, 0, sizeof(SWindowData)); + + SWindowData_Win *window_data_win = malloc(sizeof(SWindowData_Win)); + if(window_data_win == 0x0) { + free(window_data); + return 0x0; + } + memset(window_data_win, 0, sizeof(SWindowData_Win)); + window_data->specific = window_data_win; + + window_data->buffer_width = width; + window_data->buffer_height = height; + window_data->buffer_stride = width * 4; + + s_window_style = WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME; + if (flags & WF_FULLSCREEN) { + flags = WF_FULLSCREEN; // Remove all other flags + rect.right = GetSystemMetrics(SM_CXSCREEN); + rect.bottom = GetSystemMetrics(SM_CYSCREEN); + s_window_style = WS_POPUP & ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU); + + DEVMODE settings = { 0 }; + EnumDisplaySettings(0, 0, &settings); + settings.dmPelsWidth = GetSystemMetrics(SM_CXSCREEN); + settings.dmPelsHeight = GetSystemMetrics(SM_CYSCREEN); + settings.dmBitsPerPel = 32; + settings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + + if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { + flags = WF_FULLSCREEN_DESKTOP; + } + } + + if (flags & WF_BORDERLESS) { + s_window_style = WS_POPUP; + } + + if (flags & WF_RESIZABLE) { + s_window_style |= WS_MAXIMIZEBOX | WS_SIZEBOX; + } + + if (flags & WF_FULLSCREEN_DESKTOP) { + s_window_style = WS_OVERLAPPEDWINDOW; + + width = GetSystemMetrics(SM_CXFULLSCREEN); + height = GetSystemMetrics(SM_CYFULLSCREEN); + + rect.right = width; + rect.bottom = height; + AdjustWindowRect(&rect, s_window_style, 0); + if (rect.left < 0) { + width += rect.left * 2; + rect.right += rect.left; + rect.left = 0; + } + if (rect.bottom > (LONG) height) { + height -= (rect.bottom - height); + rect.bottom += (rect.bottom - height); + rect.top = 0; + } + } + else if (!(flags & WF_FULLSCREEN)) { + float scale_x, scale_y; + + get_monitor_scale(0, &scale_x, &scale_y); + + rect.right = (LONG) (width * scale_x); + rect.bottom = (LONG) (height * scale_y); + + AdjustWindowRect(&rect, s_window_style, 0); + + rect.right -= rect.left; + rect.bottom -= rect.top; + + x = (GetSystemMetrics(SM_CXSCREEN) - rect.right) / 2; + y = (GetSystemMetrics(SM_CYSCREEN) - rect.bottom + rect.top) / 2; + } + + window_data_win->wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW; + window_data_win->wc.lpfnWndProc = WndProc; + window_data_win->wc.hCursor = LoadCursor(0, IDC_ARROW); + window_data_win->wc.lpszClassName = title; + RegisterClass(&window_data_win->wc); + + calc_dst_factor(window_data, width, height); + + window_data->window_width = rect.right; + window_data->window_height = rect.bottom; + + window_data_win->window = CreateWindowEx( + 0, + title, title, + s_window_style, + x, y, + window_data->window_width, window_data->window_height, + 0, 0, 0, 0); + + if (!window_data_win->window) { + free(window_data); + free(window_data_win); + return 0x0; + } + + SetWindowLongPtr(window_data_win->window, GWLP_USERDATA, (LONG_PTR) window_data); + + if (flags & WF_ALWAYS_ON_TOP) + SetWindowPos(window_data_win->window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + + ShowWindow(window_data_win->window, SW_NORMAL); + + window_data_win->hdc = GetDC(window_data_win->window); + +#if !defined(USE_OPENGL_API) + + window_data_win->bitmapInfo = (BITMAPINFO *) calloc(1, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 3); + if(window_data_win->bitmapInfo == 0x0) { + free(window_data); + free(window_data_win); + return 0x0; + } + + window_data_win->bitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + window_data_win->bitmapInfo->bmiHeader.biPlanes = 1; + window_data_win->bitmapInfo->bmiHeader.biBitCount = 32; + window_data_win->bitmapInfo->bmiHeader.biCompression = BI_BITFIELDS; + window_data_win->bitmapInfo->bmiHeader.biWidth = window_data->buffer_width; + window_data_win->bitmapInfo->bmiHeader.biHeight = -(LONG)window_data->buffer_height; + window_data_win->bitmapInfo->bmiColors[0].rgbRed = 0xff; + window_data_win->bitmapInfo->bmiColors[1].rgbGreen = 0xff; + window_data_win->bitmapInfo->bmiColors[2].rgbBlue = 0xff; + +#else + + create_GL_context(window_data); + +#endif + + window_data_win->timer = mfb_timer_create(); + + mfb_set_keyboard_callback((struct mfb_window *) window_data, keyboard_default); + +#if defined(_DEBUG) || defined(DEBUG) + #if defined(USE_OPENGL_API) + printf("Window created using OpenGL API\n"); + #else + printf("Window created using GDI API\n"); + #endif +#endif + + window_data->is_initialized = true; + return (struct mfb_window *) window_data; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +mfb_update_state +mfb_update_ex(struct mfb_window *window, void *buffer, unsigned width, unsigned height) { + MSG msg; + + if (window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + if (window_data->close) { + destroy_window_data(window_data); + return STATE_EXIT; + } + + if (buffer == 0x0) { + return STATE_INVALID_BUFFER; + } + + window_data->draw_buffer = buffer; + window_data->buffer_width = width; + window_data->buffer_stride = width * 4; + window_data->buffer_height = height; + + SWindowData_Win *window_data_win = (SWindowData_Win *) window_data->specific; + +#if !defined(USE_OPENGL_API) + + window_data_win->bitmapInfo->bmiHeader.biWidth = window_data->buffer_width; + window_data_win->bitmapInfo->bmiHeader.biHeight = -(LONG) window_data->buffer_height; + InvalidateRect(window_data_win->window, 0x0, TRUE); + SendMessage(window_data_win->window, WM_PAINT, 0, 0); + +#else + + redraw_GL(window_data, buffer); + +#endif + + while (window_data->close == false && PeekMessage(&msg, window_data_win->window, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return STATE_OK; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +mfb_update_state +mfb_update_events(struct mfb_window *window) { + MSG msg; + + if (window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *)window; + if (window_data->close) { + destroy_window_data(window_data); + return STATE_EXIT; + } + + SWindowData_Win *window_data_win = (SWindowData_Win *) window_data->specific; + while (window_data->close == false && PeekMessage(&msg, window_data_win->window, 0, 0, PM_REMOVE)) { + //if(msg.message == WM_PAINT) + // return STATE_OK; + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return STATE_OK; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +extern double g_time_for_frame; +extern bool g_use_hardware_sync; + +bool +mfb_wait_sync(struct mfb_window *window) { + if (window == 0x0) { + return false; + } + + SWindowData *window_data = (SWindowData *)window; + if (window_data->close) { + destroy_window_data(window_data); + return false; + } + + if(g_use_hardware_sync) { + return true; + } + + MSG msg; + SWindowData_Win *window_data_win = (SWindowData_Win *) window_data->specific; + double current; + uint32_t millis = 1; + while (1) { + current = mfb_timer_now(window_data_win->timer); + if (current >= g_time_for_frame * 0.96) { + mfb_timer_reset(window_data_win->timer); + return true; + } + else if(current >= g_time_for_frame * 0.8) { + millis = 0; + } + + Sleep(millis); + + if(millis == 1 && PeekMessage(&msg, window_data_win->window, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + + if (window_data->close) { + destroy_window_data(window_data); + return false; + } + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void +destroy_window_data(SWindowData *window_data) { + if (window_data == 0x0) + return; + + SWindowData_Win *window_data_win = (SWindowData_Win *) window_data->specific; + +#if !defined(USE_OPENGL_API) + if (window_data_win->bitmapInfo != 0x0) { + free(window_data_win->bitmapInfo); + window_data_win->bitmapInfo = 0x0; + } +#else + destroy_GL_context(window_data); +#endif + + if (window_data_win->window != 0 && window_data_win->hdc != 0) { + ReleaseDC(window_data_win->window, window_data_win->hdc); + DestroyWindow(window_data_win->window); + } + + window_data_win->window = 0; + window_data_win->hdc = 0; + + mfb_timer_destroy(window_data_win->timer); + window_data_win->timer = 0x0; + + window_data->draw_buffer = 0x0; + window_data->close = true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +uint32_t +translate_mod() { + uint32_t mods = 0; + + if (GetKeyState(VK_SHIFT) & 0x8000) + mods |= KB_MOD_SHIFT; + if (GetKeyState(VK_CONTROL) & 0x8000) + mods |= KB_MOD_CONTROL; + if (GetKeyState(VK_MENU) & 0x8000) + mods |= KB_MOD_ALT; + if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000) + mods |= KB_MOD_SUPER; + if (GetKeyState(VK_CAPITAL) & 1) + mods |= KB_MOD_CAPS_LOCK; + if (GetKeyState(VK_NUMLOCK) & 1) + mods |= KB_MOD_NUM_LOCK; + + return mods; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +extern short int g_keycodes[512]; + +void +init_keycodes() { + if(g_keycodes[0x00B] != KB_KEY_0) { + g_keycodes[0x00B] = KB_KEY_0; + g_keycodes[0x002] = KB_KEY_1; + g_keycodes[0x003] = KB_KEY_2; + g_keycodes[0x004] = KB_KEY_3; + g_keycodes[0x005] = KB_KEY_4; + g_keycodes[0x006] = KB_KEY_5; + g_keycodes[0x007] = KB_KEY_6; + g_keycodes[0x008] = KB_KEY_7; + g_keycodes[0x009] = KB_KEY_8; + g_keycodes[0x00A] = KB_KEY_9; + g_keycodes[0x01E] = KB_KEY_A; + g_keycodes[0x030] = KB_KEY_B; + g_keycodes[0x02E] = KB_KEY_C; + g_keycodes[0x020] = KB_KEY_D; + g_keycodes[0x012] = KB_KEY_E; + g_keycodes[0x021] = KB_KEY_F; + g_keycodes[0x022] = KB_KEY_G; + g_keycodes[0x023] = KB_KEY_H; + g_keycodes[0x017] = KB_KEY_I; + g_keycodes[0x024] = KB_KEY_J; + g_keycodes[0x025] = KB_KEY_K; + g_keycodes[0x026] = KB_KEY_L; + g_keycodes[0x032] = KB_KEY_M; + g_keycodes[0x031] = KB_KEY_N; + g_keycodes[0x018] = KB_KEY_O; + g_keycodes[0x019] = KB_KEY_P; + g_keycodes[0x010] = KB_KEY_Q; + g_keycodes[0x013] = KB_KEY_R; + g_keycodes[0x01F] = KB_KEY_S; + g_keycodes[0x014] = KB_KEY_T; + g_keycodes[0x016] = KB_KEY_U; + g_keycodes[0x02F] = KB_KEY_V; + g_keycodes[0x011] = KB_KEY_W; + g_keycodes[0x02D] = KB_KEY_X; + g_keycodes[0x015] = KB_KEY_Y; + g_keycodes[0x02C] = KB_KEY_Z; + + g_keycodes[0x028] = KB_KEY_APOSTROPHE; + g_keycodes[0x02B] = KB_KEY_BACKSLASH; + g_keycodes[0x033] = KB_KEY_COMMA; + g_keycodes[0x00D] = KB_KEY_EQUAL; + g_keycodes[0x029] = KB_KEY_GRAVE_ACCENT; + g_keycodes[0x01A] = KB_KEY_LEFT_BRACKET; + g_keycodes[0x00C] = KB_KEY_MINUS; + g_keycodes[0x034] = KB_KEY_PERIOD; + g_keycodes[0x01B] = KB_KEY_RIGHT_BRACKET; + g_keycodes[0x027] = KB_KEY_SEMICOLON; + g_keycodes[0x035] = KB_KEY_SLASH; + g_keycodes[0x056] = KB_KEY_WORLD_2; + + g_keycodes[0x00E] = KB_KEY_BACKSPACE; + g_keycodes[0x153] = KB_KEY_DELETE; + g_keycodes[0x14F] = KB_KEY_END; + g_keycodes[0x01C] = KB_KEY_ENTER; + g_keycodes[0x001] = KB_KEY_ESCAPE; + g_keycodes[0x147] = KB_KEY_HOME; + g_keycodes[0x152] = KB_KEY_INSERT; + g_keycodes[0x15D] = KB_KEY_MENU; + g_keycodes[0x151] = KB_KEY_PAGE_DOWN; + g_keycodes[0x149] = KB_KEY_PAGE_UP; + g_keycodes[0x045] = KB_KEY_PAUSE; + g_keycodes[0x146] = KB_KEY_PAUSE; + g_keycodes[0x039] = KB_KEY_SPACE; + g_keycodes[0x00F] = KB_KEY_TAB; + g_keycodes[0x03A] = KB_KEY_CAPS_LOCK; + g_keycodes[0x145] = KB_KEY_NUM_LOCK; + g_keycodes[0x046] = KB_KEY_SCROLL_LOCK; + g_keycodes[0x03B] = KB_KEY_F1; + g_keycodes[0x03C] = KB_KEY_F2; + g_keycodes[0x03D] = KB_KEY_F3; + g_keycodes[0x03E] = KB_KEY_F4; + g_keycodes[0x03F] = KB_KEY_F5; + g_keycodes[0x040] = KB_KEY_F6; + g_keycodes[0x041] = KB_KEY_F7; + g_keycodes[0x042] = KB_KEY_F8; + g_keycodes[0x043] = KB_KEY_F9; + g_keycodes[0x044] = KB_KEY_F10; + g_keycodes[0x057] = KB_KEY_F11; + g_keycodes[0x058] = KB_KEY_F12; + g_keycodes[0x064] = KB_KEY_F13; + g_keycodes[0x065] = KB_KEY_F14; + g_keycodes[0x066] = KB_KEY_F15; + g_keycodes[0x067] = KB_KEY_F16; + g_keycodes[0x068] = KB_KEY_F17; + g_keycodes[0x069] = KB_KEY_F18; + g_keycodes[0x06A] = KB_KEY_F19; + g_keycodes[0x06B] = KB_KEY_F20; + g_keycodes[0x06C] = KB_KEY_F21; + g_keycodes[0x06D] = KB_KEY_F22; + g_keycodes[0x06E] = KB_KEY_F23; + g_keycodes[0x076] = KB_KEY_F24; + g_keycodes[0x038] = KB_KEY_LEFT_ALT; + g_keycodes[0x01D] = KB_KEY_LEFT_CONTROL; + g_keycodes[0x02A] = KB_KEY_LEFT_SHIFT; + g_keycodes[0x15B] = KB_KEY_LEFT_SUPER; + g_keycodes[0x137] = KB_KEY_PRINT_SCREEN; + g_keycodes[0x138] = KB_KEY_RIGHT_ALT; + g_keycodes[0x11D] = KB_KEY_RIGHT_CONTROL; + g_keycodes[0x036] = KB_KEY_RIGHT_SHIFT; + g_keycodes[0x15C] = KB_KEY_RIGHT_SUPER; + g_keycodes[0x150] = KB_KEY_DOWN; + g_keycodes[0x14B] = KB_KEY_LEFT; + g_keycodes[0x14D] = KB_KEY_RIGHT; + g_keycodes[0x148] = KB_KEY_UP; + + g_keycodes[0x052] = KB_KEY_KP_0; + g_keycodes[0x04F] = KB_KEY_KP_1; + g_keycodes[0x050] = KB_KEY_KP_2; + g_keycodes[0x051] = KB_KEY_KP_3; + g_keycodes[0x04B] = KB_KEY_KP_4; + g_keycodes[0x04C] = KB_KEY_KP_5; + g_keycodes[0x04D] = KB_KEY_KP_6; + g_keycodes[0x047] = KB_KEY_KP_7; + g_keycodes[0x048] = KB_KEY_KP_8; + g_keycodes[0x049] = KB_KEY_KP_9; + g_keycodes[0x04E] = KB_KEY_KP_ADD; + g_keycodes[0x053] = KB_KEY_KP_DECIMAL; + g_keycodes[0x135] = KB_KEY_KP_DIVIDE; + g_keycodes[0x11C] = KB_KEY_KP_ENTER; + g_keycodes[0x037] = KB_KEY_KP_MULTIPLY; + g_keycodes[0x04A] = KB_KEY_KP_SUBTRACT; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +mfb_key +translate_key(unsigned int wParam, unsigned long lParam) { + if (wParam == VK_CONTROL) { + MSG next; + DWORD time; + + if (lParam & 0x01000000) + return KB_KEY_RIGHT_CONTROL; + + time = GetMessageTime(); + if (PeekMessageW(&next, 0x0, 0, 0, PM_NOREMOVE)) + if (next.message == WM_KEYDOWN || next.message == WM_SYSKEYDOWN || next.message == WM_KEYUP || next.message == WM_SYSKEYUP) + if (next.wParam == VK_MENU && (next.lParam & 0x01000000) && next.time == time) + return KB_KEY_UNKNOWN; + + return KB_KEY_LEFT_CONTROL; + } + + if (wParam == VK_PROCESSKEY) + return KB_KEY_UNKNOWN; + + return (mfb_key) g_keycodes[HIWORD(lParam) & 0x1FF]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +bool +mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height) { + SWindowData *window_data = (SWindowData *) window; + SWindowData_Win *window_data_win = 0x0; + float scale_x, scale_y; + + if(window_data == 0x0) { + return false; + } + + if (offset_x + width > window_data->window_width) { + return false; + } + if (offset_y + height > window_data->window_height) { + return false; + } + + window_data_win = (SWindowData_Win *) window_data->specific; + + get_monitor_scale(window_data_win->window, &scale_x, &scale_y); + window_data->dst_offset_x = (uint32_t) (offset_x * scale_x); + window_data->dst_offset_y = (uint32_t) (offset_y * scale_y); + + window_data->dst_width = (uint32_t) (width * scale_x); + window_data->dst_height = (uint32_t) (height * scale_y); + + calc_dst_factor(window_data, window_data->window_width, window_data->window_height); + +#if !defined(USE_OPENGL_API) + window_data_win = (SWindowData_Win *) window_data->specific; + BitBlt(window_data_win->hdc, 0, 0, window_data->window_width, window_data->window_height, 0, 0, 0, BLACKNESS); +#endif + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +extern double g_timer_frequency; +extern double g_timer_resolution; + +uint64_t +mfb_timer_tick() { + int64_t counter; + + QueryPerformanceCounter((LARGE_INTEGER *) &counter); + + return counter; +} + +void +mfb_timer_init() { + uint64_t frequency; + + QueryPerformanceFrequency((LARGE_INTEGER *) &frequency); + + g_timer_frequency = (double) ((int64_t) frequency); + g_timer_resolution = 1.0 / g_timer_frequency; +} diff --git a/lib/minifb/upstream/src/windows/WindowData_Win.h b/lib/minifb/upstream/src/windows/WindowData_Win.h new file mode 100644 index 0000000..74fa04a --- /dev/null +++ b/lib/minifb/upstream/src/windows/WindowData_Win.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#define WIN32_LEAN_AND_MEAN +#include + +typedef struct { + HWND window; + WNDCLASS wc; + HDC hdc; +#if defined(USE_OPENGL_API) + HGLRC hGLRC; + uint32_t text_id; +#else + BITMAPINFO *bitmapInfo; +#endif + struct mfb_timer *timer; + bool mouse_inside; +} SWindowData_Win; diff --git a/lib/minifb/upstream/src/x11/WindowData_X11.h b/lib/minifb/upstream/src/x11/WindowData_X11.h new file mode 100644 index 0000000..646ec35 --- /dev/null +++ b/lib/minifb/upstream/src/x11/WindowData_X11.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#if defined(USE_OPENGL_API) +#include +#endif + +typedef struct { + Window window; + + Display *display; + int screen; + GC gc; +#if defined(USE_OPENGL_API) + GLXContext context; + uint32_t text_id; +#else + XImage *image; + void *image_buffer; + XImage *image_scaler; + uint32_t image_scaler_width; + uint32_t image_scaler_height; +#endif + + struct mfb_timer *timer; +} SWindowData_X11; diff --git a/lib/minifb/upstream/src/x11/X11MiniFB.c b/lib/minifb/upstream/src/x11/X11MiniFB.c new file mode 100644 index 0000000..c1b41f4 --- /dev/null +++ b/lib/minifb/upstream/src/x11/X11MiniFB.c @@ -0,0 +1,865 @@ +#include +#include +#include +#include +#include +#include + +// I cannot find a way to get dpi under VirtualBox +//#include +//#include + +#include +#include +#include +#include +#include +#include +#include "WindowData.h" +#include "WindowData_X11.h" + +#if defined(USE_OPENGL_API) + #include +#endif + +static Atom s_delete_window_atom; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void init_keycodes(SWindowData_X11 *window_data_x11); + +extern void +stretch_image(uint32_t *srcImage, uint32_t srcX, uint32_t srcY, uint32_t srcWidth, uint32_t srcHeight, uint32_t srcPitch, + uint32_t *dstImage, uint32_t dstX, uint32_t dstY, uint32_t dstWidth, uint32_t dstHeight, uint32_t dstPitch); + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +struct mfb_window * +mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) { + int depth, i, formatCount, convDepth = -1; + XPixmapFormatValues* formats; + XSetWindowAttributes windowAttributes; + XSizeHints sizeHints; + Visual* visual; + + SWindowData *window_data = (SWindowData *) malloc(sizeof(SWindowData)); + if (!window_data) { + return 0x0; + } + memset(window_data, 0, sizeof(SWindowData)); + + SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) malloc(sizeof(SWindowData_X11)); + if (!window_data_x11) { + free(window_data); + return 0x0; + } + memset(window_data_x11, 0, sizeof(SWindowData_X11)); + window_data->specific = window_data_x11; + + window_data_x11->display = XOpenDisplay(0); + if (!window_data_x11->display) { + free(window_data); + free(window_data_x11); + return 0x0; + } + + init_keycodes(window_data_x11); + + window_data_x11->screen = DefaultScreen(window_data_x11->display); + + visual = DefaultVisual(window_data_x11->display, window_data_x11->screen); + formats = XListPixmapFormats(window_data_x11->display, &formatCount); + depth = DefaultDepth(window_data_x11->display, window_data_x11->screen); + + Window defaultRootWindow = DefaultRootWindow(window_data_x11->display); + + for (i = 0; i < formatCount; ++i) + { + if (depth == formats[i].depth) + { + convDepth = formats[i].bits_per_pixel; + break; + } + } + + XFree(formats); + + // We only support 32-bit right now + if (convDepth != 32) + { + XCloseDisplay(window_data_x11->display); + return 0x0; + } + + int screenWidth = DisplayWidth(window_data_x11->display, window_data_x11->screen); + int screenHeight = DisplayHeight(window_data_x11->display, window_data_x11->screen); + + windowAttributes.border_pixel = BlackPixel(window_data_x11->display, window_data_x11->screen); + windowAttributes.background_pixel = BlackPixel(window_data_x11->display, window_data_x11->screen); + windowAttributes.backing_store = NotUseful; + + int posX, posY; + int windowWidth, windowHeight; + + window_data->window_width = width; + window_data->window_height = height; + window_data->buffer_width = width; + window_data->buffer_height = height; + window_data->buffer_stride = width * 4; + calc_dst_factor(window_data, width, height); + + if (flags & WF_FULLSCREEN_DESKTOP) { + posX = 0; + posY = 0; + windowWidth = screenWidth; + windowHeight = screenHeight; + } + else { + posX = (screenWidth - width) / 2; + posY = (screenHeight - height) / 2; + windowWidth = width; + windowHeight = height; + } + + window_data_x11->window = XCreateWindow( + window_data_x11->display, + defaultRootWindow, + posX, posY, + windowWidth, windowHeight, + 0, + depth, + InputOutput, + visual, + CWBackPixel | CWBorderPixel | CWBackingStore, + &windowAttributes); + if (!window_data_x11->window) + return 0x0; + + XSelectInput(window_data_x11->display, window_data_x11->window, + KeyPressMask | KeyReleaseMask + | ButtonPressMask | ButtonReleaseMask | PointerMotionMask + | StructureNotifyMask | ExposureMask + | FocusChangeMask + | EnterWindowMask | LeaveWindowMask + ); + + XStoreName(window_data_x11->display, window_data_x11->window, title); + + if (flags & WF_BORDERLESS) { + struct StyleHints { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; + } sh = { + .flags = 2, + .functions = 0, + .decorations = 0, + .inputMode = 0, + .status = 0, + }; + Atom sh_p = XInternAtom(window_data_x11->display, "_MOTIF_WM_HINTS", True); + XChangeProperty(window_data_x11->display, window_data_x11->window, sh_p, sh_p, 32, PropModeReplace, (unsigned char*)&sh, 5); + } + + if (flags & WF_ALWAYS_ON_TOP) { + Atom sa_p = XInternAtom(window_data_x11->display, "_NET_WM_STATE_ABOVE", False); + XChangeProperty(window_data_x11->display, window_data_x11->window, XInternAtom(window_data_x11->display, "_NET_WM_STATE", False), XA_ATOM, 32, PropModeReplace, (unsigned char *)&sa_p, 1); + } + + if (flags & WF_FULLSCREEN) { + Atom sf_p = XInternAtom(window_data_x11->display, "_NET_WM_STATE_FULLSCREEN", True); + XChangeProperty(window_data_x11->display, window_data_x11->window, XInternAtom(window_data_x11->display, "_NET_WM_STATE", True), XA_ATOM, 32, PropModeReplace, (unsigned char*)&sf_p, 1); + } + + sizeHints.flags = PPosition | PMinSize | PMaxSize; + sizeHints.x = 0; + sizeHints.y = 0; + sizeHints.min_width = width; + sizeHints.min_height = height; + if (flags & WF_RESIZABLE) { + sizeHints.max_width = screenWidth; + sizeHints.max_height = screenHeight; + } + else { + sizeHints.max_width = width; + sizeHints.max_height = height; + } + + s_delete_window_atom = XInternAtom(window_data_x11->display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(window_data_x11->display, window_data_x11->window, &s_delete_window_atom, 1); + +#if defined(USE_OPENGL_API) + if(create_GL_context(window_data) == false) { + return 0x0; + } + +#else + window_data_x11->image = XCreateImage(window_data_x11->display, CopyFromParent, depth, ZPixmap, 0, 0x0, width, height, 32, width * 4); +#endif + + XSetWMNormalHints(window_data_x11->display, window_data_x11->window, &sizeHints); + XClearWindow(window_data_x11->display, window_data_x11->window); + XMapRaised(window_data_x11->display, window_data_x11->window); + XFlush(window_data_x11->display); + + window_data_x11->gc = DefaultGC(window_data_x11->display, window_data_x11->screen); + + window_data_x11->timer = mfb_timer_create(); + + mfb_set_keyboard_callback((struct mfb_window *) window_data, keyboard_default); + +#if defined(_DEBUG) || defined(DEBUG) + printf("Window created using X11 API\n"); +#endif + + window_data->is_initialized = true; + return (struct mfb_window *) window_data; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int translate_key(int scancode); +int translate_mod(int state); +int translate_mod_ex(int key, int state, int is_pressed); + +static void +processEvent(SWindowData *window_data, XEvent *event) { + switch (event->type) { + case KeyPress: + case KeyRelease: + { + mfb_key key_code = (mfb_key) translate_key(event->xkey.keycode); + int is_pressed = (event->type == KeyPress); + window_data->mod_keys = translate_mod_ex(key_code, event->xkey.state, is_pressed); + + window_data->key_status[key_code] = is_pressed; + kCall(keyboard_func, key_code, (mfb_key_mod) window_data->mod_keys, is_pressed); + } + break; + + case ButtonPress: + case ButtonRelease: + { + mfb_mouse_button button = (mfb_mouse_button) event->xbutton.button; + int is_pressed = (event->type == ButtonPress); + window_data->mod_keys = translate_mod(event->xkey.state); + + // Swap mouse right and middle for parity with other platforms: + // https://github.com/emoon/minifb/issues/65 + switch (button) { + case Button2: + button = Button3; + break; + case Button3: + button = Button2; + break; + } + + switch (button) { + case Button1: + case Button2: + case Button3: + window_data->mouse_button_status[button & 0x07] = is_pressed; + kCall(mouse_btn_func, button, (mfb_key_mod) window_data->mod_keys, is_pressed); + break; + + case Button4: + window_data->mouse_wheel_y = 1.0f; + kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, 0.0f, window_data->mouse_wheel_y); + break; + case Button5: + window_data->mouse_wheel_y = -1.0f; + kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, 0.0f, window_data->mouse_wheel_y); + break; + + case 6: + window_data->mouse_wheel_x = 1.0f; + kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, window_data->mouse_wheel_x, 0.0f); + break; + case 7: + window_data->mouse_wheel_x = -1.0f; + kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, window_data->mouse_wheel_x, 0.0f); + break; + + default: + window_data->mouse_button_status[(button - 4) & 0x07] = is_pressed; + kCall(mouse_btn_func, (mfb_mouse_button) (button - 4), (mfb_key_mod) window_data->mod_keys, is_pressed); + break; + } + } + break; + + case MotionNotify: + window_data->mouse_pos_x = event->xmotion.x; + window_data->mouse_pos_y = event->xmotion.y; + kCall(mouse_move_func, event->xmotion.x, event->xmotion.y); + break; + + case ConfigureNotify: + { + window_data->window_width = event->xconfigure.width; + window_data->window_height = event->xconfigure.height; + resize_dst(window_data, event->xconfigure.width, event->xconfigure.height); + +#if defined(USE_OPENGL_API) + resize_GL(window_data); +#else + SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; + if(window_data_x11->image_scaler != 0x0) { + window_data_x11->image_scaler->data = 0x0; + XDestroyImage(window_data_x11->image_scaler); + window_data_x11->image_scaler = 0x0; + window_data_x11->image_scaler_width = 0; + window_data_x11->image_scaler_height = 0; + } + XClearWindow(window_data_x11->display, window_data_x11->window); +#endif + kCall(resize_func, window_data->window_width, window_data->window_height); + } + break; + + case EnterNotify: + case LeaveNotify: + break; + + case FocusIn: + window_data->is_active = true; + kCall(active_func, true); + break; + + case FocusOut: + window_data->is_active = false; + kCall(active_func, false); + break; + + case DestroyNotify: + window_data->close = true; + return; + break; + + case ClientMessage: + { + if ((Atom)event->xclient.data.l[0] == s_delete_window_atom) { + window_data->close = true; + return; + } + } + break; + } +} + +static void +processEvents(SWindowData *window_data) { + XEvent event; + SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; + + while ((window_data->close == false) && XPending(window_data_x11->display)) { + XNextEvent(window_data_x11->display, &event); + processEvent(window_data, &event); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void destroy_window_data(SWindowData *window_data); + +mfb_update_state +mfb_update_ex(struct mfb_window *window, void *buffer, unsigned width, unsigned height) { + if (window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + if (window_data->close) { + destroy_window_data(window_data); + return STATE_EXIT; + } + + if (buffer == 0x0) { + return STATE_INVALID_BUFFER; + } + +#if !defined(USE_OPENGL_API) + SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; + bool different_size = false; +#endif + + if(window_data->buffer_width != width || window_data->buffer_height != height) { + window_data->buffer_width = width; + window_data->buffer_stride = width * 4; + window_data->buffer_height = height; +#if !defined(USE_OPENGL_API) + different_size = true; +#endif + } + +#if !defined(USE_OPENGL_API) + + if (different_size || window_data->buffer_width != window_data->dst_width || window_data->buffer_height != window_data->dst_height) { + if (window_data_x11->image_scaler_width != window_data->dst_width || window_data_x11->image_scaler_height != window_data->dst_height) { + if (window_data_x11->image_scaler != 0x0) { + window_data_x11->image_scaler->data = 0x0; + XDestroyImage(window_data_x11->image_scaler); + } + if (window_data_x11->image_buffer != 0x0) { + free(window_data_x11->image_buffer); + window_data_x11->image_buffer = 0x0; + } + int depth = DefaultDepth(window_data_x11->display, window_data_x11->screen); + window_data_x11->image_buffer = malloc(window_data->dst_width * window_data->dst_height * 4); + if(window_data_x11->image_buffer == 0x0) { + return STATE_INTERNAL_ERROR; + } + window_data_x11->image_scaler_width = window_data->dst_width; + window_data_x11->image_scaler_height = window_data->dst_height; + window_data_x11->image_scaler = XCreateImage(window_data_x11->display, CopyFromParent, depth, ZPixmap, 0, 0x0, window_data_x11->image_scaler_width, window_data_x11->image_scaler_height, 32, window_data_x11->image_scaler_width * 4); + } + } + + if (window_data_x11->image_scaler != 0x0) { + stretch_image((uint32_t *) buffer, 0, 0, window_data->buffer_width, window_data->buffer_height, window_data->buffer_width, + (uint32_t *) window_data_x11->image_buffer, 0, 0, window_data->dst_width, window_data->dst_height, window_data->dst_width); + window_data_x11->image_scaler->data = (char *) window_data_x11->image_buffer; + XPutImage(window_data_x11->display, window_data_x11->window, window_data_x11->gc, window_data_x11->image_scaler, 0, 0, window_data->dst_offset_x, window_data->dst_offset_y, window_data->dst_width, window_data->dst_height); + } + else { + window_data_x11->image->data = (char *) buffer; + XPutImage(window_data_x11->display, window_data_x11->window, window_data_x11->gc, window_data_x11->image, 0, 0, window_data->dst_offset_x, window_data->dst_offset_y, window_data->dst_width, window_data->dst_height); + } + XFlush(window_data_x11->display); + +#else + + redraw_GL(window_data, buffer); + +#endif + + processEvents(window_data); + + return STATE_OK; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +mfb_update_state +mfb_update_events(struct mfb_window *window) { + if (window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + if (window_data->close) { + destroy_window_data(window_data); + return STATE_EXIT; + } + + SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; + XFlush(window_data_x11->display); + processEvents(window_data); + + return STATE_OK; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +extern double g_time_for_frame; +extern bool g_use_hardware_sync; + +bool +mfb_wait_sync(struct mfb_window *window) { + if (window == 0x0) { + return false; + } + + SWindowData *window_data = (SWindowData *) window; + if (window_data->close) { + destroy_window_data(window_data); + return false; + } + + if(g_use_hardware_sync) { + return true; + } + + SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; + XFlush(window_data_x11->display); + XEvent event; + double current; + uint32_t millis = 1; + while(1) { + current = mfb_timer_now(window_data_x11->timer); + if (current >= g_time_for_frame * 0.96) { + mfb_timer_reset(window_data_x11->timer); + return true; + } + else if(current >= g_time_for_frame * 0.8) { + millis = 0; + } + + usleep(millis * 1000); + //sched_yield(); + + if(millis == 1 && XEventsQueued(window_data_x11->display, QueuedAlready) > 0) { + XNextEvent(window_data_x11->display, &event); + processEvent(window_data, &event); + + if(window_data->close) { + destroy_window_data(window_data); + return false; + } + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void +destroy_window_data(SWindowData *window_data) { + if (window_data != 0x0) { + if (window_data->specific != 0x0) { + SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; + +#if defined(USE_OPENGL_API) + destroy_GL_context(window_data); +#else + if (window_data_x11->image != 0x0) { + window_data_x11->image->data = 0x0; + XDestroyImage(window_data_x11->image); + XDestroyWindow(window_data_x11->display, window_data_x11->window); + XCloseDisplay(window_data_x11->display); + } +#endif + + mfb_timer_destroy(window_data_x11->timer); + memset(window_data_x11, 0, sizeof(SWindowData_X11)); + free(window_data_x11); + } + memset(window_data, 0, sizeof(SWindowData)); + free(window_data); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +extern short int g_keycodes[512]; + +static int +translateKeyCodeB(int keySym) { + + switch (keySym) + { + case XK_KP_0: return KB_KEY_KP_0; + case XK_KP_1: return KB_KEY_KP_1; + case XK_KP_2: return KB_KEY_KP_2; + case XK_KP_3: return KB_KEY_KP_3; + case XK_KP_4: return KB_KEY_KP_4; + case XK_KP_5: return KB_KEY_KP_5; + case XK_KP_6: return KB_KEY_KP_6; + case XK_KP_7: return KB_KEY_KP_7; + case XK_KP_8: return KB_KEY_KP_8; + case XK_KP_9: return KB_KEY_KP_9; + case XK_KP_Separator: + case XK_KP_Decimal: return KB_KEY_KP_DECIMAL; + case XK_KP_Equal: return KB_KEY_KP_EQUAL; + case XK_KP_Enter: return KB_KEY_KP_ENTER; + } + + return KB_KEY_UNKNOWN; +} + +static int translateKeyCodeA(int keySym) { + switch (keySym) + { + case XK_Escape: return KB_KEY_ESCAPE; + case XK_Tab: return KB_KEY_TAB; + case XK_Shift_L: return KB_KEY_LEFT_SHIFT; + case XK_Shift_R: return KB_KEY_RIGHT_SHIFT; + case XK_Control_L: return KB_KEY_LEFT_CONTROL; + case XK_Control_R: return KB_KEY_RIGHT_CONTROL; + case XK_Meta_L: + case XK_Alt_L: return KB_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 KB_KEY_RIGHT_ALT; + case XK_Super_L: return KB_KEY_LEFT_SUPER; + case XK_Super_R: return KB_KEY_RIGHT_SUPER; + case XK_Menu: return KB_KEY_MENU; + case XK_Num_Lock: return KB_KEY_NUM_LOCK; + case XK_Caps_Lock: return KB_KEY_CAPS_LOCK; + case XK_Print: return KB_KEY_PRINT_SCREEN; + case XK_Scroll_Lock: return KB_KEY_SCROLL_LOCK; + case XK_Pause: return KB_KEY_PAUSE; + case XK_Delete: return KB_KEY_DELETE; + case XK_BackSpace: return KB_KEY_BACKSPACE; + case XK_Return: return KB_KEY_ENTER; + case XK_Home: return KB_KEY_HOME; + case XK_End: return KB_KEY_END; + case XK_Page_Up: return KB_KEY_PAGE_UP; + case XK_Page_Down: return KB_KEY_PAGE_DOWN; + case XK_Insert: return KB_KEY_INSERT; + case XK_Left: return KB_KEY_LEFT; + case XK_Right: return KB_KEY_RIGHT; + case XK_Down: return KB_KEY_DOWN; + case XK_Up: return KB_KEY_UP; + case XK_F1: return KB_KEY_F1; + case XK_F2: return KB_KEY_F2; + case XK_F3: return KB_KEY_F3; + case XK_F4: return KB_KEY_F4; + case XK_F5: return KB_KEY_F5; + case XK_F6: return KB_KEY_F6; + case XK_F7: return KB_KEY_F7; + case XK_F8: return KB_KEY_F8; + case XK_F9: return KB_KEY_F9; + case XK_F10: return KB_KEY_F10; + case XK_F11: return KB_KEY_F11; + case XK_F12: return KB_KEY_F12; + case XK_F13: return KB_KEY_F13; + case XK_F14: return KB_KEY_F14; + case XK_F15: return KB_KEY_F15; + case XK_F16: return KB_KEY_F16; + case XK_F17: return KB_KEY_F17; + case XK_F18: return KB_KEY_F18; + case XK_F19: return KB_KEY_F19; + case XK_F20: return KB_KEY_F20; + case XK_F21: return KB_KEY_F21; + case XK_F22: return KB_KEY_F22; + case XK_F23: return KB_KEY_F23; + case XK_F24: return KB_KEY_F24; + case XK_F25: return KB_KEY_F25; + + // Numeric keypad + case XK_KP_Divide: return KB_KEY_KP_DIVIDE; + case XK_KP_Multiply: return KB_KEY_KP_MULTIPLY; + case XK_KP_Subtract: return KB_KEY_KP_SUBTRACT; + case XK_KP_Add: return KB_KEY_KP_ADD; + + // These should have been detected in secondary keysym test above! + case XK_KP_Insert: return KB_KEY_KP_0; + case XK_KP_End: return KB_KEY_KP_1; + case XK_KP_Down: return KB_KEY_KP_2; + case XK_KP_Page_Down: return KB_KEY_KP_3; + case XK_KP_Left: return KB_KEY_KP_4; + case XK_KP_Right: return KB_KEY_KP_6; + case XK_KP_Home: return KB_KEY_KP_7; + case XK_KP_Up: return KB_KEY_KP_8; + case XK_KP_Page_Up: return KB_KEY_KP_9; + case XK_KP_Delete: return KB_KEY_KP_DECIMAL; + case XK_KP_Equal: return KB_KEY_KP_EQUAL; + case XK_KP_Enter: return KB_KEY_KP_ENTER; + + // Last resort: Check for printable keys (should not happen if the XKB + // extension is available). This will give a layout dependent mapping + // (which is wrong, and we may miss some keys, especially on non-US + // keyboards), but it's better than nothing... + case XK_a: return KB_KEY_A; + case XK_b: return KB_KEY_B; + case XK_c: return KB_KEY_C; + case XK_d: return KB_KEY_D; + case XK_e: return KB_KEY_E; + case XK_f: return KB_KEY_F; + case XK_g: return KB_KEY_G; + case XK_h: return KB_KEY_H; + case XK_i: return KB_KEY_I; + case XK_j: return KB_KEY_J; + case XK_k: return KB_KEY_K; + case XK_l: return KB_KEY_L; + case XK_m: return KB_KEY_M; + case XK_n: return KB_KEY_N; + case XK_o: return KB_KEY_O; + case XK_p: return KB_KEY_P; + case XK_q: return KB_KEY_Q; + case XK_r: return KB_KEY_R; + case XK_s: return KB_KEY_S; + case XK_t: return KB_KEY_T; + case XK_u: return KB_KEY_U; + case XK_v: return KB_KEY_V; + case XK_w: return KB_KEY_W; + case XK_x: return KB_KEY_X; + case XK_y: return KB_KEY_Y; + case XK_z: return KB_KEY_Z; + case XK_1: return KB_KEY_1; + case XK_2: return KB_KEY_2; + case XK_3: return KB_KEY_3; + case XK_4: return KB_KEY_4; + case XK_5: return KB_KEY_5; + case XK_6: return KB_KEY_6; + case XK_7: return KB_KEY_7; + case XK_8: return KB_KEY_8; + case XK_9: return KB_KEY_9; + case XK_0: return KB_KEY_0; + case XK_space: return KB_KEY_SPACE; + case XK_minus: return KB_KEY_MINUS; + case XK_equal: return KB_KEY_EQUAL; + case XK_bracketleft: return KB_KEY_LEFT_BRACKET; + case XK_bracketright: return KB_KEY_RIGHT_BRACKET; + case XK_backslash: return KB_KEY_BACKSLASH; + case XK_semicolon: return KB_KEY_SEMICOLON; + case XK_apostrophe: return KB_KEY_APOSTROPHE; + case XK_grave: return KB_KEY_GRAVE_ACCENT; + case XK_comma: return KB_KEY_COMMA; + case XK_period: return KB_KEY_PERIOD; + case XK_slash: return KB_KEY_SLASH; + case XK_less: return KB_KEY_WORLD_1; // At least in some layouts... + default: break; + } + + return KB_KEY_UNKNOWN; +} + +void +init_keycodes(SWindowData_X11 *window_data_x11) { + size_t i; + int keySym; + + // Clear keys + for (i = 0; i < sizeof(g_keycodes) / sizeof(g_keycodes[0]); ++i) + g_keycodes[i] = KB_KEY_UNKNOWN; + + // Valid key code range is [8,255], according to the Xlib manual + for (i=8; i<=255; ++i) { + // Try secondary keysym, for numeric keypad keys + keySym = XkbKeycodeToKeysym(window_data_x11->display, i, 0, 1); + g_keycodes[i] = translateKeyCodeB(keySym); + if (g_keycodes[i] == KB_KEY_UNKNOWN) { + keySym = XkbKeycodeToKeysym(window_data_x11->display, i, 0, 0); + g_keycodes[i] = translateKeyCodeA(keySym); + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int +translate_key(int scancode) { + if (scancode < 0 || scancode > 255) + return KB_KEY_UNKNOWN; + + return g_keycodes[scancode]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int +translate_mod(int state) { + int mod_keys = 0; + + if (state & ShiftMask) + mod_keys |= KB_MOD_SHIFT; + if (state & ControlMask) + mod_keys |= KB_MOD_CONTROL; + if (state & Mod1Mask) + mod_keys |= KB_MOD_ALT; + if (state & Mod4Mask) + mod_keys |= KB_MOD_SUPER; + if (state & LockMask) + mod_keys |= KB_MOD_CAPS_LOCK; + if (state & Mod2Mask) + mod_keys |= KB_MOD_NUM_LOCK; + + return mod_keys; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int +translate_mod_ex(int key, int state, int is_pressed) { + int mod_keys = 0; + + mod_keys = translate_mod(state); + + switch (key) + { + case KB_KEY_LEFT_SHIFT: + case KB_KEY_RIGHT_SHIFT: + if (is_pressed) + mod_keys |= KB_MOD_SHIFT; + else + mod_keys &= ~KB_MOD_SHIFT; + break; + + case KB_KEY_LEFT_CONTROL: + case KB_KEY_RIGHT_CONTROL: + if (is_pressed) + mod_keys |= KB_MOD_CONTROL; + else + mod_keys &= ~KB_MOD_CONTROL; + break; + + case KB_KEY_LEFT_ALT: + case KB_KEY_RIGHT_ALT: + if (is_pressed) + mod_keys |= KB_MOD_ALT; + else + mod_keys &= ~KB_MOD_ALT; + break; + + case KB_KEY_LEFT_SUPER: + case KB_KEY_RIGHT_SUPER: + if (is_pressed) + mod_keys |= KB_MOD_SUPER; + else + mod_keys &= ~KB_MOD_SUPER; + break; + } + + return mod_keys; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +bool +mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height) { + SWindowData *window_data = (SWindowData *) window; + + if (offset_x + width > window_data->window_width) { + return false; + } + if (offset_y + height > window_data->window_height) { + return false; + } + + window_data->dst_offset_x = offset_x; + window_data->dst_offset_y = offset_y; + window_data->dst_width = width; + window_data->dst_height = height; + calc_dst_factor(window_data, window_data->window_width, window_data->window_height); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void +mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y) { + float x = 96.0, y = 96.0; + + if(window != 0x0) { + //SWindowData *window_data = (SWindowData *) window; + //SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; + + // I cannot find a way to get dpi under VirtualBox + // XrmGetResource "Xft.dpi", "Xft.Dpi" + // XRRGetOutputInfo + // DisplayWidthMM, DisplayHeightMM + // All returning invalid values or 0 + } + + if (scale_x) { + *scale_x = x / 96.0f; + if(*scale_x == 0) { + *scale_x = 1.0f; + } + } + + if (scale_y) { + *scale_y = y / 96.0f; + if (*scale_y == 0) { + *scale_y = 1.0f; + } + } +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..a483b2e --- /dev/null +++ b/src/main.zig @@ -0,0 +1,41 @@ +const std = @import("std"); + +const minifb = @import("minifb"); + +const Width = 800; +const Height = 600; + +pub fn main() anyerror!void { + std.log.info("All your codebase are belong to us.", .{}); + var win = minifb.Window.open("Hello minifb-zig", Width, Height) catch unreachable; + + var color: u32 = 0xff000000; + const deltaColor: u32 = 0x00010101; + var increasing = true; + var rawBuffer: [Width*Height]u32 = undefined; + const buffer = rawBuffer[0..rawBuffer.len]; + std.mem.set(u32, buffer, color); + + while (win.waitSync()) { + if (increasing) { + if (color < 0xffffffff) { + color += deltaColor; + } else { + increasing = false; + } + } else { + if (color > 0xff000000) { + color -= deltaColor; + } else { + increasing = true; + } + } + std.mem.set(u32, buffer, color); + var i: u32 = 0; + while (i < Width) { + buffer[i] = 0xffff0000; + i += 1; + } + _ = win.update(buffer) catch unreachable; + } +}