From 57f18d7e806f733690ea4bdcea7169398cb91ec0 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 2 Oct 2026 18:52:45 +1000 Subject: [PATCH] [client] windows: add native clang-cl build Build the Windows client against the MSVC ABI while retaining clang language support. Use vcpkg for ANGLE and libsamplerate, and build MsQuic with its bundled static OpenSSL. --- client/CMakeLists.txt | 114 +++++++++---- client/CMakePresets.json | 42 +++++ client/audiodevs/WASAPI/CMakeLists.txt | 5 +- client/cmake/FindANGLE.cmake | 13 ++ client/cmake/FindSampleRate.cmake | 7 + client/include/overlay_utils.h | 3 +- client/renderers/EGL/CMakeLists.txt | 23 ++- client/renderers/EGL/filter_glsl.c | 14 +- client/renderers/EGL/glsl.include.awk | 13 -- client/renderers/EGL/postprocess.c | 9 +- client/src/config.c | 36 ++--- client/src/fallback.c | 3 +- client/src/font.c | 7 +- client/src/overlay/status.c | 2 +- client/src/overlay_utils.c | 2 +- client/tests/CMakeLists.txt | 52 +----- client/tests/texture_dmabuf_test.c | 8 +- client/transports/Test/CMakeLists.txt | 8 +- client/transports/Test/test.c | 8 +- client/vcpkg.json | 15 ++ cmake/MakeObject.cmake | 180 ++++++++++++++++++--- cmake/MsQuic.cmake | 36 ++++- cmake/OptimizeForNative.cmake | 29 +++- common/CMakeLists.txt | 4 +- common/include/common/stringutils.h | 9 +- common/src/framebuffer.c | 3 +- common/src/option.c | 20 +-- common/src/platform/windows/CMakeLists.txt | 4 +- common/src/stringutils.c | 48 +++++- doc/build_client_windows.rst | 44 +++++ doc/index.rst | 1 + doc/words.txt | 8 + repos/gui/CMakeLists.txt | 18 ++- 33 files changed, 583 insertions(+), 205 deletions(-) create mode 100644 client/CMakePresets.json delete mode 100644 client/renderers/EGL/glsl.include.awk create mode 100644 client/vcpkg.json create mode 100644 doc/build_client_windows.rst diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index a8f8a647..08ee5251 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.20) project(looking-glass-client C CXX) get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE) @@ -34,6 +34,14 @@ else() "The Looking Glass client does not support ${CMAKE_SYSTEM_NAME}") endif() +if(LG_CLIENT_PLATFORM_WINDOWS AND MSVC AND + (NOT CMAKE_C_COMPILER_ID STREQUAL "Clang" OR + NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) + message(FATAL_ERROR + "The native Windows client currently requires clang-cl for both C and " + "C++; use the windows-clangcl configure preset") +endif() + set(OPTIMIZE_FOR_NATIVE_DEFAULT ON) include(OptimizeForNative) # option(OPTIMIZE_FOR_NATIVE) include(UninstallTarget) @@ -145,34 +153,67 @@ if(ENABLE_USB_AUDIO) endif() add_feature_info(ENABLE_USB_AUDIO ENABLE_USB_AUDIO "USB audio support.") -add_compile_options( - "-Wall" - "-Wextra" - "-Wno-sign-compare" - "-Wno-unused-parameter" - "$<$:-Wstrict-prototypes>" - "$<$:-Wimplicit-fallthrough=2>" - "-Werror" - "-Wfatal-errors" - "-g" - "-ffast-math" - "-fdata-sections" - "-ffunction-sections" - "$<$:-O0;-g3;-ggdb>" -) - -if(ENABLE_BACKTRACE) +if(MSVC) + foreach(LG_CLIENT_COMPILE_OPTION + /W4 + /WX + /utf-8 + /fp:fast + /Gy + /Gw + /wd4100 + /clang:-Wno-sign-compare + /clang:-Wno-unused-parameter + /clang:-Wfatal-errors) + add_compile_options( + "$<$:${LG_CLIENT_COMPILE_OPTION}>" + "$<$:${LG_CLIENT_COMPILE_OPTION}>") + endforeach() add_compile_options( - "-fno-omit-frame-pointer" - "-fno-optimize-sibling-calls" + "$<$:/clang:-Wstrict-prototypes>") + add_link_options(/OPT:REF /NXCOMPAT) +else() + add_compile_options( + "-Wall" + "-Wextra" + "-Wno-sign-compare" + "-Wno-unused-parameter" + "$<$:-Wstrict-prototypes>" + "$<$:-Wimplicit-fallthrough=2>" + "-Werror" + "-Wfatal-errors" + "-g" + "-ffast-math" + "-fdata-sections" + "-ffunction-sections" + "$<$:-O0;-g3;-ggdb>" ) + + if(LG_CLIENT_PLATFORM_WINDOWS) + add_link_options("-Wl,--gc-sections" "-Wl,--nxcompat") + else() + add_link_options("-Wl,--gc-sections" "-z" "noexecstack") + endif() endif() -set(EXE_FLAGS "-Wl,--gc-sections -z noexecstack") -set(CMAKE_C_STANDARD 11) +if(ENABLE_BACKTRACE) + if(MSVC) + add_compile_options( + "$<$:/Oy->" + "$<$:/Oy->" + "$<$:/clang:-fno-optimize-sibling-calls>" + "$<$:/clang:-fno-optimize-sibling-calls>") + else() + add_compile_options( + "-fno-omit-frame-pointer" + "-fno-optimize-sibling-calls") + endif() +endif() -if(LG_CLIENT_PLATFORM_WINDOWS) - set(EXE_FLAGS "-Wl,--gc-sections -Wl,--nxcompat") +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +if(MINGW) add_compile_options("-fdebug-prefix-map=${PROJECT_TOP}/=") add_compile_definitions(__USE_MINGW_ANSI_STDIO=0) endif() @@ -186,13 +227,22 @@ if (ENABLE_EGL) endif() if(ENABLE_ASAN) - add_compile_options("-fno-omit-frame-pointer" "-fsanitize=address") - set(EXE_FLAGS "${EXE_FLAGS} -fno-omit-frame-pointer -fsanitize=address") + if(MSVC) + message(FATAL_ERROR + "AddressSanitizer is not supported by the clang-cl build") + else() + add_compile_options("-fno-omit-frame-pointer" "-fsanitize=address") + add_link_options("-fno-omit-frame-pointer" "-fsanitize=address") + endif() endif() if(ENABLE_UBSAN) + if(MSVC) + message(FATAL_ERROR + "UndefinedBehaviorSanitizer is not supported by the clang-cl build") + endif() add_compile_options("-fsanitize=undefined") - set(EXE_FLAGS "${EXE_FLAGS} -fsanitize=undefined") + add_link_options("-fsanitize=undefined") endif() add_definitions(-D ATOMIC_LOCKING) @@ -266,9 +316,13 @@ if(ENABLE_EGL) list(APPEND SOURCES src/egl_dynprocs.c) endif() -if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") +if(MSVC) set_source_files_properties(src/overlay/graphs.c PROPERTIES - COMPILE_FLAGS "-fno-fast-math" + COMPILE_OPTIONS "/fp:precise" + ) +elseif(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set_source_files_properties(src/overlay/graphs.c PROPERTIES + COMPILE_OPTIONS "-fno-fast-math" ) endif() @@ -407,8 +461,6 @@ target_link_libraries(looking-glass-client lg_gui ) -set_target_properties(looking-glass-client PROPERTIES LINK_FLAGS "${EXE_FLAGS}") - if(ENABLE_TESTS AND LG_CLIENT_PLATFORM_WINDOWS) add_library(client_test_version OBJECT "${CMAKE_BINARY_DIR}/version.c" diff --git a/client/CMakePresets.json b/client/CMakePresets.json new file mode 100644 index 00000000..1c9851d6 --- /dev/null +++ b/client/CMakePresets.json @@ -0,0 +1,42 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "windows-clangcl", + "displayName": "Windows clang-cl", + "description": "Build the Windows client with the MSVC ABI", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/windows-clangcl", + "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreadedDLL", + "ENABLE_AUDIO": true, + "ENABLE_EGL": true, + "ENABLE_NETWORK": true, + "ENABLE_WASAPI": true, + "OPTIMIZE_FOR_NATIVE": "OFF", + "VCPKG_TARGET_TRIPLET": "x64-windows" + } + } + ], + "buildPresets": [ + { + "name": "windows-clangcl-release", + "displayName": "Windows clang-cl RelWithDebInfo", + "configurePreset": "windows-clangcl" + } + ] +} diff --git a/client/audiodevs/WASAPI/CMakeLists.txt b/client/audiodevs/WASAPI/CMakeLists.txt index e423eb8c..5f69455b 100644 --- a/client/audiodevs/WASAPI/CMakeLists.txt +++ b/client/audiodevs/WASAPI/CMakeLists.txt @@ -11,11 +11,14 @@ add_library(audiodev_WASAPI STATIC target_link_libraries(audiodev_WASAPI avrt lg_common - m ole32 uuid ) +if(MINGW) + target_link_libraries(audiodev_WASAPI m) +endif() + target_include_directories(audiodev_WASAPI PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}" diff --git a/client/cmake/FindANGLE.cmake b/client/cmake/FindANGLE.cmake index f72ecf74..c8c1afcd 100644 --- a/client/cmake/FindANGLE.cmake +++ b/client/cmake/FindANGLE.cmake @@ -42,6 +42,18 @@ find_package_handle_standard_args(ANGLE ANGLE_GLESV2_LIBRARY ) +if(ANGLE_FOUND AND MSVC) + foreach(_ANGLE_LIBRARY IN ITEMS + "${ANGLE_EGL_LIBRARY}" + "${ANGLE_GLESV2_LIBRARY}") + if(_ANGLE_LIBRARY MATCHES "\\.(a|dll\\.a)$") + message(FATAL_ERROR + "The clang-cl client requires MSVC-ABI ANGLE .lib files; found " + "${_ANGLE_LIBRARY}") + endif() + endforeach() +endif() + mark_as_advanced( ANGLE_EGL_INCLUDE_DIR ANGLE_GLES_INCLUDE_DIR @@ -68,3 +80,4 @@ endif() unset(_ANGLE_HINTS) unset(_ANGLE_FIND_POLICY) +unset(_ANGLE_LIBRARY) diff --git a/client/cmake/FindSampleRate.cmake b/client/cmake/FindSampleRate.cmake index c2674db2..78353a20 100644 --- a/client/cmake/FindSampleRate.cmake +++ b/client/cmake/FindSampleRate.cmake @@ -27,6 +27,13 @@ find_package_handle_standard_args(SampleRate SampleRate_LIBRARY ) +if(SampleRate_FOUND AND MSVC AND + SampleRate_LIBRARY MATCHES "\\.(a|dll\\.a)$") + message(FATAL_ERROR + "The clang-cl client requires an MSVC-ABI samplerate .lib; found " + "${SampleRate_LIBRARY}") +endif() + mark_as_advanced( SampleRate_INCLUDE_DIR SampleRate_LIBRARY diff --git a/client/include/overlay_utils.h b/client/include/overlay_utils.h index 0b5c5240..20e32bb1 100644 --- a/client/include/overlay_utils.h +++ b/client/include/overlay_utils.h @@ -22,6 +22,7 @@ #define _H_LG_OVERLAY_UTILS_ #include +#include #include "common/types.h" @@ -41,7 +42,7 @@ void overlayTextURL(const char * url, const char * text); void overlayTextMaybeURL(const char * text, bool wrapped); // create a texture from a SVG and scale it to fit the supplied width & height -bool overlayLoadSVG(const char * data, unsigned int size, OverlayImage * image, +bool overlayLoadSVG(const char * data, size_t size, OverlayImage * image, int width, int height); void overlayFreeImage(OverlayImage * image); diff --git a/client/renderers/EGL/CMakeLists.txt b/client/renderers/EGL/CMakeLists.txt index 307a3de3..59e3d049 100644 --- a/client/renderers/EGL/CMakeLists.txt +++ b/client/renderers/EGL/CMakeLists.txt @@ -1,26 +1,23 @@ cmake_minimum_required(VERSION 3.10) project(renderer_EGL LANGUAGES C CXX) -find_program(AWK NAMES gawk mawk original-awk awk) - -if(AWK MATCHES ".+-NOTFOUND") - message(FATAL_ERROR "FATAL: some known version of awk couldn't be found (${AWK}).") -else() - message(STATUS "Using awk: ${AWK}") -endif() - include(MakeObject) function(build_shaders header_dir) file(GLOB headers "${header_dir}/*.h") set(EGL_SHADER_PROCESSED) + set(include_dir "${CMAKE_CURRENT_SOURCE_DIR}/${header_dir}") foreach(shader ${ARGN}) set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${shader}") - add_custom_command(OUTPUT "${out_f}" - COMMAND "${AWK}" -f "${CMAKE_CURRENT_SOURCE_DIR}/glsl.include.awk" - "${CMAKE_CURRENT_SOURCE_DIR}/${shader}" > "${out_f}" + add_custom_command( + OUTPUT "${out_f}" + COMMAND "${CMAKE_COMMAND}" + "-DLG_MAKE_OBJECT_ACTION=PREPROCESS" + "-DLG_MAKE_OBJECT_INPUT=${CMAKE_CURRENT_SOURCE_DIR}/${shader}" + "-DLG_MAKE_OBJECT_OUTPUT=${out_f}" + "-DLG_MAKE_OBJECT_INCLUDE_DIR=${include_dir}" + -P "${LG_MAKE_OBJECT_SCRIPT}" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${shader}" - DEPENDS ${headers} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/shader" + DEPENDS ${headers} "${LG_MAKE_OBJECT_SCRIPT}" COMMENT "Preprocessing shader ${shader}" VERBATIM ) diff --git a/client/renderers/EGL/filter_glsl.c b/client/renderers/EGL/filter_glsl.c index b9381743..2107430b 100644 --- a/client/renderers/EGL/filter_glsl.c +++ b/client/renderers/EGL/filter_glsl.c @@ -18,7 +18,6 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#define _GNU_SOURCE #include "filter.h" #include "effect.h" #include "app.h" @@ -45,7 +44,6 @@ #include #include #include -#include #define GLSL_MAX_TEXTURES 16 #define GLSL_EXPR_STACK 128 @@ -633,11 +631,11 @@ static bool evaluateExpression(const GLSLContext * context, if (!copy) return false; - double stack[GLSL_EXPR_STACK]; + double stack[GLSL_EXPR_STACK]; unsigned int count = 0; - char * state; - for (char * token = strtok_r(copy, " \t\r\n", &state); token; - token = strtok_r(NULL, " \t\r\n", &state)) + char * state = NULL; + for (char * token = lg_strtok_r(copy, " \t\r\n", &state); token; + token = lg_strtok_r(NULL, " \t\r\n", &state)) { if (!strcmp(token, "!")) { @@ -1077,7 +1075,7 @@ static bool createFilter(const char * root, const char * relative, if (!this->relative || !this->base.ops.name) goto fail; char * extension = strrchr((char *)this->base.ops.name, '.'); - if (extension && !strcasecmp(extension, ".glsl")) + if (extension && !lg_strcasecmp(extension, ".glsl")) *extension = '\0'; app_registerImGuiText(this->base.ops.name); @@ -1118,7 +1116,7 @@ static void glslFree(EGL_Filter * filter) static bool hasGLSLExtension(const char * name) { const char * extension = strrchr(name, '.'); - return extension && !strcasecmp(extension, ".glsl"); + return extension && !lg_strcasecmp(extension, ".glsl"); } static bool scanDirectory(const char * root, const char * relative, diff --git a/client/renderers/EGL/glsl.include.awk b/client/renderers/EGL/glsl.include.awk deleted file mode 100644 index 1c050e47..00000000 --- a/client/renderers/EGL/glsl.include.awk +++ /dev/null @@ -1,13 +0,0 @@ -BEGIN { FS="\"" } - -function process(line, second) { - if (line ~ /^#include[ \t]*".+"[ \t\r]*$/) { - while (getline < second) { - process($0, $2) - } - } else { - print line - } -} - -{ process($0, $2) } END { print "\0"; } diff --git a/client/renderers/EGL/postprocess.c b/client/renderers/EGL/postprocess.c index f4d05d26..f8bfaac4 100644 --- a/client/renderers/EGL/postprocess.c +++ b/client/renderers/EGL/postprocess.c @@ -154,7 +154,7 @@ static void loadPresetList(struct EGL_PostProcess * this) continue; DEBUG_INFO("Found preset: %s", entry); - char * name = strdup(entry); + char * name = lg_strdup(entry); if (!name) { DEBUG_ERROR("Failed to allocate memory"); @@ -260,7 +260,7 @@ static void reorderFilters(struct EGL_PostProcess * this) return; } - char * orderStr = strdup(option_get_string("eglFilter", "order")); + char * orderStr = lg_strdup(option_get_string("eglFilter", "order")); if (!orderStr) { DEBUG_ERROR("Failed to allocate memory"); @@ -339,7 +339,8 @@ static void savePresetAs(struct EGL_PostProcess * this) } } - this->activePreset = stringlist_push(this->presets, strdup(this->presetEdit)); + this->activePreset = stringlist_push( + this->presets, lg_strdup(this->presetEdit)); } static void deletePreset(struct EGL_PostProcess * this) @@ -396,7 +397,7 @@ static bool presetsUI(struct EGL_PostProcess * this) if (this->activePreset >= 0) savePreset(this, stringlist_at(this->presets, this->activePreset)); else - presetError(this, strdup("You must select a preset to save.")); + presetError(this, lg_strdup("You must select a preset to save.")); } if (igIsItemHovered(ImGuiHoveredFlags_None) && this->activePreset >= 0) diff --git a/client/src/config.c b/client/src/config.c index 28f69710..077810e1 100644 --- a/client/src/config.c +++ b/client/src/config.c @@ -738,14 +738,14 @@ static bool optRendererParse(struct Option * opt, const char * str) if (!str) return false; - if (strcasecmp(str, "auto") == 0) + if (lg_strcasecmp(str, "auto") == 0) { g_params.forceRenderer = false; return true; } for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) - if (strcasecmp(str, LG_Renderers[i]->getName()) == 0) + if (lg_strcasecmp(str, LG_Renderers[i]->getName()) == 0) { g_params.forceRenderer = true; g_params.forceRendererIndex = i; @@ -771,12 +771,12 @@ static StringList optRendererValues(struct Option * opt) static char * optRendererToString(struct Option * opt) { if (!g_params.forceRenderer) - return strdup("auto"); + return lg_strdup("auto"); if (g_params.forceRendererIndex >= LG_RENDERER_COUNT) return NULL; - return strdup(LG_Renderers[g_params.forceRendererIndex]->getName()); + return lg_strdup(LG_Renderers[g_params.forceRendererIndex]->getName()); } static bool parsePair(const char * str, long long * first, long long * second) @@ -838,7 +838,7 @@ static StringList optPosValues(struct Option * opt) static char * optPosToString(struct Option * opt) { if (g_params.center) - return strdup("center"); + return lg_strdup("center"); int len = snprintf(NULL, 0, "%dx%d", g_params.x, g_params.y); char * str = malloc(len + 1); @@ -919,7 +919,7 @@ static bool optScancodeParse(struct Option * opt, const char * str) if (!lg_keycode_to_name[i]) continue; - if (strcasecmp(lg_keycode_to_name[i], str) == 0) + if (lg_strcasecmp(lg_keycode_to_name[i], str) == 0) { opt->value.x_int = i; return true; @@ -981,11 +981,11 @@ static bool optMicDefaultParse(struct Option * opt, const char * str) if (!str) return false; - if (strcasecmp(str, "prompt") == 0) + if (lg_strcasecmp(str, "prompt") == 0) g_params.micDefaultState = MIC_DEFAULT_PROMPT; - else if (strcasecmp(str, "allow") == 0) + else if (lg_strcasecmp(str, "allow") == 0) g_params.micDefaultState = MIC_DEFAULT_ALLOW; - else if (strcasecmp(str, "deny") == 0) + else if (lg_strcasecmp(str, "deny") == 0) g_params.micDefaultState = MIC_DEFAULT_DENY; else return false; @@ -1010,11 +1010,11 @@ static char * optMicDefaultToString(struct Option * opt) switch (g_params.micDefaultState) { case MIC_DEFAULT_PROMPT: - return strdup("prompt"); + return lg_strdup("prompt"); case MIC_DEFAULT_ALLOW: - return strdup("allow"); + return lg_strdup("allow"); case MIC_DEFAULT_DENY: - return strdup("deny"); + return lg_strdup("deny"); } return NULL; @@ -1026,11 +1026,11 @@ static bool optAudioResamplerParse( if (!str) return false; - if (strcasecmp(str, "auto") == 0) + if (lg_strcasecmp(str, "auto") == 0) g_params.audioResampler = AUDIO_RESAMPLER_AUTO; - else if (strcasecmp(str, "libsamplerate") == 0) + else if (lg_strcasecmp(str, "libsamplerate") == 0) g_params.audioResampler = AUDIO_RESAMPLER_LIBSAMPLERATE; - else if (strcasecmp(str, "backend") == 0) + else if (lg_strcasecmp(str, "backend") == 0) g_params.audioResampler = AUDIO_RESAMPLER_BACKEND; else return false; @@ -1055,11 +1055,11 @@ static char * optAudioResamplerToString(struct Option * opt) switch (g_params.audioResampler) { case AUDIO_RESAMPLER_AUTO: - return strdup("auto"); + return lg_strdup("auto"); case AUDIO_RESAMPLER_LIBSAMPLERATE: - return strdup("libsamplerate"); + return lg_strdup("libsamplerate"); case AUDIO_RESAMPLER_BACKEND: - return strdup("backend"); + return lg_strdup("backend"); } return NULL; diff --git a/client/src/fallback.c b/client/src/fallback.c index d5f6d5e4..dc82efe4 100644 --- a/client/src/fallback.c +++ b/client/src/fallback.c @@ -28,6 +28,7 @@ #include "common/debug.h" #include "common/event.h" #include "common/locking.h" +#include "common/stringutils.h" #include "common/thread.h" #include "common/util.h" @@ -561,7 +562,7 @@ bool lgTransportFallback_start(const char * transportName, atomic_init(&fallback->usable, false); atomic_init(&fallback->videoAcceptingEvents, false); - fallback->transportName = strdup(transportName); + fallback->transportName = lg_strdup(transportName); if (!fallback->transportName) goto fail; diff --git a/client/src/font.c b/client/src/font.c index 1fd04b53..88419d91 100644 --- a/client/src/font.c +++ b/client/src/font.c @@ -24,6 +24,7 @@ #include "common/debug.h" #include "common/filesystem.h" #include "common/locking.h" +#include "common/stringutils.h" #include "cimgui.h" @@ -33,7 +34,7 @@ #include #include -#if defined(__GNUC__) +#if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-function" #endif @@ -43,7 +44,7 @@ #define STBTT_STATIC #define STB_TRUETYPE_IMPLEMENTATION #include "repos/gui/cimgui/imgui/imstb_truetype.h" -#if defined(__GNUC__) +#if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif @@ -360,7 +361,7 @@ static FontPlatformFaceResult uiFontAddFace(void * opaque, return FONT_PLATFORM_FACE_ERROR; } - char * fileCopy = strdup(file); + char * fileCopy = lg_strdup(file); if (!fileCopy || !uiFontGlyphReserve( &UIFontResolved, UIFontResolved.count + glyphCount)) diff --git a/client/src/overlay/status.c b/client/src/overlay/status.c index e35876a1..7465f4b0 100644 --- a/client/src/overlay/status.c +++ b/client/src/overlay/status.c @@ -38,7 +38,7 @@ static OverlayImage l_image[LG_USER_STATUS_MAX] = { 0 }; static bool l_recordToggle; static double l_scale = 1.0; -static void status_loadImage(const char * data, unsigned int size, +static void status_loadImage(const char * data, size_t size, OverlayImage * image, int width, int height) { overlayFreeImage(image); diff --git a/client/src/overlay_utils.c b/client/src/overlay_utils.c index f89658d5..b695cea3 100644 --- a/client/src/overlay_utils.c +++ b/client/src/overlay_utils.c @@ -95,7 +95,7 @@ void overlayTextMaybeURL(const char * text, bool wrapped) igTextUnformatted(text, NULL); } -bool overlayLoadSVG(const char * data, unsigned int size, OverlayImage * image, +bool overlayLoadSVG(const char * data, size_t size, OverlayImage * image, int width, int height) { image->tex = NULL; diff --git a/client/tests/CMakeLists.txt b/client/tests/CMakeLists.txt index dc012f16..dd26bfee 100644 --- a/client/tests/CMakeLists.txt +++ b/client/tests/CMakeLists.txt @@ -11,7 +11,6 @@ add_executable(common-platform-tests common_platform_test.c ) target_link_libraries(common-platform-tests - ${EXE_FLAGS} lg_common ) target_compile_definitions(common-platform-tests PRIVATE @@ -32,7 +31,6 @@ target_include_directories(event-source-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src" ) target_link_libraries(event-source-tests - ${EXE_FLAGS} lg_common ) add_test(NAME event-source-tests @@ -46,9 +44,6 @@ add_executable(windows-clipboard-files-tests clipboard_files_windows_test.c ../platform/Windows/clipboard_files.c ) -target_link_libraries(windows-clipboard-files-tests - ${EXE_FLAGS} -) add_test(NAME windows-clipboard-files-tests COMMAND windows-clipboard-files-tests ) @@ -65,7 +60,6 @@ if(LG_CLIENT_PLATFORM_LINUX) "${CMAKE_CURRENT_SOURCE_DIR}/../src" ) target_link_libraries(config-platform-tests - ${EXE_FLAGS} lg_common ) add_test(NAME config-platform-tests @@ -79,7 +73,6 @@ if(LG_CLIENT_PLATFORM_LINUX) lgmp_platform_test.c ) target_link_libraries(lgmp-platform-tests - ${EXE_FLAGS} lg_common ) add_test(NAME lgmp-platform-tests @@ -103,9 +96,6 @@ if(LG_CLIENT_PLATFORM_LINUX) process_test.c ../platform/Linux/process.c ) - target_link_libraries(process-tests - ${EXE_FLAGS} - ) add_test(NAME process-tests COMMAND process-tests ) @@ -147,7 +137,6 @@ if(LG_CLIENT_PLATFORM_WINDOWS) common_windows_test.c ) target_link_libraries(common-windows-tests - ${EXE_FLAGS} lg_common ) add_test(NAME common-windows-tests @@ -169,7 +158,6 @@ target_include_directories(sw-surface-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src" ) target_link_libraries(sw-surface-tests - ${EXE_FLAGS} lg_common ) add_test(NAME sw-surface-tests COMMAND sw-surface-tests) @@ -183,7 +171,6 @@ target_include_directories(font-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src" ) target_link_libraries(font-tests - ${EXE_FLAGS} lg_common lg_gui ) @@ -200,7 +187,6 @@ if(LG_CLIENT_PLATFORM_LINUX) ../src/kb_hid.c ) target_link_libraries(lgmp-transport-tests - ${EXE_FLAGS} transport_LGMP lg_common lgmp @@ -222,7 +208,6 @@ if(LG_CLIENT_PLATFORM_LINUX) ../src/kb_hid.c ) target_link_libraries(lgmp-frame-tests - ${EXE_FLAGS} transport_LGMP lg_common lgmp @@ -252,7 +237,6 @@ if(LG_CLIENT_PLATFORM_LINUX) "${CMAKE_CURRENT_SOURCE_DIR}/../src" ) target_link_libraries(lgmp-input-tests - ${EXE_FLAGS} lg_common lgmp ) @@ -280,7 +264,6 @@ if(LG_CLIENT_PLATFORM_LINUX) "${CMAKE_CURRENT_SOURCE_DIR}/../transports/LGMP" ) target_link_libraries(lgmp-clipboard-tests - ${EXE_FLAGS} lg_common lgmp ) @@ -322,7 +305,6 @@ if(LG_CLIENT_PLATFORM_LINUX) "${CMAKE_CURRENT_SOURCE_DIR}/../transports/LGMP" ) target_link_libraries(lgmp-audio-tests - ${EXE_FLAGS} transport_LGMP lg_common lgmp @@ -362,7 +344,6 @@ if(LG_CLIENT_PLATFORM_LINUX AND ENABLE_WAYLAND) "${CMAKE_BINARY_DIR}/wayland" ) target_link_libraries(wayland-presentation-tests - ${EXE_FLAGS} lg_common ) set(WAYLAND_PRESENTATION_CASES @@ -393,7 +374,6 @@ if(LG_CLIENT_PLATFORM_LINUX AND ENABLE_WAYLAND) "${CMAKE_BINARY_DIR}/wayland" ) target_link_libraries(wayland-clipboard-tests - ${EXE_FLAGS} lg_common ) set(WAYLAND_CLIPBOARD_CASES @@ -441,7 +421,6 @@ if(LG_CLIENT_PLATFORM_LINUX AND ENABLE_X11) "${CMAKE_CURRENT_SOURCE_DIR}/../displayservers/X11" ) target_link_libraries(x11-clipboard-tests - ${EXE_FLAGS} lg_common pthread ) @@ -561,7 +540,6 @@ target_include_directories(mouse-tests PRIVATE "${PROJECT_TOP}/repos/gui/cimgui/imgui" ) target_link_libraries(mouse-tests - ${EXE_FLAGS} lg_common ) set(MOUSE_CASES @@ -603,7 +581,6 @@ target_include_directories(input-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src" ) target_link_libraries(input-tests - ${EXE_FLAGS} lg_common ) set(INPUT_CASES @@ -636,7 +613,6 @@ target_include_directories(keybind-tests PRIVATE "${PROJECT_TOP}/repos/gui/cimgui/imgui" ) target_link_libraries(keybind-tests - ${EXE_FLAGS} lg_common ) set(KEYBIND_CASES @@ -668,7 +644,6 @@ target_include_directories(app-keybind-tests PRIVATE "${PROJECT_TOP}/repos/gui/cimgui/imgui" ) target_link_libraries(app-keybind-tests - ${EXE_FLAGS} lg_common lg_gui ) @@ -705,7 +680,6 @@ target_include_directories(clipboard-tests PRIVATE "${PROJECT_TOP}/repos/gui/cimgui/imgui" ) target_link_libraries(clipboard-tests - ${EXE_FLAGS} lg_common ) set(CLIPBOARD_CASES @@ -746,7 +720,6 @@ if(LG_CLIENT_PLATFORM_LINUX) "${CMAKE_CURRENT_SOURCE_DIR}/../src" ) target_link_libraries(clipboard-files-tests - ${EXE_FLAGS} lg_common PkgConfig::FUSE3 ) @@ -789,7 +762,6 @@ if(ENABLE_SPICE) "${PROJECT_TOP}/repos/gui/cimgui/imgui" ) target_link_libraries(spice-clipboard-tests - ${EXE_FLAGS} lg_common ) foreach(name IN ITEMS outbound inbound) @@ -811,7 +783,6 @@ target_include_directories(transport-fallback-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src" ) target_link_libraries(transport-fallback-tests - ${EXE_FLAGS} lg_common ) if(ENABLE_AUDIO) @@ -861,7 +832,6 @@ if(ENABLE_SPICE) "${PROJECT_TOP}/repos/PureSpice/include" ) target_link_libraries(spice-tests - ${EXE_FLAGS} lg_common ) set(SPICE_CASES @@ -897,7 +867,6 @@ if(ENABLE_SPICE) "$" ) target_link_libraries(purespice-connect-cancel-tests - ${EXE_FLAGS} purespice pthread ) @@ -923,7 +892,6 @@ target_include_directories(frame-scheduler-tests PRIVATE "${PROJECT_TOP}/repos/gui/cimgui/imgui" ) target_link_libraries(frame-scheduler-tests - ${EXE_FLAGS} lg_common ) set(FRAME_SCHEDULER_CASES @@ -950,17 +918,20 @@ add_executable(frame-timing-tests target_compile_definitions(frame-timing-tests PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 ) -target_compile_options(frame-timing-tests PRIVATE - -O1 - "$<$:-fwhole-program>" -) +if(MSVC) + target_compile_options(frame-timing-tests PRIVATE /clang:-O1) +else() + target_compile_options(frame-timing-tests PRIVATE + -O1 + "$<$:-fwhole-program>" + ) +endif() target_include_directories(frame-timing-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src" "${PROJECT_TOP}/repos/gui/cimgui" "${PROJECT_TOP}/repos/gui/cimgui/imgui" ) target_link_libraries(frame-timing-tests - ${EXE_FLAGS} lg_common ) set(FRAME_TIMING_CASES @@ -1005,7 +976,6 @@ target_include_directories(config-message-tests PRIVATE "${PROJECT_TOP}/repos/gui/cimgui/imgui" ) target_link_libraries(config-message-tests - ${EXE_FLAGS} lg_common ) set(CONFIG_MESSAGE_CASES @@ -1042,7 +1012,6 @@ if(ENABLE_AUDIO) "${PROJECT_TOP}/repos/gui/cimgui/imgui" ) target_link_libraries(audio-tests - ${EXE_FLAGS} lg_common ${SAMPLERATE_TARGET} ) @@ -1082,7 +1051,6 @@ target_include_directories(render-queue-tests PRIVATE "${PROJECT_TOP}/repos/gui/cimgui/imgui" ) target_link_libraries(render-queue-tests - ${EXE_FLAGS} lg_common ) set(RENDER_QUEUE_CASES @@ -1112,7 +1080,6 @@ if(ENABLE_EGL) "${CMAKE_CURRENT_SOURCE_DIR}/../renderers/EGL" ) target_link_libraries(desktop-rects-tests - ${EXE_FLAGS} renderer_EGL ) set(DESKTOP_RECTS_CASES @@ -1139,7 +1106,6 @@ if(ENABLE_EGL) "${CMAKE_BINARY_DIR}/renderers/EGL/shader" ) target_link_libraries(texture-dmabuf-tests - ${EXE_FLAGS} lg_common ) set(TEXTURE_DMABUF_CASES @@ -1167,7 +1133,6 @@ if(ENABLE_EGL) "${CMAKE_CURRENT_SOURCE_DIR}/../renderers/EGL" ) target_link_libraries(postprocess-order-tests - ${EXE_FLAGS} lg_common lg_egl_provider ) @@ -1188,7 +1153,6 @@ if(ENABLE_EGL) "${PROJECT_TOP}/common/include" ) target_link_libraries(texture-upload-tests - ${EXE_FLAGS} LGProtocol::LGProtocol lg_egl_provider ) diff --git a/client/tests/texture_dmabuf_test.c b/client/tests/texture_dmabuf_test.c index 90603d13..63fea0c7 100644 --- a/client/tests/texture_dmabuf_test.c +++ b/client/tests/texture_dmabuf_test.c @@ -116,10 +116,10 @@ static void externalFrameInit(struct FakeExternalFrame * external) struct EGLDynProcs g_egl_dynProcs; _Thread_local EGL_StateCache g_eglState; -const char b_shader_hdr_compose_vert[] = ""; -const char b_shader_hdr_compose_vert_end[] = ""; -const char b_shader_downscale_linear_frag[] = ""; -const char b_shader_downscale_linear_frag_end[] = ""; +const char b_shader_hdr_compose_vert[] = ""; +const size_t b_shader_hdr_compose_vert_size = 1; +const char b_shader_downscale_linear_frag[] = ""; +const size_t b_shader_downscale_linear_frag_size = 1; static struct FakeSync * syncPtr(GLsync sync) { diff --git a/client/transports/Test/CMakeLists.txt b/client/transports/Test/CMakeLists.txt index 5b6c38c9..e38fe6fc 100644 --- a/client/transports/Test/CMakeLists.txt +++ b/client/transports/Test/CMakeLists.txt @@ -5,9 +5,11 @@ add_library(transport_Test STATIC test.c ) -target_compile_options(transport_Test PRIVATE - -fno-fast-math -) +if(MSVC) + target_compile_options(transport_Test PRIVATE /fp:precise) +else() + target_compile_options(transport_Test PRIVATE -fno-fast-math) +endif() target_link_libraries(transport_Test lg_common diff --git a/client/transports/Test/test.c b/client/transports/Test/test.c index 56fc2625..281bcbcd 100644 --- a/client/transports/Test/test.c +++ b/client/transports/Test/test.c @@ -29,7 +29,6 @@ #include #include #include -#include #define TEST_BUFFER_COUNT 2 #define TEST_PQ_LUT_SIZE 4096 @@ -610,7 +609,7 @@ static LG_TransportStatus test_nextFrame(LG_Transport * this, bool useDMA, { if (!this->holdLastFrame) return LG_TRANSPORT_END; - usleep(1000); + nsleep(UINT64_C(1000000)); return LG_TRANSPORT_TIMEOUT; } @@ -620,7 +619,8 @@ static LG_TransportStatus test_nextFrame(LG_Transport * this, bool useDMA, if (now < this->nextFrameTime) { const uint64_t remaining = this->nextFrameTime - now; - usleep((remaining > 1000000 ? 1000000 : remaining) / 1000); + nsleep(remaining > UINT64_C(1000000) ? + UINT64_C(1000000) : remaining); return LG_TRANSPORT_TIMEOUT; } } @@ -741,7 +741,7 @@ static LG_TransportStatus test_nextPointer(LG_Transport * this, { if (!this->connected) return LG_TRANSPORT_DISCONNECTED; - usleep(1000); + nsleep(UINT64_C(1000000)); return LG_TRANSPORT_TIMEOUT; } diff --git a/client/vcpkg.json b/client/vcpkg.json new file mode 100644 index 00000000..bd0f1921 --- /dev/null +++ b/client/vcpkg.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "looking-glass-client", + "version-string": "0", + "dependencies": [ + { + "name": "angle", + "platform": "windows" + }, + { + "name": "libsamplerate", + "platform": "windows" + } + ] +} diff --git a/cmake/MakeObject.cmake b/cmake/MakeObject.cmake index 6a29bdee..794d20bc 100644 --- a/cmake/MakeObject.cmake +++ b/cmake/MakeObject.cmake @@ -1,34 +1,156 @@ +if(LG_MAKE_OBJECT_ACTION STREQUAL "EMBED") + if(NOT DEFINED LG_MAKE_OBJECT_INPUT OR + NOT DEFINED LG_MAKE_OBJECT_SOURCE OR + NOT DEFINED LG_MAKE_OBJECT_HEADER OR + NOT DEFINED LG_MAKE_OBJECT_SYMBOL) + message(FATAL_ERROR + "The embed action requires input, source, header, and symbol") + endif() + + file(READ "${LG_MAKE_OBJECT_INPUT}" LG_MAKE_OBJECT_DATA HEX) + set(LG_MAKE_OBJECT_BYTES "") + string(LENGTH "${LG_MAKE_OBJECT_DATA}" LG_MAKE_OBJECT_LENGTH) + set(LG_MAKE_OBJECT_OFFSET 0) + while(LG_MAKE_OBJECT_OFFSET LESS LG_MAKE_OBJECT_LENGTH) + math(EXPR LG_MAKE_OBJECT_REMAINING + "${LG_MAKE_OBJECT_LENGTH} - ${LG_MAKE_OBJECT_OFFSET}") + if(LG_MAKE_OBJECT_REMAINING GREATER 32) + set(LG_MAKE_OBJECT_CHUNK_LENGTH 32) + else() + set(LG_MAKE_OBJECT_CHUNK_LENGTH "${LG_MAKE_OBJECT_REMAINING}") + endif() + + string(SUBSTRING "${LG_MAKE_OBJECT_DATA}" + ${LG_MAKE_OBJECT_OFFSET} ${LG_MAKE_OBJECT_CHUNK_LENGTH} + LG_MAKE_OBJECT_CHUNK) + string(REGEX REPLACE "([0-9a-fA-F][0-9a-fA-F])" + "(char)0x\\1, " LG_MAKE_OBJECT_CHUNK "${LG_MAKE_OBJECT_CHUNK}") + string(APPEND LG_MAKE_OBJECT_BYTES + " ${LG_MAKE_OBJECT_CHUNK}\n") + math(EXPR LG_MAKE_OBJECT_OFFSET + "${LG_MAKE_OBJECT_OFFSET} + ${LG_MAKE_OBJECT_CHUNK_LENGTH}") + endwhile() + + get_filename_component(LG_MAKE_OBJECT_SOURCE_DIR + "${LG_MAKE_OBJECT_SOURCE}" DIRECTORY) + get_filename_component(LG_MAKE_OBJECT_HEADER_DIR + "${LG_MAKE_OBJECT_HEADER}" DIRECTORY) + file(MAKE_DIRECTORY + "${LG_MAKE_OBJECT_SOURCE_DIR}" + "${LG_MAKE_OBJECT_HEADER_DIR}") + + file(WRITE "${LG_MAKE_OBJECT_SOURCE}" + "#include \n\n" + "const char ${LG_MAKE_OBJECT_SYMBOL}[] =\n" + "{\n${LG_MAKE_OBJECT_BYTES} (char)0x00\n};\n\n" + "const size_t ${LG_MAKE_OBJECT_SYMBOL}_size =\n" + " sizeof(${LG_MAKE_OBJECT_SYMBOL});\n") + file(WRITE "${LG_MAKE_OBJECT_HEADER}" + "#include \n\n" + "extern const char ${LG_MAKE_OBJECT_SYMBOL}[];\n" + "extern const size_t ${LG_MAKE_OBJECT_SYMBOL}_size;\n") + return() +endif() + +function(_lg_expand_includes input include_dir output_var) + file(READ "${input}" content) + + string(REGEX MATCH + "(^|\n)#include[ \t]*\"([^\"]+)\"[ \t\r]*(\n|$)" include_match + "${content}") + while(include_match) + set(include_prefix "${CMAKE_MATCH_1}") + set(include_name "${CMAKE_MATCH_2}") + set(include_line "${include_match}") + set(include_path "${include_dir}/${include_name}") + if(NOT EXISTS "${include_path}") + message(FATAL_ERROR + "Unable to resolve ${include_name} included by ${input}") + endif() + + _lg_expand_includes("${include_path}" "${include_dir}" include_content) + if(NOT "${include_content}" MATCHES "\n$") + string(APPEND include_content "\n") + endif() + string(REPLACE "${include_line}" + "${include_prefix}${include_content}" content + "${content}") + string(REGEX MATCH + "(^|\n)#include[ \t]*\"([^\"]+)\"[ \t\r]*(\n|$)" include_match + "${content}") + endwhile() + + set(${output_var} "${content}" PARENT_SCOPE) +endfunction() + +if(LG_MAKE_OBJECT_ACTION STREQUAL "PREPROCESS") + if(NOT DEFINED LG_MAKE_OBJECT_INPUT OR + NOT DEFINED LG_MAKE_OBJECT_OUTPUT OR + NOT DEFINED LG_MAKE_OBJECT_INCLUDE_DIR) + message(FATAL_ERROR + "The preprocess action requires input, output, and include directory") + endif() + + _lg_expand_includes( + "${LG_MAKE_OBJECT_INPUT}" + "${LG_MAKE_OBJECT_INCLUDE_DIR}" + LG_MAKE_OBJECT_DATA) + get_filename_component(LG_MAKE_OBJECT_OUTPUT_DIR + "${LG_MAKE_OBJECT_OUTPUT}" DIRECTORY) + file(MAKE_DIRECTORY "${LG_MAKE_OBJECT_OUTPUT_DIR}") + file(WRITE "${LG_MAKE_OBJECT_OUTPUT}" "${LG_MAKE_OBJECT_DATA}") + return() +endif() + +if(LG_MAKE_OBJECT_ACTION STREQUAL "DEFINES") + if(NOT DEFINED LG_MAKE_OBJECT_INPUT OR + NOT DEFINED LG_MAKE_OBJECT_OUTPUT) + message(FATAL_ERROR "The defines action requires input and output") + endif() + + file(STRINGS "${LG_MAKE_OBJECT_INPUT}" LG_MAKE_OBJECT_DEFINES + REGEX "^#define") + set(LG_MAKE_OBJECT_DATA "") + foreach(LG_MAKE_OBJECT_DEFINE IN LISTS LG_MAKE_OBJECT_DEFINES) + string(APPEND LG_MAKE_OBJECT_DATA "${LG_MAKE_OBJECT_DEFINE}\n") + endforeach() + get_filename_component(LG_MAKE_OBJECT_OUTPUT_DIR + "${LG_MAKE_OBJECT_OUTPUT}" DIRECTORY) + file(MAKE_DIRECTORY "${LG_MAKE_OBJECT_OUTPUT_DIR}") + file(WRITE "${LG_MAKE_OBJECT_OUTPUT}" "${LG_MAKE_OBJECT_DATA}") + return() +endif() + +set(LG_MAKE_OBJECT_SCRIPT "${CMAKE_CURRENT_LIST_FILE}") + function(make_object out_var) set(result) set(result_h) foreach(in_f ${ARGN}) - file(RELATIVE_PATH out_f ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}") - set(out_h "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.h") - set(out_f "${out_f}.o") + file(RELATIVE_PATH out_f ${CMAKE_CURRENT_SOURCE_DIR} + "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}") + set(in_path "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}") + set(out_h "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.h") + set(out_c "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.c") string(REGEX REPLACE "[/.-]" "_" sym_in "${in_f}") - add_custom_command(OUTPUT ${out_f} - COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/${in_f} ${out_f}.tmp - COMMAND dd if=/dev/zero bs=1 count=1 of=${out_f}.tmp oflag=append conv=notrunc status=none - COMMAND ${CMAKE_LINKER} -r -b binary -o ${out_f} ${out_f}.tmp - COMMAND ${CMAKE_OBJCOPY} --rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA ${out_f} ${out_f} - COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_o_tmp_start=b_${sym_in} ${out_f} ${out_f} - COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_o_tmp_end=b_${sym_in}_end ${out_f} ${out_f} - COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_o_tmp_size=b_${sym_in}_size ${out_f} ${out_f} - COMMAND ${CMAKE_OBJCOPY} --strip-symbol _binary_${sym_in}_size ${out_f} ${out_f} - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}" - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Creating object from ${in_f}" + add_custom_command( + OUTPUT "${out_c}" "${out_h}" + COMMAND "${CMAKE_COMMAND}" + "-DLG_MAKE_OBJECT_ACTION=EMBED" + "-DLG_MAKE_OBJECT_INPUT=${in_path}" + "-DLG_MAKE_OBJECT_SOURCE=${out_c}" + "-DLG_MAKE_OBJECT_HEADER=${out_h}" + "-DLG_MAKE_OBJECT_SYMBOL=b_${sym_in}" + -P "${LG_MAKE_OBJECT_SCRIPT}" + DEPENDS "${in_path}" "${LG_MAKE_OBJECT_SCRIPT}" + COMMENT "Embedding ${in_f}" VERBATIM ) - file(WRITE ${out_h} "extern const char b_${sym_in}[];\n") - file(APPEND ${out_h} "extern const char b_${sym_in}_end[];\n") - file(APPEND ${out_h} "#define b_${sym_in}_size (b_${sym_in}_end - b_${sym_in})\n") - - get_filename_component(h_dir ${out_h} DIRECTORY) - list(APPEND result_h ${h_dir}) - list(APPEND result ${out_f}) + get_filename_component(h_dir "${out_h}" DIRECTORY) + list(APPEND result_h "${h_dir}") + list(APPEND result "${out_c}") endforeach() list(REMOVE_DUPLICATES result_h) @@ -38,9 +160,15 @@ endfunction() function(make_defines in_file out_file) file(RELATIVE_PATH rel_f ${CMAKE_CURRENT_SOURCE_DIR} "${in_file}") - add_custom_command(OUTPUT ${out_file} - COMMAND grep "^#define" "${in_file}" > "${out_file}" - DEPENDS ${in_file} - COMMENT "Creating #defines from ${rel_f}" + add_custom_command( + OUTPUT "${out_file}" + COMMAND "${CMAKE_COMMAND}" + "-DLG_MAKE_OBJECT_ACTION=DEFINES" + "-DLG_MAKE_OBJECT_INPUT=${in_file}" + "-DLG_MAKE_OBJECT_OUTPUT=${out_file}" + -P "${LG_MAKE_OBJECT_SCRIPT}" + DEPENDS "${in_file}" "${LG_MAKE_OBJECT_SCRIPT}" + COMMENT "Extracting definitions from ${rel_f}" + VERBATIM ) endfunction() diff --git a/cmake/MsQuic.cmake b/cmake/MsQuic.cmake index 23041178..1a877735 100644 --- a/cmake/MsQuic.cmake +++ b/cmake/MsQuic.cmake @@ -77,7 +77,6 @@ function(lg_add_static_msquic) set(LG_MSQUIC_BINARY_DIR "${CMAKE_BINARY_DIR}/MsQuic") set(LG_MSQUIC_SYSTEM_LIBRARIES ws2_32 - schannel ntdll bcrypt ncrypt @@ -106,8 +105,41 @@ function(lg_add_static_msquic) endif() if(MSVC) + if(NOT CMAKE_GENERATOR MATCHES "^Ninja") + message(FATAL_ERROR + "The native Windows MsQuic build requires Ninja or Ninja " + "Multi-Config") + endif() + if(NOT EXISTS + "${LG_MSQUIC_SOURCE_DIR}/submodules/openssl/Configure") + message(FATAL_ERROR + "The MsQuic OpenSSL submodule is not initialized; run " + "git submodule update --init --recursive") + endif() + + find_program(LG_MSQUIC_CL NAMES cl REQUIRED) + find_program(LG_MSQUIC_NMAKE NAMES nmake REQUIRED) + find_program(LG_MSQUIC_NASM NAMES nasm REQUIRED) + find_program(LG_MSQUIC_PERL NAMES perl REQUIRED) + list(APPEND LG_MSQUIC_CMAKE_ARGS - "-DQUIC_TLS_LIB=schannel") + "-DQUIC_TLS_LIB=openssl" + "-DQUIC_USE_EXTERNAL_OPENSSL=OFF" + "-DQUIC_USE_SYSTEM_LIBCRYPTO=OFF" + "-DQUIC_OPENSSL_INCLUDE_DIR=" + "-DQUIC_OPENSSL_LIB_DIR=" + "-DQUIC_OPENSSL_ROOT_DIR=" + "-DCMAKE_C_COMPILER=${LG_MSQUIC_CL}" + "-DCMAKE_CXX_COMPILER=${LG_MSQUIC_CL}") + if(NOT CMAKE_CONFIGURATION_TYPES) + list(APPEND LG_MSQUIC_CMAKE_ARGS + "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") + endif() + list(APPEND LG_MSQUIC_SYSTEM_LIBRARIES + gdi32 + user32 + wbemuuid + winmm) set(LG_MSQUIC_RELEASE_LIBRARY "${LG_MSQUIC_BINARY_DIR}/bin/Release/msquic.lib") endif() diff --git a/cmake/OptimizeForNative.cmake b/cmake/OptimizeForNative.cmake index ec47afe6..26abd3e0 100644 --- a/cmake/OptimizeForNative.cmake +++ b/cmake/OptimizeForNative.cmake @@ -2,18 +2,36 @@ set(OPTIMIZE_FOR_NATIVE AUTO CACHE STRING "Build with -march=native") set_property(CACHE OPTIMIZE_FOR_NATIVE PROPERTY STRINGS AUTO ON OFF) set(OPTIMIZE_FOR_NATIVE_ARCH "none") -if(OPTIMIZE_FOR_NATIVE STREQUAL "ON" OR (OPTIMIZE_FOR_NATIVE STREQUAL "AUTO" AND OPTIMIZE_FOR_NATIVE_DEFAULT)) - CHECK_C_COMPILER_FLAG("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE) +if(MSVC AND CMAKE_C_COMPILER_ID STREQUAL "Clang") + set(OPTIMIZE_FOR_NATIVE_PREFIX "/clang:-march=") +elseif(MSVC) + set(OPTIMIZE_FOR_NATIVE_PREFIX "") +else() + set(OPTIMIZE_FOR_NATIVE_PREFIX "-march=") +endif() + +if(NOT OPTIMIZE_FOR_NATIVE_PREFIX) + if(OPTIMIZE_FOR_NATIVE STREQUAL "ON" OR + NOT OPTIMIZE_FOR_NATIVE MATCHES "^(AUTO|OFF)$") + message(WARNING + "OPTIMIZE_FOR_NATIVE is not supported by ${CMAKE_C_COMPILER_ID}") + endif() +elseif(OPTIMIZE_FOR_NATIVE STREQUAL "ON" OR + (OPTIMIZE_FOR_NATIVE STREQUAL "AUTO" AND OPTIMIZE_FOR_NATIVE_DEFAULT)) + CHECK_C_COMPILER_FLAG("${OPTIMIZE_FOR_NATIVE_PREFIX}native" + COMPILER_SUPPORTS_MARCH_NATIVE) if(COMPILER_SUPPORTS_MARCH_NATIVE) set(OPTIMIZE_FOR_NATIVE_ARCH "native") set(OPTIMIZE_FOR_NATIVE_ON ON) endif() elseif(OPTIMIZE_FOR_NATIVE STREQUAL "AUTO" OR OPTIMIZE_FOR_NATIVE STREQUAL "OFF") - CHECK_C_COMPILER_FLAG("-march=x86-64-v2" COMPILER_SUPPORTS_MARCH_X86_64_V2) + CHECK_C_COMPILER_FLAG("${OPTIMIZE_FOR_NATIVE_PREFIX}x86-64-v2" + COMPILER_SUPPORTS_MARCH_X86_64_V2) if(COMPILER_SUPPORTS_MARCH_X86_64_V2) set(OPTIMIZE_FOR_NATIVE_ARCH "x86-64-v2") else() - CHECK_C_COMPILER_FLAG("-march=nehalem" COMPILER_SUPPORTS_MARCH_NEHALEM) + CHECK_C_COMPILER_FLAG("${OPTIMIZE_FOR_NATIVE_PREFIX}nehalem" + COMPILER_SUPPORTS_MARCH_NEHALEM) if(COMPILER_SUPPORTS_MARCH_NEHALEM) set(OPTIMIZE_FOR_NATIVE_ARCH "nehalem") endif() @@ -24,7 +42,8 @@ else() endif() if(NOT OPTIMIZE_FOR_NATIVE_ARCH STREQUAL "none") - add_compile_options("-march=${OPTIMIZE_FOR_NATIVE_ARCH}") + add_compile_options( + "${OPTIMIZE_FOR_NATIVE_PREFIX}${OPTIMIZE_FOR_NATIVE_ARCH}") endif() add_feature_info(OPTIMIZE_FOR_NATIVE OPTIMIZE_FOR_NATIVE_ON "Optimized for ${OPTIMIZE_FOR_NATIVE_ARCH}") diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 94386ea3..4da705cb 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -7,7 +7,9 @@ include_directories( ${PROJECT_SOURCE_DIR}/include ) -add_definitions(-D_GNU_SOURCE) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_compile_definitions(_GNU_SOURCE) +endif() if (CMAKE_C_COMPILER_ID STREQUAL "Clang") add_compile_options( diff --git a/common/include/common/stringutils.h b/common/include/common/stringutils.h index 6cc85ecf..241bce74 100644 --- a/common/include/common/stringutils.h +++ b/common/include/common/stringutils.h @@ -23,6 +23,7 @@ #include #include +#include // vsprintf but with buffer allocation int valloc_sprintf(char ** str, const char * format, va_list ap) @@ -35,8 +36,14 @@ int alloc_sprintf(char ** str, const char * format, ...) // Find value in a list separated by delimiter. bool str_containsValue(const char * list, char delimiter, const char * value); +// Portable case-insensitive string comparison. +int lg_strcasecmp(const char * left, const char * right); + +// Portable reentrant tokenization. +char * lg_strtok_r(char * str, const char * delimiters, char ** state); + // Local implementation of strdup -char * lg_strdup(const char *s); +char * lg_strdup(const char * s); // copy a potentially unterminated string into a null-terminated buffer void str_copy(char * dst, size_t dstSize, const char * src, size_t srcSize); diff --git a/common/src/framebuffer.c b/common/src/framebuffer.c index 58383d5e..280dfc5d 100644 --- a/common/src/framebuffer.c +++ b/common/src/framebuffer.c @@ -32,7 +32,6 @@ #include #include #include -#include static bool framebuffer_size_mul(size_t a, size_t b, size_t * result) { @@ -65,7 +64,7 @@ bool framebuffer_wait_timed(const KVMFRFrameBuffer * frame, size_t size, *waitTimeNs += nanotime() - waitStart; return false; } - usleep(1); + nsleep(1000); } } diff --git a/common/src/option.c b/common/src/option.c index f1df1285..0d266a11 100644 --- a/common/src/option.c +++ b/common/src/option.c @@ -65,10 +65,10 @@ static bool int_parser(struct Option * opt, const char * str) static bool bool_parser(struct Option * opt, const char * str) { opt->value.x_bool = - strcasecmp(str, "1" ) == 0 || - strcasecmp(str, "on" ) == 0 || - strcasecmp(str, "yes" ) == 0 || - strcasecmp(str, "true") == 0; + lg_strcasecmp(str, "1" ) == 0 || + lg_strcasecmp(str, "on" ) == 0 || + lg_strcasecmp(str, "yes" ) == 0 || + lg_strcasecmp(str, "true") == 0; return true; } @@ -343,13 +343,14 @@ bool option_parse(int argc, char * argv[]) // emulate getopt for backwards compatability if (argv[a][0] == '-') { - if (strcasecmp(argv[a], "-h") == 0 || strcasecmp(argv[a], "--help") == 0) + if (lg_strcasecmp(argv[a], "-h") == 0 || + lg_strcasecmp(argv[a], "--help") == 0) { state.doHelp = DOHELP_MODE_YES; continue; } - if (strcasecmp(argv[a], "--rst-help") == 0) + if (lg_strcasecmp(argv[a], "--rst-help") == 0) { state.doHelp = DOHELP_MODE_RST; continue; @@ -925,13 +926,14 @@ struct Option * option_get(const char * module, const char * name) for(int i = 0; i < state.oCount; ++i) { struct Option * o = state.options[i]; - if ((strcasecmp(o->module, module) == 0) && (strcasecmp(o->name, name) == 0)) + if ((lg_strcasecmp(o->module, module) == 0) && + (lg_strcasecmp(o->name, name) == 0)) return o; // check for usage of renamed options if (o->old_module && o->old_name && - (strcasecmp(o->old_module, module) == 0) && - (strcasecmp(o->old_name , name) == 0)) + (lg_strcasecmp(o->old_module, module) == 0) && + (lg_strcasecmp(o->old_name , name) == 0)) { // not fatal, but be noisy DEBUG_WARN( diff --git a/common/src/platform/windows/CMakeLists.txt b/common/src/platform/windows/CMakeLists.txt index 36c2afce..15ff15c3 100644 --- a/common/src/platform/windows/CMakeLists.txt +++ b/common/src/platform/windows/CMakeLists.txt @@ -8,9 +8,9 @@ include_directories( # allow use of functions for Windows 7 or later add_compile_definitions(WINVER=0x0601 _WIN32_WINNT=0x0601) -add_compile_definitions(__USE_MINGW_ANSI_STDIO=0) +if(MINGW) + add_compile_definitions(__USE_MINGW_ANSI_STDIO=0) -if (MINGW) # Build our own ntdll.dll import library # This tricks MinGW into not linking stuff like memcpy from ntdll.dll instead of mscvrt.dll if(NOT CMAKE_DLLTOOL) diff --git a/common/src/stringutils.c b/common/src/stringutils.c index 4526e274..811fd1b9 100644 --- a/common/src/stringutils.c +++ b/common/src/stringutils.c @@ -19,6 +19,7 @@ */ #include +#include #include #include #include @@ -92,7 +93,48 @@ bool str_containsValue(const char * list, char delimiter, const char * value) return false; } -char * lg_strdup(const char *s) +int lg_strcasecmp(const char * left, const char * right) +{ + while (*left && *right) + { + const int leftChar = tolower((unsigned char)*left); + const int rightChar = tolower((unsigned char)*right); + if (leftChar != rightChar) + return leftChar - rightChar; + + ++left; + ++right; + } + + return (unsigned char)*left - (unsigned char)*right; +} + +char * lg_strtok_r(char * str, const char * delimiters, char ** state) +{ + char * token = str ? str : *state; + if (!token) + return NULL; + + token += strspn(token, delimiters); + if (!*token) + { + *state = NULL; + return NULL; + } + + char * end = token + strcspn(token, delimiters); + if (*end) + { + *end = '\0'; + *state = end + 1; + } + else + *state = NULL; + + return token; +} + +char * lg_strdup(const char * s) { if (!s) { @@ -100,8 +142,8 @@ char * lg_strdup(const char *s) return NULL; } - const ssize_t len = strlen(s) + 1; - char * out = malloc(len); + const size_t len = strlen(s) + 1; + char * out = malloc(len); if (!out) { errno = ENOMEM; diff --git a/doc/build_client_windows.rst b/doc/build_client_windows.rst new file mode 100644 index 00000000..a84d3a38 --- /dev/null +++ b/doc/build_client_windows.rst @@ -0,0 +1,44 @@ +.. _build_client_windows: + +Build the Windows client +######################## + +The Windows client uses ``clang-cl`` and the MSVC ABI. ANGLE continues to +provide EGL and OpenGL ES, while MsQuic and its bundled OpenSSL are linked +statically. + +Requirements +------------ + +Install the following tools: + +- Visual Studio 2022 Build Tools with the Desktop development with C++ + workload, Clang tools for Windows, and a Windows SDK +- CMake 3.21 or newer +- Ninja +- Git +- Perl +- NASM +- vcpkg + +Set ``VCPKG_ROOT`` to the vcpkg checkout. The client manifest installs native +MSVC-ABI builds of ANGLE and libsamplerate automatically. + +Building +-------- + +Open an x64 Native Tools command prompt or Developer PowerShell, then run: + +.. code:: powershell + + git submodule update --init --recursive + cd client + cmake --preset windows-clangcl + cmake --build --preset windows-clangcl-release + +The executable is written beneath ``client/build/windows-clangcl``. The +vcpkg toolchain copies the required ANGLE and libsamplerate runtime DLLs +beside it. + +The native build deliberately does not consume MinGW ``.a`` or ``.dll.a`` +libraries. All statically linked code must use the MSVC ABI. diff --git a/doc/index.rst b/doc/index.rst index 4de3bdaa..fabcc7f0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -13,6 +13,7 @@ memory for frames, and a native Linux client for display and input. quickstart build + build_client_windows install usage diff --git a/doc/words.txt b/doc/words.txt index 217feba3..6d525243 100644 --- a/doc/words.txt +++ b/doc/words.txt @@ -1,3 +1,5 @@ +ABI +ANGLE Anime4K backend backends @@ -9,6 +11,7 @@ cadence cgroups checkboxes clang +clang-cl cmake codec compositor @@ -53,6 +56,7 @@ lexically libdecor libpipewire libpulse +libsamplerate libvirt linux LookingGlass @@ -65,9 +69,12 @@ mingw mipmapping modprobe mpv +MsQuic +MSVC msys multisampling namespace +NASM Navi NvFBC Nvidia @@ -123,6 +130,7 @@ upscaled upscaler upscales upscaling +vcpkg viewport virt VirtIO diff --git a/repos/gui/CMakeLists.txt b/repos/gui/CMakeLists.txt index d5c67967..c31608fe 100644 --- a/repos/gui/CMakeLists.txt +++ b/repos/gui/CMakeLists.txt @@ -12,15 +12,25 @@ add_library(lg_gui STATIC cimplot/implot/implot_items.cpp ) -if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") +if(MSVC) + target_compile_options(lg_gui PRIVATE /fp:precise) + + set_source_files_properties(cimgui/cimgui.cpp PROPERTIES + COMPILE_OPTIONS "/clang:-Wno-error=unused-function" + ) + set_source_files_properties(cimplot/cimplot.cpp PROPERTIES + COMPILE_OPTIONS + "/clang:-Wno-error=array-bounds;/clang:-Wno-error=unused-function" + ) +elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(lg_gui PRIVATE "-fno-fast-math") set_source_files_properties(cimgui/cimgui.cpp PROPERTIES - COMPILE_FLAGS "-Wno-error=unused-function" + COMPILE_OPTIONS "-Wno-error=unused-function" ) set_source_files_properties(cimplot/cimplot.cpp PROPERTIES - COMPILE_FLAGS - "-Wno-error=array-bounds -Wno-error=unused-function" + COMPILE_OPTIONS + "-Wno-error=array-bounds;-Wno-error=unused-function" ) endif()