From 6485041b925e5e6652df5690987b3d157d584a7d Mon Sep 17 00:00:00 2001 From: offtkp Date: Fri, 22 Mar 2024 10:54:16 +0200 Subject: [PATCH] Stuff? --- CMakeLists.txt | 3 + include/compatibility.hxx | 14 --- include/hydra/common/settings.hxx | 1 + include/hydra/common/system.hxx | 9 ++ include/hydra/common/utility.hxx | 23 +++++ include/hydra/core/thread.hxx | 13 +++ include/hydra/imgui/imgui.hxx | 7 ++ src/hydra/common/system.cxx | 46 ++++++++++ src/hydra/core/imports.cxx | 115 ++++++++++++++++++++--- src/hydra/core/thread.cxx | 146 ++++++++++++++++++++++++++++++ src/hydra/core/wrapper.cxx | 3 +- src/hydra/imgui/imgui.cxx | 21 +++++ src/hydra/qt/main_window.cxx | 1 + src/hydra/sdl3/opengl.cxx | 62 +++++++++++-- src/hydra/sdl3/vulkan.cxx | 19 +--- 15 files changed, 436 insertions(+), 47 deletions(-) create mode 100644 include/hydra/common/system.hxx create mode 100644 include/hydra/common/utility.hxx create mode 100644 include/hydra/core/thread.hxx create mode 100644 include/hydra/imgui/imgui.hxx create mode 100644 src/hydra/common/system.cxx create mode 100644 src/hydra/core/thread.cxx create mode 100644 src/hydra/imgui/imgui.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 363aefc2..46d692bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,6 +179,7 @@ set(HYDRA_IMGUI_FILES src/hydra/sdl3/opengl.cxx src/hydra/sdl3/vulkan.cxx src/hydra/sdl3/common.cxx + src/hydra/imgui/imgui.cxx vendored/imgui/backends/imgui_impl_sdl3.cpp vendored/imgui/backends/imgui_impl_vulkan.cpp vendored/imgui/imgui.cpp @@ -192,8 +193,10 @@ set(HYDRA_COMMON_FILES src/main.cxx src/hydra/core/wrapper.cxx src/hydra/core/imports.cxx + src/hydra/core/thread.cxx src/hydra/common/version.cxx src/hydra/common/settings.cxx + src/hydra/common/system.cxx vendored/argparse/argparse.c vendored/miniaudio.c vendored/stb_image.c diff --git a/include/compatibility.hxx b/include/compatibility.hxx index 95ff3c7d..1859fc28 100644 --- a/include/compatibility.hxx +++ b/include/compatibility.hxx @@ -58,20 +58,6 @@ namespace hydra return bytes; } - // Function for hashing a string in compile time in order to be used in a switch statement - // https://stackoverflow.com/a/46711735 - // If there's a collision between two strings, we will know - // at compile time since the cases can't use the same number twice - constexpr uint32_t str_hash(std::string_view data) noexcept - { - uint32_t hash = 5381; - const size_t size = data.size(); - for (size_t i = 0; i < size; i++) - hash = ((hash << 5) + hash) + (unsigned char)data[i]; - - return hash; - } - inline std::string fread(const std::filesystem::path& path) { FILE* file = std::fopen(path.string().c_str(), "rb"); diff --git a/include/hydra/common/settings.hxx b/include/hydra/common/settings.hxx index 15f2969b..5d29cc40 100644 --- a/include/hydra/common/settings.hxx +++ b/include/hydra/common/settings.hxx @@ -106,6 +106,7 @@ namespace hydra::settings Saveable windowHeight{}; Saveable, "General", "LastContentPaths"> lastContentPaths{}; Saveable, "General", "GameDirectories"> gameDirectories{}; + Saveable startPaused{}; static Config& get() { diff --git a/include/hydra/common/system.hxx b/include/hydra/common/system.hxx new file mode 100644 index 00000000..bdcc0336 --- /dev/null +++ b/include/hydra/common/system.hxx @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace hydra::common +{ + HcArchitecture architecture(); + HcOperatingSystem operatingSystem(); +} // namespace hydra::common \ No newline at end of file diff --git a/include/hydra/common/utility.hxx b/include/hydra/common/utility.hxx new file mode 100644 index 00000000..86313cec --- /dev/null +++ b/include/hydra/common/utility.hxx @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace hydra::common +{ + + // Function for hashing a string in compile time in order to be used in a switch statement + // https://stackoverflow.com/a/46711735 + // If there's a collision between two strings, we will know + // at compile time since the cases can't use the same number twice + constexpr uint32_t str_hash(std::string_view data) noexcept + { + uint32_t hash = 5381; + const size_t size = data.size(); + for (size_t i = 0; i < size; i++) + hash = ((hash << 5) + hash) + (unsigned char)data[i]; + + return hash; + } + +} // namespace hydra::common \ No newline at end of file diff --git a/include/hydra/core/thread.hxx b/include/hydra/core/thread.hxx new file mode 100644 index 00000000..d85731be --- /dev/null +++ b/include/hydra/core/thread.hxx @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include + +namespace hydra::core::thread +{ + void start(std::shared_ptr wrapper); + void stop(); + void reset(HcResetType type); + bool running(); +} // namespace hydra::core::thread \ No newline at end of file diff --git a/include/hydra/imgui/imgui.hxx b/include/hydra/imgui/imgui.hxx new file mode 100644 index 00000000..c877fe37 --- /dev/null +++ b/include/hydra/imgui/imgui.hxx @@ -0,0 +1,7 @@ +#pragma once + +namespace hydra::imgui +{ + void init(); + void shutdown(); +} // namespace hydra::imgui \ No newline at end of file diff --git a/src/hydra/common/system.cxx b/src/hydra/common/system.cxx new file mode 100644 index 00000000..96f0f248 --- /dev/null +++ b/src/hydra/common/system.cxx @@ -0,0 +1,46 @@ +#include +#include + +namespace hydra::common +{ + + HcArchitecture architecture() + { +#if defined(__x86_64__) || defined(__x86_64) || defined(__amd64) || defined(_M_X64) + return HC_ARCHITECTURE_X86_64; +#elif defined(__i386__) || defined(__i386) || defined(_M_IX86) + return HC_ARCHITECTURE_X86; +#elif defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) + return HC_ARCHITECTURE_AARCH64; +#elif defined(__arm__) + return HC_ARCHITECTURE_AARCH32; +#elif defined(__EMSCRIPTEN__) + return HC_ARCHITECTURE_WASM; +#else +#pragma message("Unknown architecture") + return HC_ARCHITECTURE_UNKNOWN; +#endif + } + + HcOperatingSystem operatingSystem() + { +#if defined(HYDRA_WINDOWS) + return HC_OPERATING_SYSTEM_WINDOWS; +#elif defined(HYDRA_LINUX) + return HC_OPERATING_SYSTEM_LINUX; +#elif defined(HYDRA_MACOS) + return HC_OPERATING_SYSTEM_MACOS; +#elif defined(HYDRA_FREEBSD) + return HC_OPERATING_SYSTEM_FREEBSD; +#elif defined(HYDRA_WEB) + return HC_OPERATING_SYSTEM_WEB; +#elif defined(HYDRA_IOS) + return HC_OPERATING_SYSTEM_IOS; +#elif defined(HYDRA_ANDROID) + return HC_OPERATING_SYSTEM_ANDROID; +#else + return HC_OPERATING_SYSTEM_UNKNOWN; +#endif + } + +} // namespace hydra::common \ No newline at end of file diff --git a/src/hydra/core/imports.cxx b/src/hydra/core/imports.cxx index c13407c5..b2df3f24 100644 --- a/src/hydra/core/imports.cxx +++ b/src/hydra/core/imports.cxx @@ -1,14 +1,104 @@ // Define the hydra core API imports -#include "compatibility.hxx" +#include "SDL_video.h" #include +#include +#include +#include +#include #include +#include + +#include +#include + +#include + +static HcHostInfo* globalHostInfo = nullptr; extern "C" { void hydra_hcGetHostInfo(HcHostInfo* hostInfo) { - printf("STUB: hcGetHostInfo\n"); + if (!globalHostInfo) + { + globalHostInfo = new HcHostInfo(); + globalHostInfo->architecture = hydra::common::architecture(); + globalHostInfo->operatingSystem = hydra::common::operatingSystem(); + + // Create a hidden OpenGL window to get the OpenGL version + std::thread context([]() { + SDL_Window* window = + SDL_CreateWindow("hidden", 0, 0, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); + SDL_GLContext context = SDL_GL_CreateContext(window); + if (gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) == 0) + { + hydra::log("Failed to load OpenGL functions when trying to get OpenGL version"); + globalHostInfo->openGlVersion = HC_OPENGL_NOT_SUPPORTED; + } + else + { + int major, minor; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + globalHostInfo->openGlVersion = (HcOpenGlVersion)((major << 16) | minor); + printf("OpenGL version: %d.%d\n", major, minor); + } + + SDL_GL_DeleteContext(context); + SDL_DestroyWindow(window); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + window = SDL_CreateWindow("hidden", 0, 0, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); + context = SDL_GL_CreateContext(window); + if (gladLoadGLES2Loader((GLADloadproc)SDL_GL_GetProcAddress) == 0) + { + hydra::log( + "Failed to load OpenGL ES functions when trying to get OpenGL ES version"); + globalHostInfo->openGlEsVersion = HC_OPENGL_ES_NOT_SUPPORTED; + } + else + { + int major, minor; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + globalHostInfo->openGlEsVersion = (HcOpenGlEsVersion)((major << 16) | minor); + printf("OpenGL ES version: %d.%d\n", major, minor); + } + + SDL_GL_DeleteContext(context); + SDL_DestroyWindow(window); + }); + context.join(); + + uint32_t vkVersion; + vkEnumerateInstanceVersion(&vkVersion); + int major = VK_VERSION_MAJOR(vkVersion); + int minor = VK_VERSION_MINOR(vkVersion); + globalHostInfo->vulkanVersion = (HcVulkanVersion)((major << 16) | minor); + printf("Vulkan version: %d.%d\n", major, minor); + +#ifdef HYDRA_WINDOWS + printf("Direct3d stuff\n"); + globalHostInfo->direct3DVersion = HC_DIRECT3D_NOT_SUPPORTED; + // DWORD dwVersion; + // DWORD dwRevision; + // if (DirectXSetupGetVersion(&dwVersion, &dwRevision)) + // { + // printf("DirectX version is %d.%d.%d.%d\n", + // HIWORD(dwVersion), LOWORD(dwVersion), + // HIWORD(dwRevision), LOWORD(dwRevision)); + // } +#else + // TODO: can we use dxvk? Is it too ambitious? + globalHostInfo->direct3DVersion = HC_DIRECT3D_NOT_SUPPORTED; +#endif + globalHostInfo->metalVersion = HC_METAL_NOT_SUPPORTED; + } + + std::memcpy(hostInfo, globalHostInfo, sizeof(HcHostInfo)); } HcResult hydra_hcGetInputsSync(const HcInputRequest* const* requests, int requestCount, @@ -62,27 +152,28 @@ HcResult hydra_hcSetCallbacks(const HcCallbacks* callbacks) void* hydra_GetAddress(const char* name) { - uint32_t hash = hydra::str_hash(name); + using namespace hydra::common; + uint32_t hash = str_hash(name); switch (hash) { - case hydra::str_hash("hcGetHostInfo"): + case str_hash("hcGetHostInfo"): return (void*)hydra_hcGetHostInfo; - case hydra::str_hash("hcGetInputsSync"): + case str_hash("hcGetInputsSync"): return (void*)hydra_hcGetInputsSync; - case hydra::str_hash("hcReconfigureEnvironment"): + case str_hash("hcReconfigureEnvironment"): return (void*)hydra_hcReconfigureEnvironment; - case hydra::str_hash("hcPushSamples"): + case str_hash("hcPushSamples"): return (void*)hydra_hcPushSamples; - case hydra::str_hash("hcSwPushVideoFrame"): + case str_hash("hcSwPushVideoFrame"): return (void*)hydra_hcSwPushVideoFrame; - case hydra::str_hash("hcGlMakeCurrent"): + case str_hash("hcGlMakeCurrent"): return (void*)hydra_hcGlMakeCurrent; - case hydra::str_hash("hcGlSwapBuffers"): + case str_hash("hcGlSwapBuffers"): return (void*)hydra_hcGlSwapBuffers; - case hydra::str_hash("hcGlGetProcAddress"): + case str_hash("hcGlGetProcAddress"): return (void*)hydra_hcGlGetProcAddress; - case hydra::str_hash("hcSetCallbacks"): + case str_hash("hcSetCallbacks"): return (void*)hydra_hcSetCallbacks; default: return nullptr; diff --git a/src/hydra/core/thread.cxx b/src/hydra/core/thread.cxx new file mode 100644 index 00000000..2d7d1d09 --- /dev/null +++ b/src/hydra/core/thread.cxx @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include + +std::shared_ptr globalWrapper; +std::unique_ptr emulatorThread; +std::atomic_bool scheduledReset = false; +HcDriveMode driveMode = HC_DRIVE_MODE_NULL; + +namespace +{ + inline static void handleError(HcResult result, std::shared_ptr wrapper, + const char* hint) + { + if (result != HC_SUCCESS) + { + switch (result) + { + case HC_ERROR_CORE: + { + const char* error = wrapper->hcGetError(); + if (error) + { + hydra::panic("TODO: don't panic, Core error: {}\n{}", error, hint); + } + else + { + hydra::panic( + "TODO: don't panic, Core error, but hcGetError returned nullptr.\n{}", + hint); + } + break; + } + default: + hydra::panic("TODO: don't panic, Unhandled error code: {}\n", (int)result, + hint); + break; + } + } + } + + static void entrypointSelfDriven(std::shared_ptr wrapper) + { + printf("TODO: implement entrypointSelfDriven\n"); + } + + static void entrypointFrontendDriven(std::shared_ptr wrapper) + { + printf("TODO: implement entrypointFrontendDriven\n"); + + printf("Handle scheduled reset\n"); + } +} // namespace + +namespace hydra::core::thread +{ + void start(std::shared_ptr wrapper) + { + globalWrapper = wrapper; + emulatorThread = std::make_unique([wrapper] { + HcVideoInfo videoInfo{}; + HcAudioInfo audioInfo{}; + HcEnvironmentInfo environmentInfo{}; + environmentInfo.video = &videoInfo; + environmentInfo.audio = &audioInfo; + HcResult result = wrapper->hcCreate(&environmentInfo); + handleError(result, wrapper, "Failed to create core"); + + HcRunStateInfo runStateInfo{}; + runStateInfo.runState = + settings::Config::get().startPaused ? HC_RUN_STATE_PAUSED : HC_RUN_STATE_RUNNING; + result = wrapper->hcSetRunState(&runStateInfo); + handleError(result, wrapper, "Failed to set run state"); + + HcDriveMode mode = environmentInfo.driveMode; + driveMode = mode; + switch (mode) + { + case HC_DRIVE_MODE_SELF_DRIVEN_EXCEPT_AUDIO: + printf("TODO: initialize audio:\n"); + [[fallthrough]]; + case HC_DRIVE_MODE_SELF_DRIVEN: + entrypointSelfDriven(wrapper); + break; + case HC_DRIVE_MODE_FRONTEND_DRIVEN: + entrypointFrontendDriven(wrapper); + break; + default: + hydra::panic("TODO: don't panic, Unhandled drive mode: {}\n", (int)mode); + break; + } + }); + } + + void stop() + { + HcRunStateInfo runStateInfo{}; + runStateInfo.runState = HC_RUN_STATE_QUIT; + HcResult result = globalWrapper->hcSetRunState(&runStateInfo); + handleError(result, globalWrapper, "Failed to set run state to quit"); + + HcDestroyInfo destroyInfo{}; + result = globalWrapper->hcDestroy(&destroyInfo); + handleError(result, globalWrapper, "Failed to destroy core"); + + emulatorThread->join(); + + if (globalWrapper.use_count() != 1) + { + hydra::log("Wrapper use count is not 1 while exiting core"); + } + + globalWrapper.reset(); + emulatorThread.reset(); + driveMode = HC_DRIVE_MODE_NULL; + } + + void reset(HcResetType type) + { + if (!globalWrapper) + { + hydra::panic("Wrapper is not initialized while trying to reset core"); + } + + if (driveMode == HC_DRIVE_MODE_FRONTEND_DRIVEN) + { + scheduledReset = true; + } + else + { + HcResetInfo resetInfo{}; + resetInfo.resetType = type; + HcResult result = globalWrapper->hcReset(&resetInfo); + handleError(result, globalWrapper, "Failed to reset core"); + } + } + + bool running() + { + return emulatorThread && emulatorThread->joinable(); + } +} // namespace hydra::core::thread \ No newline at end of file diff --git a/src/hydra/core/wrapper.cxx b/src/hydra/core/wrapper.cxx index 555a5926..1a5dadbe 100644 --- a/src/hydra/core/wrapper.cxx +++ b/src/hydra/core/wrapper.cxx @@ -72,7 +72,8 @@ namespace hydra::core bool Wrapper::okay() const { - return handle != nullptr; + return handle && hcGetCoreInfo && hcCreate && hcDestroy && hcReset && hcSetRunState && + hcLoadContent && hcGetError; } } // namespace hydra::core \ No newline at end of file diff --git a/src/hydra/imgui/imgui.cxx b/src/hydra/imgui/imgui.cxx new file mode 100644 index 00000000..068e4c5e --- /dev/null +++ b/src/hydra/imgui/imgui.cxx @@ -0,0 +1,21 @@ +#include +#include + +namespace hydra::imgui +{ + void init() + { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + ImGui::StyleColorsDark(); + } + + void shutdown() + { + ImGui::DestroyContext(); + } +} // namespace hydra::imgui \ No newline at end of file diff --git a/src/hydra/qt/main_window.cxx b/src/hydra/qt/main_window.cxx index 0fe9ceec..6d879637 100644 --- a/src/hydra/qt/main_window.cxx +++ b/src/hydra/qt/main_window.cxx @@ -50,6 +50,7 @@ namespace hydra::qt environmentInfo.audio = &audioInfo; wrapper.hcCreate(&environmentInfo); SDL3::Context* ctx = hydra::SDL3::init(&environmentInfo); + printf("TODO: delete ctx\n"); SDL3::EventResult result = SDL3::EventResult::Continue; while (result != SDL3::EventResult::Quit) { diff --git a/src/hydra/sdl3/opengl.cxx b/src/hydra/sdl3/opengl.cxx index 12364c10..9ed3eedf 100644 --- a/src/hydra/sdl3/opengl.cxx +++ b/src/hydra/sdl3/opengl.cxx @@ -1,19 +1,28 @@ #include "backends/imgui_impl_sdl3.h" -#include "hydra/core.h" -#include "SDL_video.h" +#include "hydra/imgui/imgui.hxx" +#include #include -#include #include #include +#include #include +struct InnerContext +{ + SDL_GLContext glContext; +}; + namespace hydra::SDL3::Gl { Context* init(const HcEnvironmentInfo* environmentInfo) { + Context* ctx = new Context(); + InnerContext* inner = new InnerContext(); + ctx->inner = inner; + if (!environmentInfo || !environmentInfo->video) { hydra::panic("Invalid environment info"); @@ -93,8 +102,6 @@ namespace hydra::SDL3::Gl return nullptr; } - Context* ctx = new Context(); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); @@ -108,6 +115,23 @@ namespace hydra::SDL3::Gl SDL_SetWindowPosition(ctx->window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + inner->glContext = SDL_GL_CreateContext(ctx->window); + if (!inner->glContext) + { + hydra::panic("Failed to create OpenGL context: {}", SDL_GetError()); + return nullptr; + } + + if (gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) == 0) + { + hydra::panic("Failed to load OpenGL functions: {}", SDL_GetError()); + return nullptr; + } + + hydra::imgui::init(); + + ImGui_ImplSDL3_InitForOpenGL(ctx->window, inner->glContext); + return ctx; } @@ -118,7 +142,33 @@ namespace hydra::SDL3::Gl void shutdown(Context* context) { - printf("TODO: shutdown\n"); + if (!context || !context->window) + { + hydra::panic("Invalid context"); + return; + } + + ImGui_ImplSDL3_Shutdown(); + hydra::imgui::shutdown(); + + InnerContext* inner = (InnerContext*)context->inner; + if (!inner || !inner->glContext) + { + hydra::panic("Invalid inner context"); + return; + } + else + { + SDL_GL_DeleteContext(inner->glContext); + } + + if (context->window) + { + SDL_DestroyWindow(context->window); + } + + delete inner; + delete context; } } // namespace hydra::SDL3::Gl \ No newline at end of file diff --git a/src/hydra/sdl3/vulkan.cxx b/src/hydra/sdl3/vulkan.cxx index cf06b38c..cdacb917 100644 --- a/src/hydra/sdl3/vulkan.cxx +++ b/src/hydra/sdl3/vulkan.cxx @@ -1,7 +1,5 @@ #include "backends/imgui_impl_sdl3.h" #include "backends/imgui_impl_vulkan.h" -#include "hydra/common/version.hxx" -#include "hydra/core.h" #include #include #include @@ -11,6 +9,9 @@ #include #include +#include +#include +#include #include struct InnerContext @@ -449,17 +450,7 @@ namespace hydra::SDL3::Vk ImGui_ImplVulkanH_Window* wd = &ctx->mainWindowData; setupVulkanWindow(context, wd, surface, w, h); - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - - // Setup Dear ImGui style - ImGui::StyleColorsDark(); - // ImGui::StyleColorsLight(); + hydra::imgui::init(); // Setup Platform/Renderer backends ImGui_ImplSDL3_InitForVulkan(context->window); @@ -490,7 +481,7 @@ namespace hydra::SDL3::Vk ImGui_ImplVulkan_Shutdown(); ImGui_ImplSDL3_Shutdown(); - ImGui::DestroyContext(); + hydra::imgui::shutdown(); ImGui_ImplVulkanH_DestroyWindow(ctx->instance, ctx->device, &ctx->mainWindowData, ctx->allocator);