From 0bbb11fffb8a375368ec188f06b90dd9091cd68b Mon Sep 17 00:00:00 2001 From: Georgi Gerganov Date: Sun, 29 Nov 2020 17:45:20 +0200 Subject: [PATCH] refactor example + adding imgui and imtui submodules --- .gitmodules | 6 + examples/CMakeLists.txt | 9 + examples/ggwave-cli/main.cpp | 129 +--- examples/ggwave-common-sdl2.cpp | 203 ++++- examples/ggwave-common-sdl2.h | 20 +- examples/ggwave-gui/CMakeLists.txt | 14 + examples/ggwave-gui/main.cpp | 162 ++++ examples/ggwave-wasm/main.cpp | 164 +--- examples/ggwave-wasm/main.js | 5 - examples/third-party/CMakeLists.txt | 4 + examples/third-party/imgui/CMakeLists.txt | 67 ++ examples/third-party/imgui/imgui | 1 + .../imgui/imgui-extra/imgui_impl.cpp | 95 +++ .../imgui/imgui-extra/imgui_impl.h | 31 + .../imgui/imgui-extra/imgui_impl_opengl3.cpp | 722 ++++++++++++++++++ .../imgui/imgui-extra/imgui_impl_opengl3.h | 70 ++ .../imgui/imgui-extra/imgui_impl_sdl.cpp | 410 ++++++++++ .../imgui/imgui-extra/imgui_impl_sdl.h | 31 + examples/third-party/imtui | 1 + 19 files changed, 1839 insertions(+), 305 deletions(-) create mode 100644 .gitmodules create mode 100644 examples/ggwave-gui/CMakeLists.txt create mode 100644 examples/ggwave-gui/main.cpp create mode 100644 examples/third-party/CMakeLists.txt create mode 100644 examples/third-party/imgui/CMakeLists.txt create mode 160000 examples/third-party/imgui/imgui create mode 100644 examples/third-party/imgui/imgui-extra/imgui_impl.cpp create mode 100644 examples/third-party/imgui/imgui-extra/imgui_impl.h create mode 100644 examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.cpp create mode 100644 examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.h create mode 100644 examples/third-party/imgui/imgui-extra/imgui_impl_sdl.cpp create mode 100644 examples/third-party/imgui/imgui-extra/imgui_impl_sdl.h create mode 160000 examples/third-party/imtui diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..aba14e7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "examples/third-party/imtui"] + path = examples/third-party/imtui + url = https://github.com/ggerganov/imtui +[submodule "examples/third-party/imgui/imgui"] + path = examples/third-party/imgui/imgui + url = https://github.com/ocornut/imgui diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d2b59e6..3c2c713 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,11 @@ find_package(Threads REQUIRED) +# third-party + +add_subdirectory(third-party) + +# helper libraries + add_library(ggwave-common ${GGWAVE_LIBRARY_TYPE} ggwave-common.cpp ) @@ -48,6 +54,8 @@ if (GGWAVE_SUPPORT_SDL2) ) endif() +# examples + if (GGWAVE_SUPPORT_SDL2) if (EMSCRIPTEN) # emscripten sdl2 examples @@ -57,5 +65,6 @@ if (GGWAVE_SUPPORT_SDL2) # non-emscripten sdl2 examples add_subdirectory(ggwave-cli) + add_subdirectory(ggwave-gui) endif() endif() diff --git a/examples/ggwave-cli/main.cpp b/examples/ggwave-cli/main.cpp index 813de6a..09c605a 100644 --- a/examples/ggwave-cli/main.cpp +++ b/examples/ggwave-cli/main.cpp @@ -1,94 +1,15 @@ -/*! \file main.cpp - * \brief Send/Receive data through sound in the terminal - * \author Georgi Gerganov - */ - #include "ggwave/ggwave.h" #include "ggwave-common.h" #include "ggwave-common-sdl2.h" -#include -#include - #include #include -#include #include #include #include -static char *g_defaultCaptureDeviceName = nullptr; - -static int g_captureId = -1; -static int g_playbackId = -1; - -static bool g_isInitialized = false; - -static SDL_AudioDeviceID g_devIdIn = 0; -static SDL_AudioDeviceID g_devIdOut = 0; - -static GGWave *g_ggWave = nullptr; - -// main loop -void update() { - if (g_isInitialized == false) return; - - SDL_Event e; - SDL_bool shouldTerminate = SDL_FALSE; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) { - shouldTerminate = SDL_TRUE; - } - } - - static GGWave::CBQueueAudio cbQueueAudio = [&](const void * data, uint32_t nBytes) { - SDL_QueueAudio(g_devIdOut, data, nBytes); - }; - - static GGWave::CBDequeueAudio CBDequeueAudio = [&](void * data, uint32_t nMaxBytes) { - return SDL_DequeueAudio(g_devIdIn, data, nMaxBytes); - }; - - if (g_ggWave->getHasData() == false) { - SDL_PauseAudioDevice(g_devIdOut, SDL_FALSE); - - static auto tLastNoData = std::chrono::high_resolution_clock::now(); - auto tNow = std::chrono::high_resolution_clock::now(); - - if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesOut()) { - SDL_PauseAudioDevice(g_devIdIn, SDL_FALSE); - if (::getTime_ms(tLastNoData, tNow) > 500.0f) { - g_ggWave->receive(CBDequeueAudio); - if ((int) SDL_GetQueuedAudioSize(g_devIdIn) > 32*g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesIn()) { - SDL_ClearQueuedAudio(g_devIdIn); - } - } else { - SDL_ClearQueuedAudio(g_devIdIn); - } - } else { - tLastNoData = tNow; - //SDL_ClearQueuedAudio(g_devIdIn); - //SDL_Delay(10); - } - } else { - SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE); - SDL_PauseAudioDevice(g_devIdIn, SDL_TRUE); - - g_ggWave->send(cbQueueAudio); - } - - if (shouldTerminate) { - SDL_PauseAudioDevice(g_devIdIn, 1); - SDL_CloseAudioDevice(g_devIdIn); - SDL_PauseAudioDevice(g_devIdOut, 1); - SDL_CloseAudioDevice(g_devIdOut); - SDL_CloseAudio(); - SDL_Quit(); - } -} - int main(int argc, char** argv) { printf("Usage: %s [-cN] [-pN] [-tN]\n", argv[0]); printf(" -cN - select capture device N\n"); @@ -100,61 +21,58 @@ int main(int argc, char** argv) { printf(" -t3 : Ultrasonic\n"); printf("\n"); - g_defaultCaptureDeviceName = nullptr; - auto argm = parseCmdArguments(argc, argv); - g_captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]); - g_playbackId = argm["p"].empty() ? 0 : std::stoi(argm["p"]); + int captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]); + int playbackId = argm["p"].empty() ? 0 : std::stoi(argm["p"]); int txProtocol = argm["t"].empty() ? 1 : std::stoi(argm["t"]); - initSDL2ForGGWave( - g_isInitialized, - g_playbackId, - g_devIdIn, - g_captureId, - g_devIdOut, - g_ggWave); + if (GGWave_init(playbackId, captureId) == false) { + fprintf(stderr, "Failed to initialize GGWave\n"); + return -1; + } - g_ggWave->setTxMode(GGWave::TxMode::VariableLength); + auto ggWave = GGWave_instance(); + + ggWave->setTxMode(GGWave::TxMode::VariableLength); printf("Selecting Tx protocol %d\n", txProtocol); switch (txProtocol) { case 0: { printf("Using 'Normal' Tx Protocol\n"); - g_ggWave->setParameters(1, 40, 9, 3, 50); + ggWave->setParameters(1, 40, 9, 3, 50); } break; case 1: { printf("Using 'Fast' Tx Protocol\n"); - g_ggWave->setParameters(1, 40, 6, 3, 50); + ggWave->setParameters(1, 40, 6, 3, 50); } break; case 2: { printf("Using 'Fastest' Tx Protocol\n"); - g_ggWave->setParameters(1, 40, 3, 3, 50); + ggWave->setParameters(1, 40, 3, 3, 50); } break; case 3: { printf("Using 'Ultrasonic' Tx Protocol\n"); - g_ggWave->setParameters(1, 320, 9, 3, 50); + ggWave->setParameters(1, 320, 9, 3, 50); } break; default: { printf("Using 'Fast' Tx Protocol\n"); - g_ggWave->setParameters(1, 40, 6, 3, 50); + ggWave->setParameters(1, 40, 6, 3, 50); } }; printf("\n"); - g_ggWave->init(0, ""); + ggWave->init(0, ""); std::mutex mutex; - std::thread inputThread([&mutex]() { + std::thread inputThread([&]() { std::string inputOld = ""; while (true) { std::string input; @@ -168,30 +86,23 @@ int main(int argc, char** argv) { } { std::lock_guard lock(mutex); - g_ggWave->init(input.size(), input.data()); + ggWave->init(input.size(), input.data()); } inputOld = input; } }); while (true) { - SDL_Delay(1); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); { std::lock_guard lock(mutex); - update(); + GGWave_mainLoop(); } } inputThread.join(); - delete g_ggWave; - - SDL_PauseAudioDevice(g_devIdIn, 1); - SDL_CloseAudioDevice(g_devIdIn); - SDL_PauseAudioDevice(g_devIdOut, 1); - SDL_CloseAudioDevice(g_devIdOut); - SDL_CloseAudio(); - SDL_Quit(); + GGWave_deinit(); return 0; } diff --git a/examples/ggwave-common-sdl2.cpp b/examples/ggwave-common-sdl2.cpp index d758659..3875842 100644 --- a/examples/ggwave-common-sdl2.cpp +++ b/examples/ggwave-common-sdl2.cpp @@ -2,17 +2,112 @@ #include "ggwave/ggwave.h" +#include "ggwave-common.h" + +#include + +#ifdef __EMSCRIPTEN__ +#include "emscripten/emscripten.h" +#else +#define EMSCRIPTEN_KEEPALIVE +#endif + constexpr double kBaseSampleRate = 48000.0; -bool initSDL2ForGGWave( - bool & isInitialized, +namespace { + +std::string g_defaultCaptureDeviceName = ""; + +bool g_isInitialized = false; + +SDL_AudioDeviceID g_devIdIn = 0; +SDL_AudioDeviceID g_devIdOut = 0; + +GGWave *g_ggWave = nullptr; + +} + +// JS interface +extern "C" { + EMSCRIPTEN_KEEPALIVE + int setText(int textLength, const char * text) { + g_ggWave->init(textLength, text); + return 0; + } + + EMSCRIPTEN_KEEPALIVE + int getText(char * text) { + std::copy(g_ggWave->getRxData().begin(), g_ggWave->getRxData().end(), text); + return 0; + } + + EMSCRIPTEN_KEEPALIVE + int getSampleRate() { return g_ggWave->getSampleRateIn(); } + + EMSCRIPTEN_KEEPALIVE + float getAverageRxTime_ms() { return g_ggWave->getAverageRxTime_ms(); } + + EMSCRIPTEN_KEEPALIVE + int getFramesToRecord() { return g_ggWave->getFramesToRecord(); } + + EMSCRIPTEN_KEEPALIVE + int getFramesLeftToRecord() { return g_ggWave->getFramesLeftToRecord(); } + + EMSCRIPTEN_KEEPALIVE + int getFramesToAnalyze() { return g_ggWave->getFramesToAnalyze(); } + + EMSCRIPTEN_KEEPALIVE + int getFramesLeftToAnalyze() { return g_ggWave->getFramesLeftToAnalyze(); } + + EMSCRIPTEN_KEEPALIVE + int hasDeviceOutput() { return g_devIdOut; } + + EMSCRIPTEN_KEEPALIVE + int hasDeviceCapture() { return (g_ggWave->getTotalBytesCaptured() > 0) ? g_devIdIn : 0; } + + EMSCRIPTEN_KEEPALIVE + int doInit() { + return GGWave_init(-1, -1); + } + + EMSCRIPTEN_KEEPALIVE + int setTxMode(int txMode) { + g_ggWave->setTxMode((GGWave::TxMode)(txMode)); + g_ggWave->init(0, ""); + return 0; + } + + EMSCRIPTEN_KEEPALIVE + void setParameters( + int paramFreqDelta, + int paramFreqStart, + int paramFramesPerTx, + int paramBytesPerTx, + int /*paramECCBytesPerTx*/, + int paramVolume) { + if (g_ggWave == nullptr) return; + + g_ggWave->setParameters( + paramFreqDelta, + paramFreqStart, + paramFramesPerTx, + paramBytesPerTx, + paramVolume); + + g_ggWave->init(0, ""); + } +} + +void GGWave_setDefaultCaptureDeviceName(std::string name) { + g_defaultCaptureDeviceName = std::move(name); +} + +bool GGWave_init( const int playbackId, - SDL_AudioDeviceID & devIdIn, - const int captureId, - SDL_AudioDeviceID & devIdOut, - GGWave *& ggWave, - const char * defaultCaptureDeviceName) { - if (isInitialized) return 0; + const int captureId) { + if (g_isInitialized) { + return false; + } printf("Initializing ...\n"); @@ -60,17 +155,17 @@ bool initSDL2ForGGWave( if (playbackId >= 0) { printf("Attempt to open playback device %d : '%s' ...\n", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE)); - devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &obtainedSpecOut, 0); + g_devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &obtainedSpecOut, 0); } else { printf("Attempt to open default playback device ...\n"); - devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &obtainedSpecOut, 0); + g_devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &obtainedSpecOut, 0); } - if (!devIdOut) { + if (!g_devIdOut) { printf("Couldn't open an audio device for playback: %s!\n", SDL_GetError()); - devIdOut = 0; + g_devIdOut = 0; } else { - printf("Obtained spec for output device (SDL Id = %d):\n", devIdOut); + printf("Obtained spec for output device (SDL Id = %d):\n", g_devIdOut); printf(" - Sample rate: %d (required: %d)\n", obtainedSpecOut.freq, playbackSpec.freq); printf(" - Format: %d (required: %d)\n", obtainedSpecOut.format, playbackSpec.format); printf(" - Channels: %d (required: %d)\n", obtainedSpecOut.channels, playbackSpec.channels); @@ -108,16 +203,17 @@ bool initSDL2ForGGWave( if (captureId >= 0) { printf("Attempt to open capture device %d : '%s' ...\n", captureId, SDL_GetAudioDeviceName(captureId, SDL_FALSE)); - devIdIn = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &obtainedSpecIn, 0); + g_devIdIn = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &obtainedSpecIn, 0); } else { printf("Attempt to open default capture device ...\n"); - devIdIn = SDL_OpenAudioDevice(defaultCaptureDeviceName, SDL_TRUE, &captureSpec, &obtainedSpecIn, 0); + g_devIdIn = SDL_OpenAudioDevice(g_defaultCaptureDeviceName.empty() ? nullptr : g_defaultCaptureDeviceName.c_str(), + SDL_TRUE, &captureSpec, &obtainedSpecIn, 0); } - if (!devIdIn) { + if (!g_devIdIn) { printf("Couldn't open an audio device for capture: %s!\n", SDL_GetError()); - devIdIn = 0; + g_devIdIn = 0; } else { - printf("Obtained spec for input device (SDL Id = %d):\n", devIdIn); + printf("Obtained spec for input device (SDL Id = %d):\n", g_devIdIn); printf(" - Sample rate: %d\n", obtainedSpecIn.freq); printf(" - Format: %d (required: %d)\n", obtainedSpecIn.format, captureSpec.format); printf(" - Channels: %d (required: %d)\n", obtainedSpecIn.channels, captureSpec.channels); @@ -139,14 +235,79 @@ bool initSDL2ForGGWave( break; } - ggWave = new GGWave( + g_ggWave = new GGWave( obtainedSpecIn.freq, obtainedSpecOut.freq, 1024, sampleSizeBytesIn, sampleSizeBytesOut); - isInitialized = true; - return 0; + g_isInitialized = true; + + return true; } +GGWave * GGWave_instance() { return g_ggWave; } + +bool GGWave_mainLoop() { + if (g_isInitialized == false) { + return false; + } + + static GGWave::CBQueueAudio cbQueueAudio = [&](const void * data, uint32_t nBytes) { + SDL_QueueAudio(g_devIdOut, data, nBytes); + }; + + static GGWave::CBDequeueAudio CBDequeueAudio = [&](void * data, uint32_t nMaxBytes) { + return SDL_DequeueAudio(g_devIdIn, data, nMaxBytes); + }; + + if (g_ggWave->getHasData() == false) { + SDL_PauseAudioDevice(g_devIdOut, SDL_FALSE); + + static auto tLastNoData = std::chrono::high_resolution_clock::now(); + auto tNow = std::chrono::high_resolution_clock::now(); + + if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesOut()) { + SDL_PauseAudioDevice(g_devIdIn, SDL_FALSE); + if (::getTime_ms(tLastNoData, tNow) > 500.0f) { + g_ggWave->receive(CBDequeueAudio); + if ((int) SDL_GetQueuedAudioSize(g_devIdIn) > 32*g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesIn()) { + SDL_ClearQueuedAudio(g_devIdIn); + } + } else { + SDL_ClearQueuedAudio(g_devIdIn); + } + } else { + tLastNoData = tNow; + } + } else { + SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE); + SDL_PauseAudioDevice(g_devIdIn, SDL_TRUE); + + g_ggWave->send(cbQueueAudio); + } + + return true; +} + +bool GGWave_deinit() { + if (g_isInitialized == false) { + return false; + } + + delete g_ggWave; + g_ggWave = nullptr; + + SDL_PauseAudioDevice(g_devIdIn, 1); + SDL_CloseAudioDevice(g_devIdIn); + SDL_PauseAudioDevice(g_devIdOut, 1); + SDL_CloseAudioDevice(g_devIdOut); + SDL_CloseAudio(); + SDL_Quit(); +#ifdef __EMSCRIPTEN__ + emscripten_cancel_main_loop(); +#endif + + return true; +} diff --git a/examples/ggwave-common-sdl2.h b/examples/ggwave-common-sdl2.h index 7f82bbd..7c9387c 100644 --- a/examples/ggwave-common-sdl2.h +++ b/examples/ggwave-common-sdl2.h @@ -2,13 +2,17 @@ #include +#include + class GGWave; -bool initSDL2ForGGWave( - bool & isInitialized, - const int playbackId, - SDL_AudioDeviceID & devIdIn, - const int captureId, - SDL_AudioDeviceID & devIdOut, - GGWave *& ggWave, - const char * defaultCaptureDeviceName = nullptr); +// GGWave helpers + +void GGWave_setDefaultCaptureDeviceName(std::string name); +bool GGWave_init(const int playbackId, const int captureId); +GGWave * GGWave_instance(); +bool GGWave_mainLoop(); +bool GGWave_deinit(); + +// ImGui helpers + diff --git a/examples/ggwave-gui/CMakeLists.txt b/examples/ggwave-gui/CMakeLists.txt new file mode 100644 index 0000000..664a204 --- /dev/null +++ b/examples/ggwave-gui/CMakeLists.txt @@ -0,0 +1,14 @@ +add_executable(ggwave-gui main.cpp) + +target_include_directories(ggwave-gui PRIVATE + .. + ${SDL2_INCLUDE_DIRS} + ) + +target_link_libraries(ggwave-gui PRIVATE + ggwave + ggwave-common + ggwave-common-sdl2 + imgui-sdl2 + ${CMAKE_THREAD_LIBS_INIT} + ) diff --git a/examples/ggwave-gui/main.cpp b/examples/ggwave-gui/main.cpp new file mode 100644 index 0000000..a074a22 --- /dev/null +++ b/examples/ggwave-gui/main.cpp @@ -0,0 +1,162 @@ +#include "ggwave/ggwave.h" + +#include "ggwave-common.h" +#include "ggwave-common-sdl2.h" + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +static SDL_Window * g_window; +static void * g_gl_context; + +int main(int argc, char** argv) { + printf("Usage: %s [-cN] [-pN] [-tN]\n", argv[0]); + printf(" -cN - select capture device N\n"); + printf(" -pN - select playback device N\n"); + printf(" -tN - transmission protocol:\n"); + printf(" -t0 : Normal\n"); + printf(" -t1 : Fast (default)\n"); + printf(" -t2 : Fastest\n"); + printf(" -t3 : Ultrasonic\n"); + printf("\n"); + + auto argm = parseCmdArguments(argc, argv); + int captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]); + int playbackId = argm["p"].empty() ? 0 : std::stoi(argm["p"]); + int txProtocol = argm["t"].empty() ? 1 : std::stoi(argm["t"]); + + if (GGWave_init(playbackId, captureId) == false) { + fprintf(stderr, "Failed to initialize GGWave\n"); + return -1; + } + + auto ggWave = GGWave_instance(); + + ggWave->setTxMode(GGWave::TxMode::VariableLength); + + printf("Selecting Tx protocol %d\n", txProtocol); + switch (txProtocol) { + case 0: + { + printf("Using 'Normal' Tx Protocol\n"); + ggWave->setParameters(1, 40, 9, 3, 50); + } + break; + case 1: + { + printf("Using 'Fast' Tx Protocol\n"); + ggWave->setParameters(1, 40, 6, 3, 50); + } + break; + case 2: + { + printf("Using 'Fastest' Tx Protocol\n"); + ggWave->setParameters(1, 40, 3, 3, 50); + } + break; + case 3: + { + printf("Using 'Ultrasonic' Tx Protocol\n"); + ggWave->setParameters(1, 320, 9, 3, 50); + } + break; + default: + { + printf("Using 'Fast' Tx Protocol\n"); + ggWave->setParameters(1, 40, 6, 3, 50); + } + }; + printf("\n"); + + ggWave->init(0, ""); + + std::mutex mutex; + std::thread inputThread([&]() { + std::string inputOld = ""; + while (true) { + std::string input; + std::cout << "Enter text: "; + getline(std::cin, input); + if (input.empty()) { + std::cout << "Re-sending ... " << std::endl; + input = inputOld; + } else { + std::cout << "Sending ... " << std::endl; + } + { + std::lock_guard lock(mutex); + ggWave->init(input.size(), input.data()); + } + inputOld = input; + } + }); + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { + fprintf(stderr, "Error: %s\n", SDL_GetError()); + return -1; + } + + int windowX = 400; + int windowY = 600; + const char * windowTitle = "ggwave-gui"; + + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + g_window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowX, windowY, window_flags); + + g_gl_context = SDL_GL_CreateContext(g_window); + SDL_GL_MakeCurrent(g_window, g_gl_context); + SDL_GL_SetSwapInterval(0); // Enable vsync + + ImGui_Init(g_window, g_gl_context); + + ImGui_NewFrame(g_window); + ImGui::Render(); + + while (true) { + { + std::lock_guard lock(mutex); + GGWave_mainLoop(); + } + + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ProcessEvent(&event); + if (event.type == SDL_QUIT) {} + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(g_window)) {} + } + + ImGui_NewFrame(g_window); + + ImGui::ShowDemoWindow(); + + // Rendering + int display_w, display_h; + SDL_GetWindowSize(g_window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(0.0f, 0.0f, 0.0f, 0.4f); + glClear(GL_COLOR_BUFFER_BIT); + + ImGui::Render(); + ImGui_RenderDrawData(ImGui::GetDrawData()); + + SDL_GL_SwapWindow(g_window); + } + + inputThread.join(); + + GGWave_deinit(); + + return 0; +} diff --git a/examples/ggwave-wasm/main.cpp b/examples/ggwave-wasm/main.cpp index ad6e541..3176299 100644 --- a/examples/ggwave-wasm/main.cpp +++ b/examples/ggwave-wasm/main.cpp @@ -1,179 +1,19 @@ -/*! \file main.cpp - * \brief Send/Receive - WebAssembly port - * \author Georgi Gerganov - */ - #include "ggwave/ggwave.h" -#include "ggwave-common.h" #include "ggwave-common-sdl2.h" -#include -#include - -#include -#include -#include - #include "build_timestamp.h" #include "emscripten/emscripten.h" -static char *g_defaultCaptureDeviceName = nullptr; - -static int g_captureId = -1; -static int g_playbackId = -1; - -static bool g_isInitialized = false; - -static SDL_AudioDeviceID g_devIdIn = 0; -static SDL_AudioDeviceID g_devIdOut = 0; - -static GGWave *g_ggWave = nullptr; - -// JS interface -extern "C" { - EMSCRIPTEN_KEEPALIVE - int setText(int textLength, const char * text) { - g_ggWave->init(textLength, text); - return 0; - } - - EMSCRIPTEN_KEEPALIVE - int getText(char * text) { - std::copy(g_ggWave->getRxData().begin(), g_ggWave->getRxData().end(), text); - return 0; - } - - EMSCRIPTEN_KEEPALIVE - int getSampleRate() { return g_ggWave->getSampleRateIn(); } - - EMSCRIPTEN_KEEPALIVE - float getAverageRxTime_ms() { return g_ggWave->getAverageRxTime_ms(); } - - EMSCRIPTEN_KEEPALIVE - int getFramesToRecord() { return g_ggWave->getFramesToRecord(); } - - EMSCRIPTEN_KEEPALIVE - int getFramesLeftToRecord() { return g_ggWave->getFramesLeftToRecord(); } - - EMSCRIPTEN_KEEPALIVE - int getFramesToAnalyze() { return g_ggWave->getFramesToAnalyze(); } - - EMSCRIPTEN_KEEPALIVE - int getFramesLeftToAnalyze() { return g_ggWave->getFramesLeftToAnalyze(); } - - EMSCRIPTEN_KEEPALIVE - int hasDeviceOutput() { return g_devIdOut; } - - EMSCRIPTEN_KEEPALIVE - int hasDeviceCapture() { return (g_ggWave->getTotalBytesCaptured() > 0) ? g_devIdIn : 0; } - - EMSCRIPTEN_KEEPALIVE - int doInit() { - return initSDL2ForGGWave( - g_isInitialized, - g_playbackId, - g_devIdIn, - g_captureId, - g_devIdOut, - g_ggWave, - g_defaultCaptureDeviceName); - } - - EMSCRIPTEN_KEEPALIVE - int setTxMode(int txMode) { - g_ggWave->setTxMode((GGWave::TxMode)(txMode)); - g_ggWave->init(0, ""); - return 0; - } - - EMSCRIPTEN_KEEPALIVE - void setParameters( - int paramFreqDelta, - int paramFreqStart, - int paramFramesPerTx, - int paramBytesPerTx, - int /*paramECCBytesPerTx*/, - int paramVolume) { - if (g_ggWave == nullptr) return; - - g_ggWave->setParameters( - paramFreqDelta, - paramFreqStart, - paramFramesPerTx, - paramBytesPerTx, - paramVolume); - - g_ggWave->init(0, ""); - } -} - -// main loop void update() { - if (g_isInitialized == false) return; - - SDL_Event e; - SDL_bool shouldTerminate = SDL_FALSE; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) { - shouldTerminate = SDL_TRUE; - } - } - - static GGWave::CBQueueAudio cbQueueAudio = [&](const void * data, uint32_t nBytes) { - SDL_QueueAudio(g_devIdOut, data, nBytes); - }; - - static GGWave::CBDequeueAudio CBDequeueAudio = [&](void * data, uint32_t nMaxBytes) { - return SDL_DequeueAudio(g_devIdIn, data, nMaxBytes); - }; - - if (g_ggWave->getHasData() == false) { - SDL_PauseAudioDevice(g_devIdOut, SDL_FALSE); - - static auto tLastNoData = std::chrono::high_resolution_clock::now(); - auto tNow = std::chrono::high_resolution_clock::now(); - - if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesOut()) { - SDL_PauseAudioDevice(g_devIdIn, SDL_FALSE); - if (::getTime_ms(tLastNoData, tNow) > 500.0f) { - g_ggWave->receive(CBDequeueAudio); - if ((int) SDL_GetQueuedAudioSize(g_devIdIn) > 32*g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesIn()) { - SDL_ClearQueuedAudio(g_devIdIn); - } - } else { - SDL_ClearQueuedAudio(g_devIdIn); - } - } else { - tLastNoData = tNow; - //SDL_ClearQueuedAudio(g_devIdIn); - //SDL_Delay(10); - } - } else { - SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE); - SDL_PauseAudioDevice(g_devIdIn, SDL_TRUE); - - g_ggWave->send(cbQueueAudio); - } - - if (shouldTerminate) { - SDL_PauseAudioDevice(g_devIdIn, 1); - SDL_CloseAudioDevice(g_devIdIn); - SDL_PauseAudioDevice(g_devIdOut, 1); - SDL_CloseAudioDevice(g_devIdOut); - SDL_CloseAudio(); - SDL_Quit(); - #ifdef __EMSCRIPTEN__ - emscripten_cancel_main_loop(); - #endif - } + GGWave_mainLoop(); } int main(int , char** argv) { printf("Build time: %s\n", BUILD_TIMESTAMP); printf("Press the Init button to start\n"); - g_defaultCaptureDeviceName = argv[1]; + GGWave_setDefaultCaptureDeviceName(argv[1]); emscripten_set_main_loop(update, 60, 1); diff --git a/examples/ggwave-wasm/main.js b/examples/ggwave-wasm/main.js index fd64655..af2e63b 100644 --- a/examples/ggwave-wasm/main.js +++ b/examples/ggwave-wasm/main.js @@ -1,8 +1,3 @@ -/*! \file main.js - * \brief Text transfer over sound - * \author Georgi Gerganov - */ - function transmitText(sText) { var r = new Uint8Array(256); for (var i = 0; i < sText.length; ++i) { diff --git a/examples/third-party/CMakeLists.txt b/examples/third-party/CMakeLists.txt new file mode 100644 index 0000000..60b0ddc --- /dev/null +++ b/examples/third-party/CMakeLists.txt @@ -0,0 +1,4 @@ +if (NOT EMSCRIPTEN) + add_subdirectory(imtui) + add_subdirectory(imgui) +endif() diff --git a/examples/third-party/imgui/CMakeLists.txt b/examples/third-party/imgui/CMakeLists.txt new file mode 100644 index 0000000..32e6f60 --- /dev/null +++ b/examples/third-party/imgui/CMakeLists.txt @@ -0,0 +1,67 @@ +if (GGWAVE_ALL_WARNINGS_3RD_PARTY) + if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic") + else() + # todo : windows + endif() +endif() + +if (APPLE) + set(ADDITIONAL_LIBRARIES "-framework Cocoa") +else (APPLE) + unset(ADDITIONAL_LIBRARIES) +endif (APPLE) + +add_library(imgui SHARED + imgui/imgui.cpp + imgui/imgui_draw.cpp + imgui/imgui_demo.cpp + imgui/imgui_widgets.cpp + ) + +target_include_directories(imgui INTERFACE + . + ) + +target_include_directories(imgui PRIVATE + imgui + ) + +target_link_libraries(imgui PRIVATE + ${ADDITIONAL_LIBRARIES} + ) + +if (GGWAVE_SUPPORT_SDL2) + find_package(OpenGL REQUIRED) + find_package(SDL2 REQUIRED) + string(STRIP "${SDL2_LIBRARIES}" SDL2_LIBRARIES) + + add_library(imgui-sdl2 SHARED + imgui/examples/libs/gl3w/GL/gl3w.c + imgui-extra/imgui_impl.cpp + imgui-extra/imgui_impl_sdl.cpp + imgui-extra/imgui_impl_opengl3.cpp + ) + + target_include_directories(imgui-sdl2 PUBLIC + imgui/examples/libs/gl3w + ${SDL2_INCLUDE_DIRS} + ) + + target_include_directories(imgui-sdl2 PRIVATE + imgui + imgui-extra + ) + + target_link_libraries(imgui-sdl2 PUBLIC + imgui + ${OPENGL_LIBRARIES} + ${SDL2_LIBRARIES} + ) + + target_link_libraries(imgui-sdl2 PRIVATE + ${CMAKE_DL_LIBS} + ${CMAKE_THREAD_LIBS_INIT} + ${ADDITIONAL_LIBRARIES} + ) +endif() diff --git a/examples/third-party/imgui/imgui b/examples/third-party/imgui/imgui new file mode 160000 index 0000000..9801c8c --- /dev/null +++ b/examples/third-party/imgui/imgui @@ -0,0 +1 @@ +Subproject commit 9801c8c1c5f8757e8eed43592beb0a5e215c4291 diff --git a/examples/third-party/imgui/imgui-extra/imgui_impl.cpp b/examples/third-party/imgui/imgui-extra/imgui_impl.cpp new file mode 100644 index 0000000..0edb746 --- /dev/null +++ b/examples/third-party/imgui/imgui-extra/imgui_impl.cpp @@ -0,0 +1,95 @@ +#include "imgui-extra/imgui_impl.h" + +#include "imgui-extra/imgui_impl_sdl.h" +#include "imgui-extra/imgui_impl_opengl3.h" + +#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) +#include // Initialize with gl3wInit() +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) +#include // Initialize with glewInit() +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) +#include // Initialize with gladLoadGL() +#else +#include IMGUI_IMPL_OPENGL_LOADER_CUSTOM +#endif + +#include + +ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) { + // Decide GL+GLSL versions +#if __APPLE__ + // GL 3.2 Core + GLSL 150 + const char* glsl_version = "#version 150"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); +#else + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#endif + + // Create window with graphics context + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + + static bool isInitialized = false; + if (!isInitialized) { + // Initialize OpenGL loader +#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) + bool err = gl3wInit() != 0; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) + bool err = glewInit() != GLEW_OK; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) + bool err = gladLoadGL() == 0; +#else + bool err = false; // If you use IMGUI_IMPL_OPENGL_LOADER_CUSTOM, your loader is likely to requires some form of initialization. +#endif + + if (err) { + fprintf(stderr, "Failed to initialize OpenGL loader!\n"); + return nullptr; + } + isInitialized = true; + } + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + auto ctx = ImGui::CreateContext(); + ImGui::SetCurrentContext(ctx); + + ImGuiIO& io = ImGui::GetIO(); (void)io; + //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Platform/Renderer bindings + bool res = true; + res &= ImGui_ImplSDL2_InitForOpenGL(window, gl_context); + +#ifdef __EMSCRIPTEN__ + res &= ImGui_ImplOpenGL3_Init("#version 300 es"); +#else + res &= ImGui_ImplOpenGL3_Init(glsl_version); +#endif + + return res ? ctx : nullptr; +} + +void ImGui_Shutdown() { ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); } +void ImGui_NewFrame(SDL_Window* window) { ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(window); ImGui::NewFrame(); } +bool ImGui_ProcessEvent(const SDL_Event* event) { return ImGui_ImplSDL2_ProcessEvent(event); } + +void ImGui_RenderDrawData(ImDrawData* draw_data) { ImGui_ImplOpenGL3_RenderDrawData(draw_data); } + +bool ImGui_CreateFontsTexture() { return ImGui_ImplOpenGL3_CreateFontsTexture(); } +void ImGui_DestroyFontsTexture() { ImGui_ImplOpenGL3_DestroyFontsTexture(); } +bool ImGui_CreateDeviceObjects() { return ImGui_ImplOpenGL3_CreateDeviceObjects(); } +void ImGui_DestroyDeviceObjects() { ImGui_ImplOpenGL3_DestroyDeviceObjects(); } + +void ImGui_SaveState(int id) { ImGui_ImplSDL2_SaveState(id); ImGui_ImplOpenGL3_SaveState(id); } +void ImGui_LoadState(int id) { ImGui_ImplSDL2_LoadState(id); ImGui_ImplOpenGL3_LoadState(id); } diff --git a/examples/third-party/imgui/imgui-extra/imgui_impl.h b/examples/third-party/imgui/imgui-extra/imgui_impl.h new file mode 100644 index 0000000..03c5b0d --- /dev/null +++ b/examples/third-party/imgui/imgui-extra/imgui_impl.h @@ -0,0 +1,31 @@ +/*! \file imgui_impl.h + * \brief Enter description here. + */ + +#pragma once + +#if (defined(__EMSCRIPTEN__)) +#define IMGUI_IMPL_OPENGL_LOADER_GLEW +#endif + +#include "imgui/imgui.h" + +struct SDL_Window; +typedef void * SDL_GLContext; +typedef union SDL_Event SDL_Event; + +IMGUI_API ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context); + +void IMGUI_API ImGui_Shutdown(); +void IMGUI_API ImGui_NewFrame(SDL_Window* window); +bool IMGUI_API ImGui_ProcessEvent(const SDL_Event* event); + +void IMGUI_API ImGui_RenderDrawData(ImDrawData* draw_data); + +bool IMGUI_API ImGui_CreateFontsTexture(); +void IMGUI_API ImGui_DestroyFontsTexture(); +bool IMGUI_API ImGui_CreateDeviceObjects(); +void IMGUI_API ImGui_DestroyDeviceObjects(); + +void IMGUI_API ImGui_SaveState(int id); +void IMGUI_API ImGui_LoadState(int id); diff --git a/examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.cpp b/examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.cpp new file mode 100644 index 0000000..3eca476 --- /dev/null +++ b/examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.cpp @@ -0,0 +1,722 @@ +// dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline +// - Desktop GL: 2.x 3.x 4.x +// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) +// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! +// [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2020-01-07: OpenGL: Added support for glbindings OpenGL loader. +// 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders. +// 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility. +// 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call. +// 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. +// 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. +// 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop. +// 2019-03-15: OpenGL: Added a dummy GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early. +// 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0). +// 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader. +// 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. +// 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450). +// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. +// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN. +// 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used. +// 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES". +// 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation. +// 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link. +// 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples. +// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. +// 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state. +// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer. +// 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150". +// 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself. +// 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. +// 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. +// 2017-05-01: OpenGL: Fixed save and restore of current blend func state. +// 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. +// 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752) + +//---------------------------------------- +// OpenGL GLSL GLSL +// version version string +//---------------------------------------- +// 2.0 110 "#version 110" +// 2.1 120 "#version 120" +// 3.0 130 "#version 130" +// 3.1 140 "#version 140" +// 3.2 150 "#version 150" +// 3.3 330 "#version 330 core" +// 4.0 400 "#version 400 core" +// 4.1 410 "#version 410 core" +// 4.2 420 "#version 410 core" +// 4.3 430 "#version 430 core" +// ES 2.0 100 "#version 100" = WebGL 1.0 +// ES 3.0 300 "#version 300 es" = WebGL 2.0 +//---------------------------------------- + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#include "imgui_impl_opengl3.h" +#include +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif + +// Auto-enable GLES on matching platforms +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) +#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" +#undef IMGUI_IMPL_OPENGL_LOADER_GL3W +#undef IMGUI_IMPL_OPENGL_LOADER_GLEW +#undef IMGUI_IMPL_OPENGL_LOADER_GLAD +#undef IMGUI_IMPL_OPENGL_LOADER_GLBINDING +#undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM +#elif defined(__EMSCRIPTEN__) +#define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" +#undef IMGUI_IMPL_OPENGL_LOADER_GL3W +#undef IMGUI_IMPL_OPENGL_LOADER_GLEW +#undef IMGUI_IMPL_OPENGL_LOADER_GLAD +#undef IMGUI_IMPL_OPENGL_LOADER_GLBINDING +#undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM +#endif +#endif + +// GL includes +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include +#elif defined(IMGUI_IMPL_OPENGL_ES3) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) +#include // Use GL ES 3 +#else +#include // Use GL ES 3 +#endif +#else +// About Desktop OpenGL function loaders: +// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. +// Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). +// You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. +#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) +#include // Needs to be initialized with gl3wInit() in user's code +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) +#include // Needs to be initialized with glewInit() in user's code +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) +#include // Needs to be initialized with gladLoadGL() in user's code +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING) +#include // Initialize with glbinding::initialize() +#include +using namespace gl; +#else +#include IMGUI_IMPL_OPENGL_LOADER_CUSTOM +#endif +#endif + +// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have. +#if defined(IMGUI_IMPL_OPENGL_ES2) || defined(IMGUI_IMPL_OPENGL_ES3) || !defined(GL_VERSION_3_2) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 0 +#else +#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 1 +#endif + +// OpenGL Data +static GLuint g_GlVersion = 0; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries. +static char g_GlslVersionString[32] = ""; // Specified by user or detected based on compile time GL settings. +static GLuint g_FontTexture = 0; +static GLuint g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; +static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; // Uniforms location +static int g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0, g_AttribLocationVtxColor = 0; // Vertex attributes location +static unsigned int g_VboHandle = 0, g_ElementsHandle = 0; + +#include + +struct State { + GLuint FontTexture = 0; + GLuint ShaderHandle = 0, VertHandle = 0, FragHandle = 0; + int AttribLocationTex = 0, AttribLocationProjMtx = 0; + int AttribLocationVtxPos = 0, AttribLocationVtxUV = 0, AttribLocationVtxColor = 0; + unsigned int VboHandle = 0, ElementsHandle = 0; +}; + +static std::map g_state; + +void ImGui_ImplOpenGL3_SaveState(int id) { + auto & dst = g_state[id]; + + dst.FontTexture = g_FontTexture; + dst.FontTexture = g_FontTexture; + dst.ShaderHandle = g_ShaderHandle; + dst.VertHandle = g_VertHandle; + dst.FragHandle = g_FragHandle; + dst.AttribLocationTex = g_AttribLocationTex; + dst.AttribLocationProjMtx = g_AttribLocationProjMtx; + dst.AttribLocationVtxPos = g_AttribLocationVtxPos; + dst.AttribLocationVtxUV = g_AttribLocationVtxUV; + dst.AttribLocationVtxColor = g_AttribLocationVtxColor; + dst.VboHandle = g_VboHandle; + dst.ElementsHandle = g_ElementsHandle; +} + +void ImGui_ImplOpenGL3_LoadState(int id) { + const auto & src = g_state[id]; + + g_FontTexture = src.FontTexture; + g_ShaderHandle = src.ShaderHandle; + g_VertHandle = src.VertHandle; + g_FragHandle = src.FragHandle; + g_AttribLocationTex = src.AttribLocationTex; + g_AttribLocationProjMtx = src.AttribLocationProjMtx; + g_AttribLocationVtxPos = src.AttribLocationVtxPos; + g_AttribLocationVtxUV = src.AttribLocationVtxUV; + g_AttribLocationVtxColor = src.AttribLocationVtxColor; + g_VboHandle = src.VboHandle; + g_ElementsHandle = src.ElementsHandle; +} + +// Functions +bool ImGui_ImplOpenGL3_Init(const char* glsl_version) +{ + // Query for GL version +#if !defined(IMGUI_IMPL_OPENGL_ES2) + GLint major, minor; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + g_GlVersion = major * 1000 + minor; +#else + g_GlVersion = 2000; // GLES 2 +#endif + + // Setup back-end capabilities flags + ImGuiIO& io = ImGui::GetIO(); + io.BackendRendererName = "imgui_impl_opengl3"; +#if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET + if (g_GlVersion >= 3200) + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. +#endif + + // Store GLSL version string so we can refer to it later in case we recreate shaders. + // Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure. +#if defined(IMGUI_IMPL_OPENGL_ES2) + if (glsl_version == NULL) + glsl_version = "#version 100"; +#elif defined(IMGUI_IMPL_OPENGL_ES3) + if (glsl_version == NULL) + glsl_version = "#version 300 es"; +#else + if (glsl_version == NULL) + glsl_version = "#version 130"; +#endif + IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString)); + strcpy(g_GlslVersionString, glsl_version); + strcat(g_GlslVersionString, "\n"); + + // Dummy construct to make it easily visible in the IDE and debugger which GL loader has been selected. + // The code actually never uses the 'gl_loader' variable! It is only here so you can read it! + // If auto-detection fails or doesn't select the same GL loader file as used by your application, + // you are likely to get a crash below. + // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. + const char* gl_loader = "Unknown"; + IM_UNUSED(gl_loader); +#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) + gl_loader = "GL3W"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) + gl_loader = "GLEW"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) + gl_loader = "GLAD"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING) + gl_loader = "glbinding"; +#else // IMGUI_IMPL_OPENGL_LOADER_CUSTOM + gl_loader = "Custom"; +#endif + + // Make a dummy GL call (we don't actually need the result) + // IF YOU GET A CRASH HERE: it probably means that you haven't initialized the OpenGL function loader used by this code. + // Desktop OpenGL 3/4 need a function loader. See the IMGUI_IMPL_OPENGL_LOADER_xxx explanation above. + GLint current_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); + + return true; +} + +void ImGui_ImplOpenGL3_Shutdown() +{ + ImGui_ImplOpenGL3_DestroyDeviceObjects(); +} + +void ImGui_ImplOpenGL3_NewFrame() +{ + if (!g_ShaderHandle) + ImGui_ImplOpenGL3_CreateDeviceObjects(); +} + +static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) +{ + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); +#ifdef GL_POLYGON_MODE + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; + const float ortho_projection[4][4] = + { + { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, + }; + glUseProgram(g_ShaderHandle); + glUniform1i(g_AttribLocationTex, 0); + glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); +#ifdef GL_SAMPLER_BINDING + glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise. +#endif + + (void)vertex_array_object; +#ifndef IMGUI_IMPL_OPENGL_ES2 + glBindVertexArray(vertex_array_object); +#endif + + // Bind vertex/index buffers and setup attributes for ImDrawVert + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); + glEnableVertexAttribArray(g_AttribLocationVtxPos); + glEnableVertexAttribArray(g_AttribLocationVtxUV); + glEnableVertexAttribArray(g_AttribLocationVtxColor); + glVertexAttribPointer(g_AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(g_AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); + glVertexAttribPointer(g_AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); +} + +// OpenGL3 Render function. +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. +void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0) + return; + + // Backup GL state + GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); + glActiveTexture(GL_TEXTURE0); + GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); +#ifdef GL_SAMPLER_BINDING + GLint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, &last_sampler); +#endif + GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_ES2 + GLint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array_object); +#endif +#ifdef GL_POLYGON_MODE + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); +#endif + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); + GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); + GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); + GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); + GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); + GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); + GLboolean last_enable_blend = glIsEnabled(GL_BLEND); + GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); + GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); + GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); + bool clip_origin_lower_left = true; +#if defined(GL_CLIP_ORIGIN) && !defined(__APPLE__) + GLenum last_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&last_clip_origin); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT) + if (last_clip_origin == GL_UPPER_LEFT) + clip_origin_lower_left = false; +#endif + + // Setup desired GL state + // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) + // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. + GLuint vertex_array_object = 0; +#ifndef IMGUI_IMPL_OPENGL_ES2 + glGenVertexArrays(1, &vertex_array_object); +#endif + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + + // Upload vertex/index buffers + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != NULL) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + else + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec4 clip_rect; + clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; + clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; + clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; + clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; + + if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) + { + // Apply scissor/clipping rectangle + if (clip_origin_lower_left) + glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); + else + glScissor((int)clip_rect.x, (int)clip_rect.y, (int)clip_rect.z, (int)clip_rect.w); // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) + + // Bind texture, Draw + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); +#if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET + if (g_GlVersion >= 3200) + glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset); + else +#endif + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))); + } + } + } + } + + // Destroy the temporary VAO +#ifndef IMGUI_IMPL_OPENGL_ES2 + glDeleteVertexArrays(1, &vertex_array_object); +#endif + + // Restore modified GL state + glUseProgram(last_program); + glBindTexture(GL_TEXTURE_2D, last_texture); +#ifdef GL_SAMPLER_BINDING + glBindSampler(0, last_sampler); +#endif + glActiveTexture(last_active_texture); +#ifndef IMGUI_IMPL_OPENGL_ES2 + glBindVertexArray(last_vertex_array_object); +#endif + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); + glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); + glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); + if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); + if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); + if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); + if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); +#ifdef GL_POLYGON_MODE + glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); +#endif + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +bool ImGui_ImplOpenGL3_CreateFontsTexture() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +#ifdef GL_UNPACK_ROW_LENGTH + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); +#endif + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +void ImGui_ImplOpenGL3_DestroyFontsTexture() +{ + if (g_FontTexture) + { + ImGuiIO& io = ImGui::GetIO(); + glDeleteTextures(1, &g_FontTexture); + io.Fonts->TexID = 0; + g_FontTexture = 0; + } +} + +// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. +static bool CheckShader(GLuint handle, const char* desc) +{ + GLint status = 0, log_length = 0; + glGetShaderiv(handle, GL_COMPILE_STATUS, &status); + glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s!\n", desc); + if (log_length > 1) + { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +// If you get an error please report on GitHub. You may try different GL context version or GLSL version. +static bool CheckProgram(GLuint handle, const char* desc) +{ + GLint status = 0, log_length = 0; + glGetProgramiv(handle, GL_LINK_STATUS, &status); + glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')\n", desc, g_GlslVersionString); + if (log_length > 1) + { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +bool ImGui_ImplOpenGL3_CreateDeviceObjects() +{ + // Backup GL state + GLint last_texture, last_array_buffer; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_ES2 + GLint last_vertex_array; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); +#endif + + // Parse GLSL version string + int glsl_version = 130; + sscanf(g_GlslVersionString, "#version %d", &glsl_version); + + const GLchar* vertex_shader_glsl_120 = + "uniform mat4 ProjMtx;\n" + "attribute vec2 Position;\n" + "attribute vec2 UV;\n" + "attribute vec4 Color;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_130 = + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_300_es = + "precision mediump float;\n" + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_410_core = + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_120 = + "#ifdef GL_ES\n" + " precision mediump float;\n" + "#endif\n" + "uniform sampler2D Texture;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_130 = + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_300_es = + "precision mediump float;\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_410_core = + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "uniform sampler2D Texture;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + // Select shaders matching our GLSL versions + const GLchar* vertex_shader = NULL; + const GLchar* fragment_shader = NULL; + if (glsl_version < 130) + { + vertex_shader = vertex_shader_glsl_120; + fragment_shader = fragment_shader_glsl_120; + } + else if (glsl_version >= 410) + { + vertex_shader = vertex_shader_glsl_410_core; + fragment_shader = fragment_shader_glsl_410_core; + } + else if (glsl_version == 300) + { + vertex_shader = vertex_shader_glsl_300_es; + fragment_shader = fragment_shader_glsl_300_es; + } + else + { + vertex_shader = vertex_shader_glsl_130; + fragment_shader = fragment_shader_glsl_130; + } + + // Create shaders + const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader }; + g_VertHandle = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(g_VertHandle, 2, vertex_shader_with_version, NULL); + glCompileShader(g_VertHandle); + CheckShader(g_VertHandle, "vertex shader"); + + const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader }; + g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(g_FragHandle, 2, fragment_shader_with_version, NULL); + glCompileShader(g_FragHandle); + CheckShader(g_FragHandle, "fragment shader"); + + g_ShaderHandle = glCreateProgram(); + glAttachShader(g_ShaderHandle, g_VertHandle); + glAttachShader(g_ShaderHandle, g_FragHandle); + glLinkProgram(g_ShaderHandle); + CheckProgram(g_ShaderHandle, "shader program"); + + g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); + g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); + g_AttribLocationVtxPos = glGetAttribLocation(g_ShaderHandle, "Position"); + g_AttribLocationVtxUV = glGetAttribLocation(g_ShaderHandle, "UV"); + g_AttribLocationVtxColor = glGetAttribLocation(g_ShaderHandle, "Color"); + + // Create buffers + glGenBuffers(1, &g_VboHandle); + glGenBuffers(1, &g_ElementsHandle); + + ImGui_ImplOpenGL3_CreateFontsTexture(); + + // Restore modified GL state + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_ES2 + glBindVertexArray(last_vertex_array); +#endif + + return true; +} + +void ImGui_ImplOpenGL3_DestroyDeviceObjects() +{ + if (g_VboHandle) { glDeleteBuffers(1, &g_VboHandle); g_VboHandle = 0; } + if (g_ElementsHandle) { glDeleteBuffers(1, &g_ElementsHandle); g_ElementsHandle = 0; } + if (g_ShaderHandle && g_VertHandle) { glDetachShader(g_ShaderHandle, g_VertHandle); } + if (g_ShaderHandle && g_FragHandle) { glDetachShader(g_ShaderHandle, g_FragHandle); } + if (g_VertHandle) { glDeleteShader(g_VertHandle); g_VertHandle = 0; } + if (g_FragHandle) { glDeleteShader(g_FragHandle); g_FragHandle = 0; } + if (g_ShaderHandle) { glDeleteProgram(g_ShaderHandle); g_ShaderHandle = 0; } + + ImGui_ImplOpenGL3_DestroyFontsTexture(); +} diff --git a/examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.h b/examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.h new file mode 100644 index 0000000..4f28def --- /dev/null +++ b/examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.h @@ -0,0 +1,70 @@ +// dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline +// - Desktop GL: 2.x 3.x 4.x +// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) +// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! +// [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// About Desktop OpenGL function loaders: +// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. +// Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). +// You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. + +// About GLSL version: +// The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string. +// On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" +// Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. + +#pragma once + +// Backend API +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); + +// (Optional) Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); + +IMGUI_IMPL_API void ImGui_ImplOpenGL3_SaveState(int id); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_LoadState(int id); + +// Specific OpenGL versions +//#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten +//#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android + +// Desktop OpenGL: attempt to detect default GL loader based on available header files. +// If auto-detection fails or doesn't select the same GL loader file as used by your application, +// you are likely to get a crash in ImGui_ImplOpenGL3_Init(). +// You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. +#if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) + #if defined(__has_include) + #if __has_include() + #define IMGUI_IMPL_OPENGL_LOADER_GLEW + #elif __has_include() + #define IMGUI_IMPL_OPENGL_LOADER_GLAD + #elif __has_include() + #define IMGUI_IMPL_OPENGL_LOADER_GL3W + #elif __has_include() + #define IMGUI_IMPL_OPENGL_LOADER_GLBINDING + #else + #error "Cannot detect OpenGL loader!" + #endif + #else + #define IMGUI_IMPL_OPENGL_LOADER_GL3W // Default to GL3W + #endif +#endif + diff --git a/examples/third-party/imgui/imgui-extra/imgui_impl_sdl.cpp b/examples/third-party/imgui/imgui-extra/imgui_impl_sdl.cpp new file mode 100644 index 0000000..e5ed575 --- /dev/null +++ b/examples/third-party/imgui/imgui-extra/imgui_impl_sdl.cpp @@ -0,0 +1,410 @@ +// dear imgui: Platform Binding for SDL2 +// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) +// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.) +// (Requires: SDL 2.0. Prefer SDL 2.0.4+ for full feature support.) + +// Implemented features: +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Clipboard support. +// [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE). +// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// Missing features: +// [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2020-02-20: Inputs: Fixed mapping for ImGuiKey_KeyPadEnter (using SDL_SCANCODE_KP_ENTER instead of SDL_SCANCODE_RETURN2). +// 2019-12-17: Inputs: On Wayland, use SDL_GetMouseState (because there is no global mouse state). +// 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor. +// 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter. +// 2019-04-23: Inputs: Added support for SDL_GameController (if ImGuiConfigFlags_NavEnableGamepad is set by user application). +// 2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized. +// 2018-12-21: Inputs: Workaround for Android/iOS which don't seem to handle focus related calls. +// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window. +// 2018-11-14: Changed the signature of ImGui_ImplSDL2_ProcessEvent() to take a 'const SDL_Event*'. +// 2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls. +// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor. +// 2018-06-08: Misc: Extracted imgui_impl_sdl.cpp/.h away from the old combined SDL2+OpenGL/Vulkan examples. +// 2018-06-08: Misc: ImGui_ImplSDL2_InitForOpenGL() now takes a SDL_GLContext parameter. +// 2018-05-09: Misc: Fixed clipboard paste memory leak (we didn't call SDL_FreeMemory on the data returned by SDL_GetClipboardText). +// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag. +// 2018-02-16: Inputs: Added support for mouse cursors, honoring ImGui::GetMouseCursor() value. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-02-05: Misc: Using SDL_GetPerformanceCounter() instead of SDL_GetTicks() to be able to handle very high framerate (1000+ FPS). +// 2018-02-05: Inputs: Keyboard mapping is using scancodes everywhere instead of a confusing mixture of keycodes and scancodes. +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-19: Inputs: When available (SDL 2.0.4+) using SDL_CaptureMouse() to retrieve coordinates outside of client area when dragging. Otherwise (SDL 2.0.3 and before) testing for SDL_WINDOW_INPUT_FOCUS instead of SDL_WINDOW_MOUSE_FOCUS. +// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. +// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). +// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. + +#include "imgui.h" +#include "imgui_impl_sdl.h" + +// SDL +#include +#include +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif + +#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE SDL_VERSION_ATLEAST(2,0,4) +#define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6) + +// Data +static SDL_Window* g_Window = NULL; +static Uint64 g_Time = 0; +static bool g_MousePressed[3] = { false, false, false }; +static SDL_Cursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {}; +static char* g_ClipboardTextData = NULL; +static bool g_MouseCanUseGlobalState = true; + +#include + +struct State { + SDL_Window* Window = NULL; + Uint64 Time = 0; + bool MousePressed[3] = { false, false, false }; + SDL_Cursor* MouseCursors[ImGuiMouseCursor_COUNT] = {}; + char* ClipboardTextData = NULL; + bool MouseCanUseGlobalState = true; +}; + +static std::map g_state; + +void ImGui_ImplSDL2_SaveState(int id) { + auto & dst = g_state[id]; + + dst.Window = g_Window; + dst.Time = g_Time; + dst.MousePressed[0] = g_MousePressed[0]; + dst.MousePressed[1] = g_MousePressed[1]; + dst.MousePressed[2] = g_MousePressed[2]; + for (int i = 0; i < ImGuiMouseCursor_COUNT; ++i) { + dst.MouseCursors[i] = g_MouseCursors[i]; + } + dst.ClipboardTextData = g_ClipboardTextData; + dst.MouseCanUseGlobalState = g_MouseCanUseGlobalState; +} + +void ImGui_ImplSDL2_LoadState(int id) { + const auto & src = g_state[id]; + + g_Window = src.Window; + g_Time = src.Time; + g_MousePressed[0] = src.MousePressed[0]; + g_MousePressed[1] = src.MousePressed[1]; + g_MousePressed[2] = src.MousePressed[2]; + for (int i = 0; i < ImGuiMouseCursor_COUNT; ++i) { + g_MouseCursors[i] = src.MouseCursors[i]; + } + g_ClipboardTextData = src.ClipboardTextData; + g_MouseCanUseGlobalState = src.MouseCanUseGlobalState; +} + +static const char* ImGui_ImplSDL2_GetClipboardText(void*) +{ + if (g_ClipboardTextData) + SDL_free(g_ClipboardTextData); + g_ClipboardTextData = SDL_GetClipboardText(); + return g_ClipboardTextData; +} + +static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text) +{ + SDL_SetClipboardText(text); +} + +// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. +// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. +// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. +// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. +// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field. +bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) +{ + ImGuiIO& io = ImGui::GetIO(); + switch (event->type) + { + case SDL_MOUSEWHEEL: + { + if (event->wheel.x > 0) io.MouseWheelH += 1; + if (event->wheel.x < 0) io.MouseWheelH -= 1; + if (event->wheel.y > 0) io.MouseWheel += 1; + if (event->wheel.y < 0) io.MouseWheel -= 1; + return true; + } + case SDL_MOUSEBUTTONDOWN: + { + if (event->button.button == SDL_BUTTON_LEFT) g_MousePressed[0] = true; + if (event->button.button == SDL_BUTTON_RIGHT) g_MousePressed[1] = true; + if (event->button.button == SDL_BUTTON_MIDDLE) g_MousePressed[2] = true; + return true; + } + case SDL_TEXTINPUT: + { + io.AddInputCharactersUTF8(event->text.text); + return true; + } + case SDL_KEYDOWN: + case SDL_KEYUP: + { + int key = event->key.keysym.scancode; + IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown)); + io.KeysDown[key] = (event->type == SDL_KEYDOWN); + io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0); + io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0); + io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0); +#ifdef _WIN32 + io.KeySuper = false; +#else + io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0); +#endif + return true; + } + } + return false; +} + +static bool ImGui_ImplSDL2_Init(SDL_Window* window) +{ + g_Window = window; + + // Setup back-end capabilities flags + ImGuiIO& io = ImGui::GetIO(); + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) + io.BackendPlatformName = "imgui_impl_sdl"; + + // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP; + io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN; + io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP; + io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN; + io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME; + io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END; + io.KeyMap[ImGuiKey_Insert] = SDL_SCANCODE_INSERT; + io.KeyMap[ImGuiKey_Delete] = SDL_SCANCODE_DELETE; + io.KeyMap[ImGuiKey_Backspace] = SDL_SCANCODE_BACKSPACE; + io.KeyMap[ImGuiKey_Space] = SDL_SCANCODE_SPACE; + io.KeyMap[ImGuiKey_Enter] = SDL_SCANCODE_RETURN; + io.KeyMap[ImGuiKey_Escape] = SDL_SCANCODE_ESCAPE; + io.KeyMap[ImGuiKey_KeyPadEnter] = SDL_SCANCODE_KP_ENTER; + io.KeyMap[ImGuiKey_A] = SDL_SCANCODE_A; + io.KeyMap[ImGuiKey_C] = SDL_SCANCODE_C; + io.KeyMap[ImGuiKey_V] = SDL_SCANCODE_V; + io.KeyMap[ImGuiKey_X] = SDL_SCANCODE_X; + io.KeyMap[ImGuiKey_Y] = SDL_SCANCODE_Y; + io.KeyMap[ImGuiKey_Z] = SDL_SCANCODE_Z; + + io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText; + io.ClipboardUserData = NULL; + + // Load mouse cursors + g_MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + g_MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); + g_MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + g_MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); + g_MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); + g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); + g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); + g_MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); + g_MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); + + // Check and store if we are on Wayland + g_MouseCanUseGlobalState = strncmp(SDL_GetCurrentVideoDriver(), "wayland", 7) != 0; + +#ifdef _WIN32 + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); + io.ImeWindowHandle = wmInfo.info.win.window; +#else + (void)window; +#endif + + return true; +} + +bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context) +{ + (void)sdl_gl_context; // Viewport branch will need this. + return ImGui_ImplSDL2_Init(window); +} + +bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window) +{ +#if !SDL_HAS_VULKAN + IM_ASSERT(0 && "Unsupported"); +#endif + return ImGui_ImplSDL2_Init(window); +} + +bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window) +{ +#if !defined(_WIN32) + IM_ASSERT(0 && "Unsupported"); +#endif + return ImGui_ImplSDL2_Init(window); +} + +bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window) +{ + return ImGui_ImplSDL2_Init(window); +} + +void ImGui_ImplSDL2_Shutdown() +{ + g_Window = NULL; + + // Destroy last known clipboard data + if (g_ClipboardTextData) + SDL_free(g_ClipboardTextData); + g_ClipboardTextData = NULL; + + // Destroy SDL mouse cursors + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) + SDL_FreeCursor(g_MouseCursors[cursor_n]); + memset(g_MouseCursors, 0, sizeof(g_MouseCursors)); +} + +static void ImGui_ImplSDL2_UpdateMousePosAndButtons() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Set OS mouse position if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + if (io.WantSetMousePos) + SDL_WarpMouseInWindow(g_Window, (int)io.MousePos.x, (int)io.MousePos.y); + else + io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + + int mx, my; + Uint32 mouse_buttons = SDL_GetMouseState(&mx, &my); + io.MouseDown[0] = g_MousePressed[0] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. + io.MouseDown[1] = g_MousePressed[1] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; + io.MouseDown[2] = g_MousePressed[2] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0; + g_MousePressed[0] = g_MousePressed[1] = g_MousePressed[2] = false; + +#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) + SDL_Window* focused_window = SDL_GetKeyboardFocus(); + if (g_Window == focused_window) + { + if (g_MouseCanUseGlobalState) + { + // SDL_GetMouseState() gives mouse position seemingly based on the last window entered/focused(?) + // The creation of a new windows at runtime and SDL_CaptureMouse both seems to severely mess up with that, so we retrieve that position globally. + // Won't use this workaround when on Wayland, as there is no global mouse position. + int wx, wy; + SDL_GetWindowPosition(focused_window, &wx, &wy); + SDL_GetGlobalMouseState(&mx, &my); + mx -= wx; + my -= wy; + } + io.MousePos = ImVec2((float)mx, (float)my); + } + + // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger the OS window resize cursor. + // The function is only supported from SDL 2.0.4 (released Jan 2016) + bool any_mouse_button_down = ImGui::IsAnyMouseDown(); + SDL_CaptureMouse(any_mouse_button_down ? SDL_TRUE : SDL_FALSE); +#else + if (SDL_GetWindowFlags(g_Window) & SDL_WINDOW_INPUT_FOCUS) + io.MousePos = ImVec2((float)mx, (float)my); +#endif +} + +static void ImGui_ImplSDL2_UpdateMouseCursor() +{ + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) + return; + + ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) + { + // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor + SDL_ShowCursor(SDL_FALSE); + } + else + { + // Show OS mouse cursor + SDL_SetCursor(g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); + SDL_ShowCursor(SDL_TRUE); + } +} + +static void ImGui_ImplSDL2_UpdateGamepads() +{ + ImGuiIO& io = ImGui::GetIO(); + memset(io.NavInputs, 0, sizeof(io.NavInputs)); + if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) + return; + + // Get gamepad + SDL_GameController* game_controller = SDL_GameControllerOpen(0); + if (!game_controller) + { + io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; + return; + } + + // Update gamepad inputs + #define MAP_BUTTON(NAV_NO, BUTTON_NO) { io.NavInputs[NAV_NO] = (SDL_GameControllerGetButton(game_controller, BUTTON_NO) != 0) ? 1.0f : 0.0f; } + #define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float vn = (float)(SDL_GameControllerGetAxis(game_controller, AXIS_NO) - V0) / (float)(V1 - V0); if (vn > 1.0f) vn = 1.0f; if (vn > 0.0f && io.NavInputs[NAV_NO] < vn) io.NavInputs[NAV_NO] = vn; } + const int thumb_dead_zone = 8000; // SDL_gamecontroller.h suggests using this value. + MAP_BUTTON(ImGuiNavInput_Activate, SDL_CONTROLLER_BUTTON_A); // Cross / A + MAP_BUTTON(ImGuiNavInput_Cancel, SDL_CONTROLLER_BUTTON_B); // Circle / B + MAP_BUTTON(ImGuiNavInput_Menu, SDL_CONTROLLER_BUTTON_X); // Square / X + MAP_BUTTON(ImGuiNavInput_Input, SDL_CONTROLLER_BUTTON_Y); // Triangle / Y + MAP_BUTTON(ImGuiNavInput_DpadLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT); // D-Pad Left + MAP_BUTTON(ImGuiNavInput_DpadRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); // D-Pad Right + MAP_BUTTON(ImGuiNavInput_DpadUp, SDL_CONTROLLER_BUTTON_DPAD_UP); // D-Pad Up + MAP_BUTTON(ImGuiNavInput_DpadDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN); // D-Pad Down + MAP_BUTTON(ImGuiNavInput_FocusPrev, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); // L1 / LB + MAP_BUTTON(ImGuiNavInput_FocusNext, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); // R1 / RB + MAP_BUTTON(ImGuiNavInput_TweakSlow, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); // L1 / LB + MAP_BUTTON(ImGuiNavInput_TweakFast, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); // R1 / RB + MAP_ANALOG(ImGuiNavInput_LStickLeft, SDL_CONTROLLER_AXIS_LEFTX, -thumb_dead_zone, -32768); + MAP_ANALOG(ImGuiNavInput_LStickRight, SDL_CONTROLLER_AXIS_LEFTX, +thumb_dead_zone, +32767); + MAP_ANALOG(ImGuiNavInput_LStickUp, SDL_CONTROLLER_AXIS_LEFTY, -thumb_dead_zone, -32767); + MAP_ANALOG(ImGuiNavInput_LStickDown, SDL_CONTROLLER_AXIS_LEFTY, +thumb_dead_zone, +32767); + + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + #undef MAP_BUTTON + #undef MAP_ANALOG +} + +void ImGui_ImplSDL2_NewFrame(SDL_Window* window) +{ + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer back-end. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + SDL_GetWindowSize(window, &w, &h); + SDL_GL_GetDrawableSize(window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + if (w > 0 && h > 0) + io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); + + // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) + static Uint64 frequency = SDL_GetPerformanceFrequency(); + Uint64 current_time = SDL_GetPerformanceCounter(); + io.DeltaTime = g_Time > 0 ? (float)((double)(current_time - g_Time) / frequency) : (float)(1.0f / 60.0f); + g_Time = current_time; + + ImGui_ImplSDL2_UpdateMousePosAndButtons(); + ImGui_ImplSDL2_UpdateMouseCursor(); + + // Update game controllers (if enabled and available) + ImGui_ImplSDL2_UpdateGamepads(); +} diff --git a/examples/third-party/imgui/imgui-extra/imgui_impl_sdl.h b/examples/third-party/imgui/imgui-extra/imgui_impl_sdl.h new file mode 100644 index 0000000..2deb112 --- /dev/null +++ b/examples/third-party/imgui/imgui-extra/imgui_impl_sdl.h @@ -0,0 +1,31 @@ +// dear imgui: Platform Binding for SDL2 +// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) +// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.) + +// Implemented features: +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Clipboard support. +// [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE). +// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// Missing features: +// [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +#pragma once + +struct SDL_Window; +typedef union SDL_Event SDL_Event; + +IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context); +IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window); +IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window); +IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window); +IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(SDL_Window* window); +IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event); + +IMGUI_IMPL_API void ImGui_ImplSDL2_SaveState(int id); +IMGUI_IMPL_API void ImGui_ImplSDL2_LoadState(int id); diff --git a/examples/third-party/imtui b/examples/third-party/imtui new file mode 160000 index 0000000..bbbc501 --- /dev/null +++ b/examples/third-party/imtui @@ -0,0 +1 @@ +Subproject commit bbbc5011bddc364d938aed97db09e043c9ff15c7