Add inspector with hooks for SDL2. (#75)

* Add inspector with hooks for SDL2.

* Fix build errors.

* Add the remaining SDL2 implementation that was required.
This commit is contained in:
Darío 2024-08-10 19:41:29 -03:00 committed by GitHub
parent 1812bc03c3
commit 88c618c1f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 227 additions and 153 deletions

View File

@ -333,6 +333,7 @@ set (SOURCES
"${PROJECT_SOURCE_DIR}/src/contrib/imgui/imgui_draw.cpp" "${PROJECT_SOURCE_DIR}/src/contrib/imgui/imgui_draw.cpp"
"${PROJECT_SOURCE_DIR}/src/contrib/imgui/imgui_tables.cpp" "${PROJECT_SOURCE_DIR}/src/contrib/imgui/imgui_tables.cpp"
"${PROJECT_SOURCE_DIR}/src/contrib/imgui/imgui_widgets.cpp" "${PROJECT_SOURCE_DIR}/src/contrib/imgui/imgui_widgets.cpp"
"${PROJECT_SOURCE_DIR}/src/contrib/imgui/backends/imgui_impl_sdl2.cpp"
"${PROJECT_SOURCE_DIR}/src/contrib/imgui/backends/imgui_impl_vulkan.cpp" "${PROJECT_SOURCE_DIR}/src/contrib/imgui/backends/imgui_impl_vulkan.cpp"
"${PROJECT_SOURCE_DIR}/src/contrib/im3d/im3d.cpp" "${PROJECT_SOURCE_DIR}/src/contrib/im3d/im3d.cpp"
"${PROJECT_SOURCE_DIR}/src/contrib/implot/implot.cpp" "${PROJECT_SOURCE_DIR}/src/contrib/implot/implot.cpp"

View File

@ -12,6 +12,12 @@
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "implot/implot.h" #include "implot/implot.h"
// Volk must be included before the ImGui Vulkan backend.
#include "vulkan/rt64_vulkan.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_vulkan.h"
#if defined(_WIN32) #if defined(_WIN32)
# include "imgui/backends/imgui_impl_dx12.h" # include "imgui/backends/imgui_impl_dx12.h"
# include "imgui/backends/imgui_impl_win32.h" # include "imgui/backends/imgui_impl_win32.h"
@ -22,10 +28,6 @@
# include "d3d12/rt64_d3d12.h" # include "d3d12/rt64_d3d12.h"
#endif #endif
// Volk must be included before the ImGui Vulkan backend.
#include "vulkan/rt64_vulkan.h"
#include "imgui/backends/imgui_impl_vulkan.h"
static std::string IniFilenameUTF8; static std::string IniFilenameUTF8;
#ifdef _WIN32 #ifdef _WIN32
@ -59,13 +61,14 @@ namespace RT64 {
// Inspector // Inspector
Inspector::Inspector(RenderDevice *device, const RenderSwapChain *swapChain, UserConfiguration::GraphicsAPI graphicsAPI) { Inspector::Inspector(RenderDevice *device, const RenderSwapChain *swapChain, UserConfiguration::GraphicsAPI graphicsAPI, SDL_Window *sdlWindow) {
assert(device != nullptr); assert(device != nullptr);
assert(swapChain != nullptr); assert(swapChain != nullptr);
this->device = device; this->device = device;
this->swapChain = swapChain; this->swapChain = swapChain;
this->graphicsAPI = graphicsAPI; this->graphicsAPI = graphicsAPI;
this->sdlWindow = sdlWindow;
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
@ -73,10 +76,15 @@ namespace RT64 {
ImPlot::CreateContext(); ImPlot::CreateContext();
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
# ifdef _WIN32 if (sdlWindow != nullptr) {
RenderWindow renderWindow = swapChain->getWindow(); ImGui_ImplSDL2_InitForOther(sdlWindow);
ImGui_ImplWin32_Init(renderWindow); }
# endif else {
# ifdef _WIN32
RenderWindow renderWindow = swapChain->getWindow();
ImGui_ImplWin32_Init(renderWindow);
# endif
}
switch (graphicsAPI) { switch (graphicsAPI) {
case UserConfiguration::GraphicsAPI::D3D12: { case UserConfiguration::GraphicsAPI::D3D12: {
@ -153,11 +161,14 @@ namespace RT64 {
break; break;
} }
# ifdef _WIN32 if (sdlWindow != nullptr) {
ImGui_ImplWin32_Shutdown(); ImGui_ImplSDL2_Shutdown();
# else }
assert(false && "Unimplemented."); else {
# endif # ifdef _WIN32
ImGui_ImplWin32_Shutdown();
# endif
}
ImPlot::DestroyContext(); ImPlot::DestroyContext();
ImGui::DestroyContext(); ImGui::DestroyContext();
@ -173,12 +184,15 @@ namespace RT64 {
assert(worker != nullptr); assert(worker != nullptr);
frameMutex.lock(); frameMutex.lock();
# ifdef _WIN32 if (sdlWindow != nullptr) {
ImGui_ImplWin32_NewFrame(); ImGui_ImplSDL2_NewFrame();
# else }
assert(false && "Unimplemented."); else {
# endif # ifdef _WIN32
ImGui_ImplWin32_NewFrame();
# endif
}
switch (graphicsAPI) { switch (graphicsAPI) {
case UserConfiguration::GraphicsAPI::D3D12: { case UserConfiguration::GraphicsAPI::D3D12: {
@ -240,4 +254,20 @@ namespace RT64 {
return ImGui_ImplWin32_WndProcHandler(swapChain->getWindow(), msg, wParam, lParam); return ImGui_ImplWin32_WndProcHandler(swapChain->getWindow(), msg, wParam, lParam);
} }
#endif #endif
bool Inspector::handleSdlEvent(SDL_Event *event) {
assert((sdlWindow != nullptr) && "SDL Events shouldn't be handled if SDL was not used.");
bool processed = ImGui_ImplSDL2_ProcessEvent(event);
if (processed) {
if ((event->type == SDL_KEYDOWN) || (event->type == SDL_KEYUP)) {
return ImGui::GetIO().WantCaptureKeyboard;
}
else if ((event->type == SDL_MOUSEMOTION) || (event->type == SDL_MOUSEBUTTONDOWN) || (event->type == SDL_MOUSEBUTTONUP) || (event->type == SDL_MOUSEWHEEL)) {
return ImGui::GetIO().WantCaptureMouse;
}
}
return false;
}
}; };

View File

@ -14,6 +14,7 @@
#include "common/rt64_common.h" #include "common/rt64_common.h"
#include "common/rt64_user_configuration.h" #include "common/rt64_user_configuration.h"
#include "render/rt64_render_worker.h" #include "render/rt64_render_worker.h"
#include "hle/rt64_application_window.h"
#include "rhi/rt64_render_interface.h" #include "rhi/rt64_render_interface.h"
namespace RT64 { namespace RT64 {
@ -22,12 +23,13 @@ namespace RT64 {
struct Inspector { struct Inspector {
RenderDevice *device = nullptr; RenderDevice *device = nullptr;
const RenderSwapChain *swapChain = nullptr; const RenderSwapChain *swapChain = nullptr;
UserConfiguration::GraphicsAPI graphicsAPI;
SDL_Window *sdlWindow = nullptr;
std::unique_ptr<RenderDescriptorSet> descriptorSet; std::unique_ptr<RenderDescriptorSet> descriptorSet;
std::unique_ptr<VulkanContext> vulkanContext; std::unique_ptr<VulkanContext> vulkanContext;
UserConfiguration::GraphicsAPI graphicsAPI;
std::mutex frameMutex; std::mutex frameMutex;
Inspector(RenderDevice *device, const RenderSwapChain *swapChain, UserConfiguration::GraphicsAPI graphicsAPI); Inspector(RenderDevice *device, const RenderSwapChain *swapChain, UserConfiguration::GraphicsAPI graphicsAPI, SDL_Window *sdlWindow);
~Inspector(); ~Inspector();
void setIniPath(const std::filesystem::path &path); void setIniPath(const std::filesystem::path &path);
void newFrame(RenderWorker *worker); void newFrame(RenderWorker *worker);
@ -36,5 +38,6 @@ namespace RT64 {
# ifdef _WIN32 # ifdef _WIN32
bool handleMessage(UINT msg, WPARAM wParam, LPARAM lParam); bool handleMessage(UINT msg, WPARAM wParam, LPARAM lParam);
# endif # endif
bool handleSdlEvent(SDL_Event *event);
}; };
}; };

View File

@ -390,6 +390,7 @@ namespace RT64 {
} }
void Application::updateScreen() { void Application::updateScreen() {
appWindow->sdlCheckFilterInstallation();
screenApiProfiler.logAndRestart(); screenApiProfiler.logAndRestart();
state->updateScreen(core.decodeVI(), false); state->updateScreen(core.decodeVI(), false);
} }
@ -448,43 +449,20 @@ namespace RT64 {
switch (message) { switch (message) {
case WM_KEYDOWN: { case WM_KEYDOWN: {
switch (wParam) { switch (wParam) {
case VK_F1: { case VK_F1:
if (userConfig.developerMode) { processDeveloperShortcut(DeveloperShortcut::Inspector);
const std::lock_guard<std::mutex> lock(presentQueue->inspectorMutex);
if (presentQueue->inspector == nullptr) {
presentQueue->inspector = std::make_unique<Inspector>(device.get(), swapChain.get(), createdGraphicsAPI);
if (!userPaths.isEmpty()) {
presentQueue->inspector->setIniPath(userPaths.imguiPath);
}
freeCamClearQueued = true;
appWindow->blockSdlKeyboard();
}
else if (presentQueue->inspector != nullptr) {
presentQueue->inspector.reset(nullptr);
appWindow->unblockSdlKeyboard();
}
}
else {
fprintf(stdout, "Inspector is not available: developer mode is not enabled in the configuration.\n");
}
return true; return true;
} case VK_F2:
case VK_F2: { processDeveloperShortcut(DeveloperShortcut::RayTracing);
workloadQueue->rtEnabled = !workloadQueue->rtEnabled;
return true; return true;
} case VK_F3:
case VK_F3: { processDeveloperShortcut(DeveloperShortcut::ViewRDRAM);
presentQueue->viewRDRAM = !presentQueue->viewRDRAM;
return true; return true;
} case VK_F4:
case VK_F4: { processDeveloperShortcut(DeveloperShortcut::Replacements);
textureCache->textureMap.replacementMapEnabled = !textureCache->textureMap.replacementMapEnabled;
return true; return true;
}
default: default:
// Ignore key. // Unknown shortcut.
break; break;
} }
} }
@ -492,11 +470,82 @@ namespace RT64 {
return false; return false;
} }
#endif
bool Application::sdlEventFilter(SDL_Event *event) {
if (userConfig.developerMode && (presentQueue != nullptr) && (state != nullptr) && !FileDialog::isOpen) {
const std::lock_guard<std::mutex> lock(presentQueue->inspectorMutex);
if ((presentQueue->inspector != nullptr) && presentQueue->inspector->handleSdlEvent(event)) {
return true;
}
}
if (event->type == SDL_KEYDOWN) {
switch (event->key.keysym.scancode) {
case SDL_SCANCODE_F1:
processDeveloperShortcut(DeveloperShortcut::Inspector);
return true;
case SDL_SCANCODE_F2:
processDeveloperShortcut(DeveloperShortcut::RayTracing);
return true;
case SDL_SCANCODE_F3:
processDeveloperShortcut(DeveloperShortcut::ViewRDRAM);
return true;
case SDL_SCANCODE_F4:
processDeveloperShortcut(DeveloperShortcut::Replacements);
return true;
default:
// Don't filter the key event.
break;
}
}
return false;
}
bool Application::usesWindowMessageFilter() { bool Application::usesWindowMessageFilter() {
return userConfig.developerMode; return userConfig.developerMode;
} }
#endif
void Application::processDeveloperShortcut(DeveloperShortcut developerShortcut) {
switch (developerShortcut) {
case DeveloperShortcut::Inspector: {
if (userConfig.developerMode) {
const std::lock_guard<std::mutex> lock(presentQueue->inspectorMutex);
if (presentQueue->inspector == nullptr) {
presentQueue->inspector = std::make_unique<Inspector>(device.get(), swapChain.get(), createdGraphicsAPI, appWindow->sdlWindow);
if (!userPaths.isEmpty()) {
presentQueue->inspector->setIniPath(userPaths.imguiPath);
}
freeCamClearQueued = true;
}
else if (presentQueue->inspector != nullptr) {
presentQueue->inspector.reset(nullptr);
}
}
else {
fprintf(stdout, "Inspector is not available: developer mode is not enabled in the configuration.\n");
}
break;
}
case DeveloperShortcut::RayTracing: {
workloadQueue->rtEnabled = !workloadQueue->rtEnabled;
break;
}
case DeveloperShortcut::ViewRDRAM: {
presentQueue->viewRDRAM = !presentQueue->viewRDRAM;
break;
}
case DeveloperShortcut::Replacements: {
textureCache->textureMap.replacementMapEnabled = !textureCache->textureMap.replacementMapEnabled;
break;
}
default:
break;
};
}
void Application::end() { void Application::end() {
# if SCRIPT_ENABLED # if SCRIPT_ENABLED

View File

@ -51,6 +51,13 @@ namespace RT64 {
GraphicsDeviceNotFound GraphicsDeviceNotFound
}; };
enum class DeveloperShortcut {
Inspector,
RayTracing,
ViewRDRAM,
Replacements
};
struct Core { struct Core {
RenderWindow window; RenderWindow window;
uint8_t *HEADER; uint8_t *HEADER;
@ -159,8 +166,10 @@ namespace RT64 {
bool checkDirectoryCreated(const std::filesystem::path &path); bool checkDirectoryCreated(const std::filesystem::path &path);
# ifdef _WIN32 # ifdef _WIN32
bool windowMessageFilter(unsigned int message, WPARAM wParam, LPARAM lParam) override; bool windowMessageFilter(unsigned int message, WPARAM wParam, LPARAM lParam) override;
bool usesWindowMessageFilter() override;
# endif # endif
bool sdlEventFilter(SDL_Event *event) override;
bool usesWindowMessageFilter() override;
void processDeveloperShortcut(DeveloperShortcut developerShortcut);
void updateUserConfig(bool discardFBs); void updateUserConfig(bool discardFBs);
void updateEmulatorConfig(); void updateEmulatorConfig();
void updateEnhancementConfig(); void updateEnhancementConfig();

View File

@ -23,16 +23,7 @@ namespace RT64 {
ApplicationWindow *ApplicationWindow::HookedApplicationWindow = nullptr; ApplicationWindow *ApplicationWindow::HookedApplicationWindow = nullptr;
ApplicationWindow::ApplicationWindow() { ApplicationWindow::ApplicationWindow() {
windowHandle = {}; // Empty.
sdlEventFilterUserdata = nullptr;
sdlEventFilterStored = false;
fullScreen = false;
lastMaximizedState = false;
# ifdef _WIN32
windowHook = nullptr;
windowMenu = nullptr;
# endif
usingSdl = false;
} }
ApplicationWindow::~ApplicationWindow() { ApplicationWindow::~ApplicationWindow() {
@ -51,16 +42,27 @@ namespace RT64 {
assert(listener != nullptr); assert(listener != nullptr);
this->listener = listener; this->listener = listener;
windowHandle = window;
# ifdef _WIN32 windowHandle = window;
if (listener->usesWindowMessageFilter()) { if (listener->usesWindowMessageFilter()) {
assert(HookedApplicationWindow == nullptr); if ((sdlWindow == nullptr) && SDL_WasInit(SDL_INIT_VIDEO)) {
assert(threadId != 0); // We'd normally install the event filter here, but Mupen does not set its own event filter
windowHook = SetWindowsHookEx(WH_GETMESSAGE, &windowHookCallback, NULL, threadId); // until much later. Instead, we delegate this to the first time a screen update is sent.
HookedApplicationWindow = this; // FIXME: We attempt to get the first window created by SDL2. This can be improved later
// by actually passing the SDL_Window handle through as a parameter.
sdlWindow = SDL_GetWindowFromID(1);
}
if (sdlWindow == nullptr) {
# ifdef _WIN32
assert(HookedApplicationWindow == nullptr);
assert(threadId != 0);
windowHook = SetWindowsHookEx(WH_GETMESSAGE, &windowHookCallback, NULL, threadId);
HookedApplicationWindow = this;
# endif
}
} }
# endif
} }
void ApplicationWindow::setup(const char *windowTitle, Listener *listener) { void ApplicationWindow::setup(const char *windowTitle, Listener *listener) {
@ -71,7 +73,7 @@ namespace RT64 {
const int Height = 720; const int Height = 720;
struct { struct {
uint32_t left, top, width, height; uint32_t left, top, width, height;
} bounds {}; } bounds{};
# if defined(_WIN32) # if defined(_WIN32)
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
@ -108,12 +110,13 @@ namespace RT64 {
# else # else
static_assert(false && "Unimplemented"); static_assert(false && "Unimplemented");
# endif # endif
// Create window. // Create window.
#ifdef RT64_SDL_WINDOW sdlWindow = SDL_CreateWindow(windowTitle, bounds.left, bounds.top, bounds.width, bounds.height, SDL_WINDOW_RESIZABLE);
SDL_Window *sdlWindow = SDL_CreateWindow(windowTitle, bounds.left, bounds.top, bounds.width, bounds.height, SDL_WINDOW_RESIZABLE); assert((sdlWindow != nullptr) && "Failed to open window with SDL");
// Get native window handles from the window.
SDL_SysWMinfo wmInfo; SDL_SysWMinfo wmInfo;
assert(sdlWindow && "Failed to open window with SDL");
SDL_VERSION(&wmInfo.version); SDL_VERSION(&wmInfo.version);
SDL_GetWindowWMInfo(sdlWindow, &wmInfo); SDL_GetWindowWMInfo(sdlWindow, &wmInfo);
# if defined(_WIN32) # if defined(_WIN32)
@ -128,10 +131,6 @@ namespace RT64 {
# else # else
static_assert(false && "Unimplemented"); static_assert(false && "Unimplemented");
# endif # endif
usingSdl = true;
#else
static_assert(false && "Unimplemented");
#endif
# ifdef _WIN32 # ifdef _WIN32
setup(windowHandle, listener, GetCurrentThreadId()); setup(windowHandle, listener, GetCurrentThreadId());
@ -305,7 +304,7 @@ namespace RT64 {
} }
} }
# ifdef _WIN32 #ifdef _WIN32
void ApplicationWindow::windowMessage(UINT message, WPARAM wParam, LPARAM lParam) { void ApplicationWindow::windowMessage(UINT message, WPARAM wParam, LPARAM lParam) {
if (listener->windowMessageFilter(message, wParam, lParam)) { if (listener->windowMessageFilter(message, wParam, lParam)) {
return; return;
@ -326,47 +325,34 @@ namespace RT64 {
return CallNextHookEx(NULL, nCode, wParam, lParam); return CallNextHookEx(NULL, nCode, wParam, lParam);
} }
# endif
#ifdef RT64_SDL_WINDOW
void ApplicationWindow::blockSdlKeyboard() {
if (!usingSdl) {
return;
}
sdlEventFilterStored = SDL_GetEventFilter(&sdlEventFilter, &sdlEventFilterUserdata);
if (sdlEventFilterStored) {
SDL_SetEventFilter(&ApplicationWindow::sdlEventKeyboardFilter, this);
}
}
void ApplicationWindow::unblockSdlKeyboard() {
if (!usingSdl) {
return;
}
if (sdlEventFilterStored) {
SDL_SetEventFilter(sdlEventFilter, sdlEventFilterUserdata);
sdlEventFilterStored = false;
}
}
int ApplicationWindow::sdlEventKeyboardFilter(void *userdata, SDL_Event *event) {
ApplicationWindow *appWindow = reinterpret_cast<ApplicationWindow *>(userdata);
switch (event->type) {
// Ignore all keyboard events.
case SDL_KEYDOWN:
case SDL_KEYUP:
return 0;
// Pass through to the original event filter.
default:
return appWindow->sdlEventFilter(userdata, event);
}
}
#else
static_assert(false && "Unimplemented");
#endif #endif
void ApplicationWindow::sdlCheckFilterInstallation() {
if (!sdlEventFilterInstalled && (sdlWindow != nullptr)) {
if (!SDL_GetEventFilter(&sdlEventFilterStored, &sdlEventFilterUserdata)) {
sdlEventFilterStored = nullptr;
sdlEventFilterUserdata = nullptr;
}
SDL_SetEventFilter(&ApplicationWindow::sdlEventFilter, this);
sdlEventFilterInstalled = true;
}
}
int ApplicationWindow::sdlEventFilter(void *userdata, SDL_Event *event) {
// Run it through the listener's event filter. If it's processed by the listener, the event should be filtered.
ApplicationWindow *appWindow = reinterpret_cast<ApplicationWindow *>(userdata);
if (appWindow->listener->sdlEventFilter(event)) {
return 0;
}
// Pass to the event filter that was stored if it exists. Let the original filter determine the result.
if (appWindow->sdlEventFilterStored != nullptr) {
return appWindow->sdlEventFilterStored(appWindow->sdlEventFilterUserdata, event);
}
// The event should not be filtered.
else {
return 1;
}
}
}; };

View File

@ -9,41 +9,45 @@
#include <Windows.h> #include <Windows.h>
#endif #endif
#define RT64_SDL_WINDOW #include "SDL.h"
#include "SDL_events.h"
#ifdef RT64_SDL_WINDOW #include "SDL_system.h"
# include "SDL_events.h" #include "SDL_syswm.h"
# include "SDL_system.h" #include "SDL_video.h"
# include "SDL_syswm.h"
# include "SDL_video.h"
#endif
namespace RT64 { namespace RT64 {
struct ApplicationWindow { struct ApplicationWindow {
static ApplicationWindow *HookedApplicationWindow; static ApplicationWindow *HookedApplicationWindow;
struct Listener { struct Listener {
virtual bool usesWindowMessageFilter() = 0;
// Return true if the listener should accept and handle the message. // Return true if the listener should accept and handle the message.
// Return false otherwise for the default message handler to take over. // Return false otherwise for the default message handler to take over.
virtual bool sdlEventFilter(SDL_Event *event) = 0;
# ifdef _WIN32 # ifdef _WIN32
virtual bool windowMessageFilter(unsigned int message, WPARAM wParam, LPARAM lParam) = 0; virtual bool windowMessageFilter(unsigned int message, WPARAM wParam, LPARAM lParam) = 0;
virtual bool usesWindowMessageFilter() = 0;
# endif # endif
}; };
RenderWindow windowHandle; RenderWindow windowHandle = {};
# ifdef _WIN32
HHOOK windowHook;
HMENU windowMenu;
RECT lastWindowRect;
# endif
Listener *listener; Listener *listener;
uint32_t refreshRate = 0; uint32_t refreshRate = 0;
bool fullScreen; bool fullScreen = false;
bool lastMaximizedState; bool lastMaximizedState = false;
bool usingSdl;
int32_t windowLeft = INT32_MAX; int32_t windowLeft = INT32_MAX;
int32_t windowTop = INT32_MAX; int32_t windowTop = INT32_MAX;
SDL_Window *sdlWindow = nullptr;
SDL_EventFilter sdlEventFilterStored = nullptr;
void *sdlEventFilterUserdata = nullptr;
bool sdlEventFilterInstalled = false;
# ifdef _WIN32
HHOOK windowHook = nullptr;
HMENU windowMenu = nullptr;
RECT lastWindowRect = {};
# endif
ApplicationWindow(); ApplicationWindow();
~ApplicationWindow(); ~ApplicationWindow();
@ -54,20 +58,12 @@ namespace RT64 {
void detectRefreshRate(); void detectRefreshRate();
uint32_t getRefreshRate() const; uint32_t getRefreshRate() const;
bool detectWindowMoved(); bool detectWindowMoved();
void sdlCheckFilterInstallation();
static int sdlEventFilter(void *userdata, SDL_Event *event);
# ifdef _WIN32 # ifdef _WIN32
void windowMessage(UINT message, WPARAM wParam, LPARAM lParam); void windowMessage(UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT windowHookCallback(int nCode, WPARAM wParam, LPARAM lParam); static LRESULT windowHookCallback(int nCode, WPARAM wParam, LPARAM lParam);
# endif # endif
# ifdef RT64_SDL_WINDOW
SDL_EventFilter sdlEventFilter;
void *sdlEventFilterUserdata;
bool sdlEventFilterStored;
void blockSdlKeyboard();
void unblockSdlKeyboard();
static int sdlEventKeyboardFilter(void *userdata, SDL_Event *event);
# endif
}; };
}; };