From 4f82049e5749c101a1c57f6d90c91a3df6082ca3 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 31 Mar 2026 22:42:59 -0400 Subject: [PATCH 1/2] test: initial unit testing --- .github/workflows/ci.yml | 37 ++++++------- CMakeLists.txt | 10 ++-- CMakePresets.json | 29 +++++++++++ README.md | 35 ++++++++++++- src/nxdk/hal/debug.h | 4 +- src/nxdk/hal/video.h | 23 ++++++++- src/nxdk/hal/xbox.h | 6 ++- src/nxdk/windows.h | 7 ++- src/splash/splash_layout.cpp | 67 ++++++++++++++++++++++++ src/splash/splash_layout.h | 66 ++++++++++++++++++++++++ src/splash/splash_screen.cpp | 58 +++++++-------------- src/startup/video_mode.cpp | 53 ++++++++++++------- src/startup/video_mode.h | 21 ++++++++ tests/CMakeLists.txt | 80 +++++++++++------------------ tests/splash/splash_layout_test.cpp | 32 ++++++++++++ tests/startup/video_mode_test.cpp | 44 ++++++++++++++++ 16 files changed, 430 insertions(+), 142 deletions(-) create mode 100644 src/splash/splash_layout.cpp create mode 100644 src/splash/splash_layout.h create mode 100644 tests/splash/splash_layout_test.cpp create mode 100644 tests/startup/video_mode_test.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 256e4b8..61ebcd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,8 @@ jobs: defaults: run: shell: ${{ matrix.shell }} + env: + CMAKE_TEST: cmake-build-host-tests steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -135,16 +137,6 @@ jobs: chmod +x build.sh ./build.sh - # TODO: Tests are not building properly... - # the tests target should not be cross compiled, so we can run it on the runner - # https://stackoverflow.com/a/64335131/11214013 - # echo "--- Building tests ---" - # cmake \ - # -B build/tests \ - # -S . \ - # -DBUILD_TESTS=ON - # cmake --build build/tests --target test_moonlight - # recursively list all files in build directory ls -R build @@ -159,11 +151,16 @@ jobs: name: moonlight-${{ runner.os }} path: artifacts/ + - name: Configure host tests + run: cmake -S . -B ${CMAKE_TEST} -DBUILD_TESTS=ON -DBUILD_DOCS=OFF + + - name: Build host tests + run: cmake --build ${CMAKE_TEST} --target test_moonlight + - name: Run tests id: test - if: false && always() - working-directory: build/tests - run: ./test_moonlight --gtest_color=yes --gtest_output=xml:test_results.xml + working-directory: ${{ env.CMAKE_TEST }} + run: ./tests/test_moonlight --gtest_color=yes --gtest_output=xml:tests/test_results.xml - name: Generate gcov report id: test_report @@ -171,22 +168,22 @@ jobs: if: >- always() && (steps.test.outcome == 'success' || steps.test.outcome == 'failure') - working-directory: build run: | - ${{ steps.python-path.outputs.python-path }} -m pip install gcovr - ${{ steps.python-path.outputs.python-path }} -m gcovr . -r ../src \ + ${{ steps.python-path.outputs.python-path }} -m pip install ".[test]" + ${{ steps.python-path.outputs.python-path }} -m gcovr ${CMAKE_TEST} -r . \ + --filter src \ --exclude-noncode-lines \ --exclude-throw-branches \ --exclude-unreachable-branches \ --verbose \ --xml-pretty \ - -o coverage.xml + -o "${CMAKE_TEST}/coverage.xml" - name: Debug coverage file if: >- always() && steps.test_report.outcome == 'success' - run: cat build/coverage.xml + run: cat "${CMAKE_TEST}/coverage.xml" - name: Upload coverage # any except canceled or skipped @@ -198,7 +195,7 @@ jobs: with: disable_search: true fail_ci_if_error: true - files: ./build/coverage.xml + files: ./${{ env.CMAKE_TEST }}/coverage.xml flags: ${{ runner.os }} report_type: coverage token: ${{ secrets.CODECOV_TOKEN }} @@ -214,7 +211,7 @@ jobs: with: disable_search: true fail_ci_if_error: true - files: ./build/tests/test_results.xml + files: ./${{ env.CMAKE_TEST }}/tests/test_results.xml flags: ${{ runner.os }} handle_no_reports_found: true report_type: test_results diff --git a/CMakeLists.txt b/CMakeLists.txt index 224b727..fb1f4d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,11 +68,6 @@ set(MOONLIGHT_EXTERNAL_LIBRARIES set(MOONLIGHT_INCLUDE_DIRS ) set(MOONLIGHT_COMPILE_OPTIONS "-Wno-builtin-macro-redefined") -set(MOONLIGHT_DEFINITIONS "-DXBOX -DNXDK") - -# Stop lots of warning spam -add_compile_options(${MOONLIGHT_COMPILE_OPTIONS}) -add_definitions(${MOONLIGHT_DEFINITIONS}) # submodules # moonlight common library @@ -98,8 +93,8 @@ include_directories(SYSTEM target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC ${MOONLIGHT_EXTERNAL_LIBRARIES} ) - -target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE "") +target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE ${MOONLIGHT_COMPILE_OPTIONS}) +target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE XBOX NXDK) add_dependencies(${CMAKE_PROJECT_NAME} moonlight-common-c) # @@ -113,6 +108,7 @@ endif() # tests # if(BUILD_TESTS) + enable_testing() add_subdirectory(tests EXCLUDE_FROM_ALL) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 0e7764f..cb0db9a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -39,6 +39,19 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + }, + { + "name": "host-tests (mingw64)", + "displayName": "Host Tests (mingw64)", + "description": "Build host-native googletest targets under MSYS2/mingw64", + "generator": "Unix Makefiles", + "cmakeExecutable": "C:/msys64/usr/bin/cmake", + "binaryDir": "${sourceDir}/cmake-build-host-tests", + "cacheVariables": { + "BUILD_DOCS": "OFF", + "BUILD_TESTS": "ON", + "CMAKE_BUILD_TYPE": "Debug" + } } ], "buildPresets": [ @@ -49,6 +62,22 @@ { "name": "nxdk-debug (mingw64)", "configurePreset": "nxdk-debug (mingw64)" + }, + { + "name": "host-tests (mingw64)", + "configurePreset": "host-tests (mingw64)", + "targets": [ + "test_moonlight" + ] + } + ], + "testPresets": [ + { + "name": "host-tests (mingw64)", + "configurePreset": "host-tests (mingw64)", + "output": { + "outputOnFailure": true + } } ] } diff --git a/README.md b/README.md index d3e38b2..b6e03c0 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![GitHub stars](https://img.shields.io/github/stars/lizardbyte/moonlight-xboxog.svg?logo=github&style=for-the-badge)](https://github.com/LizardByte/Moonlight-XboxOG) [![GitHub Releases](https://img.shields.io/github/downloads/lizardbyte/moonlight-xboxog/total.svg?style=for-the-badge&logo=github)](https://github.com/LizardByte/Moonlight-XboxOG/releases/latest) [![GitHub Workflow Status (CI)](https://img.shields.io/github/actions/workflow/status/lizardbyte/moonlight-xboxog/ci.yml.svg?branch=master&label=CI%20build&logo=github&style=for-the-badge)](https://github.com/LizardByte/Moonlight-XboxOG/actions/workflows/CI.yml?query=branch%3Amaster) +[![Codecov](https://img.shields.io/codecov/c/gh/LizardByte/Moonlight-XboxOG?token=DoIh5pkEzA&style=for-the-badge&logo=codecov&label=codecov)](https://codecov.io/gh/LizardByte/Moonlight-XboxOG) Port of Moonlight for the Original Xbox. Unlikely to ever actually work. Do NOT use! @@ -148,6 +149,36 @@ build-mingw64.bat ./build-mingw64.sh ``` +### Host-native unit tests + +The Xbox executable cannot run directly on Windows, macOS, or Linux, so unit tests are built as a separate host-native target. Keep Xbox runtime code thin and move logic you want to test into platform-neutral sources that can be linked into `test_moonlight`. + +#### Windows via MSYS2/mingw64 + +From `cmd.exe`, configure, build, and run the host tests with: + +```bat +C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64 -c "cd /c/Users/%USERNAME%/Dev/git/Moonlight-XboxOG && cmake --preset \"host-tests (mingw64)\" && cmake --build --preset \"host-tests (mingw64)\" && ctest --preset \"host-tests (mingw64)\"" +``` + +If you are already inside a `mingw64` shell, the equivalent commands are: + +```bash +cmake --preset "host-tests (mingw64)" +cmake --build --preset "host-tests (mingw64)" +ctest --preset "host-tests (mingw64)" +``` + +#### Linux or macOS + +```bash +cmake -S . -B cmake-build-host-tests -DBUILD_TESTS=ON -DBUILD_DOCS=OFF +cmake --build cmake-build-host-tests --target test_moonlight +ctest --test-dir cmake-build-host-tests --output-on-failure +``` + +Coverage should come from this host-native test build instead of the cross-compiled Xbox build. + ### CLion on Windows The repository now includes CLion-friendly nxdk wrapper scripts in `cmake/` plus shared run configurations in `.run/`. @@ -184,8 +215,8 @@ scripts\setup-xemu.cmd --skip-support-files - [x] Automatically run built xiso in Xemu - [x] Add unit testing framework - [x] Separate main build and unit test builds, due to cross compiling, see https://stackoverflow.com/a/64335131/11214013 - - [ ] Get tests to properly compile - - [ ] Enable codecov + - [x] Get tests to properly compile + - [x] Enable codecov - [x] Enable sonarcloud - [x] Build moonlight-common-c - [x] Build custom enet diff --git a/src/nxdk/hal/debug.h b/src/nxdk/hal/debug.h index 6fa14ab..19c5232 100644 --- a/src/nxdk/hal/debug.h +++ b/src/nxdk/hal/debug.h @@ -2,5 +2,7 @@ #if defined(NXDK) #include #else - #define debugPrint(...) printf(__VA_ARGS__) + #include + + #define debugPrint(...) std::printf(__VA_ARGS__) #endif diff --git a/src/nxdk/hal/video.h b/src/nxdk/hal/video.h index 62be38b..a6a2185 100644 --- a/src/nxdk/hal/video.h +++ b/src/nxdk/hal/video.h @@ -2,6 +2,25 @@ #if defined(NXDK) #include #else - #define XVideoSetMode(...) - #define XVideoWaitForVBlank(...) SDL_Delay(1000 / 60 - 1) +struct VIDEO_MODE { + int width; + int height; + int bpp; + int refresh; +}; + +inline constexpr int REFRESH_DEFAULT = 0; +inline constexpr unsigned long VIDEO_WIDESCREEN = 0x00000001UL; + +inline bool XVideoListModes(VIDEO_MODE *, int, int, void **) { + return false; +} + +inline unsigned long XVideoGetEncoderSettings() { + return 0; +} + +inline void XVideoSetMode(int, int, int, int) {} + +inline void XVideoWaitForVBlank() {} #endif diff --git a/src/nxdk/hal/xbox.h b/src/nxdk/hal/xbox.h index 3c290d8..3889667 100644 --- a/src/nxdk/hal/xbox.h +++ b/src/nxdk/hal/xbox.h @@ -2,5 +2,9 @@ #if defined(NXDK) #include #else - #define XReboot(...) exit(555) + #include + +[[noreturn]] inline void XReboot() { + std::exit(555); +} #endif diff --git a/src/nxdk/windows.h b/src/nxdk/windows.h index 9fe6c5c..3e15eb2 100644 --- a/src/nxdk/windows.h +++ b/src/nxdk/windows.h @@ -2,5 +2,10 @@ #if defined(NXDK) #include #else - #define Sleep(x) SDL_Delay(x) + #include + #include + +inline void Sleep(unsigned long milliseconds) { + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); +} #endif diff --git a/src/splash/splash_layout.cpp b/src/splash/splash_layout.cpp new file mode 100644 index 0000000..0a76e4d --- /dev/null +++ b/src/splash/splash_layout.cpp @@ -0,0 +1,67 @@ +#include "src/splash/splash_layout.h" + +#include +#include + +namespace { + + constexpr float SPLASH_LOGO_MAX_WIDTH_RATIO = 0.72f; + constexpr float SPLASH_LOGO_MAX_HEIGHT_RATIO = 0.32f; + constexpr float SPLASH_ASPECT_RATIO_EPSILON = 0.05f; + + int clamp_scaled_dimension(float value) { + if (const auto scaledValue = static_cast(value); scaledValue > 0) { + return scaledValue; + } + + return 1; + } + +} // namespace + +namespace splash { + + float get_framebuffer_aspect_ratio(const VIDEO_MODE &videoMode) { + return static_cast(videoMode.width) / static_cast(videoMode.height); + } + + float get_display_aspect_ratio(const VIDEO_MODE &videoMode, unsigned long encoderSettings) { + const float framebufferAspectRatio = get_framebuffer_aspect_ratio(videoMode); + const float preferredDisplayAspectRatio = ((encoderSettings & VIDEO_WIDESCREEN) != 0UL) ? (16.0f / 9.0f) : (4.0f / 3.0f); + const bool isStandardDefinitionRaster = videoMode.height <= 576; + + if (const bool needsAspectCorrection = isStandardDefinitionRaster && std::fabs(framebufferAspectRatio - preferredDisplayAspectRatio) > SPLASH_ASPECT_RATIO_EPSILON) { + return preferredDisplayAspectRatio; + } + + return framebufferAspectRatio; + } + + float get_logo_width_aspect_correction(const VIDEO_MODE &videoMode, unsigned long encoderSettings) { + return get_framebuffer_aspect_ratio(videoMode) / get_display_aspect_ratio(videoMode, encoderSettings); + } + + SplashLayout calculate_logo_destination( + int screenWidth, + int screenHeight, + int logoWidth, + int logoHeight, + const VIDEO_MODE &videoMode, + unsigned long encoderSettings + ) { + const float correctedLogoWidth = static_cast(logoWidth) * get_logo_width_aspect_correction(videoMode, encoderSettings); + const float maxLogoWidth = static_cast(screenWidth) * SPLASH_LOGO_MAX_WIDTH_RATIO; + const float maxLogoHeight = static_cast(screenHeight) * SPLASH_LOGO_MAX_HEIGHT_RATIO; + const float widthScale = maxLogoWidth / correctedLogoWidth; + const float heightScale = maxLogoHeight / static_cast(logoHeight); + const float scale = std::min(widthScale, heightScale); + + SplashLayout layout {}; + layout.width = clamp_scaled_dimension(correctedLogoWidth * scale); + layout.height = clamp_scaled_dimension(static_cast(logoHeight) * scale); + layout.x = (screenWidth - layout.width) / 2; + layout.y = (screenHeight - layout.height) / 2; + return layout; + } + +} // namespace splash diff --git a/src/splash/splash_layout.h b/src/splash/splash_layout.h new file mode 100644 index 0000000..a49e8bf --- /dev/null +++ b/src/splash/splash_layout.h @@ -0,0 +1,66 @@ +#pragma once + +#include "src/nxdk/hal/video.h" + +namespace splash { + + /** + * @brief Rectangle used for splash image placement. + */ + struct SplashLayout { + int x; + int y; + int width; + int height; + }; + + /** + * @brief Return the framebuffer aspect ratio for a video mode. + * + * @param videoMode The video mode being rendered. + * @return The framebuffer width divided by height. + */ + float get_framebuffer_aspect_ratio(const VIDEO_MODE &videoMode); + + /** + * @brief Return the display aspect ratio after encoder corrections. + * + * Standard-definition framebuffers may need to be displayed with a corrected + * aspect ratio based on the active encoder settings. + * + * @param videoMode The video mode being rendered. + * @param encoderSettings The value returned by XVideoGetEncoderSettings(). + * @return The effective display aspect ratio. + */ + float get_display_aspect_ratio(const VIDEO_MODE &videoMode, unsigned long encoderSettings); + + /** + * @brief Return the width correction factor applied before scaling the logo. + * + * @param videoMode The video mode being rendered. + * @param encoderSettings The value returned by XVideoGetEncoderSettings(). + * @return The correction multiplier applied to the source logo width. + */ + float get_logo_width_aspect_correction(const VIDEO_MODE &videoMode, unsigned long encoderSettings); + + /** + * @brief Calculate the centered destination rectangle for the splash logo. + * + * @param screenWidth Width of the destination surface. + * @param screenHeight Height of the destination surface. + * @param logoWidth Width of the source logo. + * @param logoHeight Height of the source logo. + * @param videoMode Active video mode. + * @param encoderSettings The value returned by XVideoGetEncoderSettings(). + * @return The scaled and centered destination rectangle. + */ + SplashLayout calculate_logo_destination( + int screenWidth, + int screenHeight, + int logoWidth, + int logoHeight, + const VIDEO_MODE &videoMode, + unsigned long encoderSettings + ); + +} // namespace splash diff --git a/src/splash/splash_screen.cpp b/src/splash/splash_screen.cpp index cf9b12d..06bbf82 100644 --- a/src/splash/splash_screen.cpp +++ b/src/splash/splash_screen.cpp @@ -17,24 +17,22 @@ #include "src/nxdk/hal/xbox.h" #include "src/nxdk/windows.h" #include "src/os.h" +#include "src/splash/splash_layout.h" namespace { constexpr Uint8 SPLASH_BACKGROUND_RED = 0x2A; constexpr Uint8 SPLASH_BACKGROUND_GREEN = 0x2D; constexpr Uint8 SPLASH_BACKGROUND_BLUE = 0x30; - constexpr float SPLASH_LOGO_MAX_WIDTH_RATIO = 0.72f; - constexpr float SPLASH_LOGO_MAX_HEIGHT_RATIO = 0.32f; - constexpr float SPLASH_ASPECT_RATIO_EPSILON = 0.05f; - [[noreturn]] void printSDLErrorAndReboot() { + void printSDLErrorAndReboot() { debugPrint("SDL_Error: %s\n", SDL_GetError()); debugPrint("Rebooting in 5 seconds.\n"); Sleep(5000); XReboot(); } - [[noreturn]] void printIMGErrorAndReboot() { + void printIMGErrorAndReboot() { debugPrint("SDL_Image Error: %s\n", IMG_GetError()); debugPrint("Rebooting in 5 seconds.\n"); Sleep(5000); @@ -45,28 +43,6 @@ namespace { return std::string(DATA_PATH) + "assets" + PATH_SEP + assetName; } - float getFramebufferAspectRatio(const VIDEO_MODE &videoMode) { - return static_cast(videoMode.width) / static_cast(videoMode.height); - } - - float getDisplayAspectRatio(const VIDEO_MODE &videoMode) { - const float framebufferAspectRatio = getFramebufferAspectRatio(videoMode); - const DWORD encoderSettings = XVideoGetEncoderSettings(); - const float preferredDisplayAspectRatio = ((encoderSettings & VIDEO_WIDESCREEN) != 0) ? (16.0f / 9.0f) : (4.0f / 3.0f); - const bool isStandardDefinitionRaster = videoMode.height <= 576; - const bool needsAspectCorrection = isStandardDefinitionRaster && std::fabs(framebufferAspectRatio - preferredDisplayAspectRatio) > SPLASH_ASPECT_RATIO_EPSILON; - - if (needsAspectCorrection) { - return preferredDisplayAspectRatio; - } - - return framebufferAspectRatio; - } - - float getLogoWidthAspectCorrection(const VIDEO_MODE &videoMode) { - return getFramebufferAspectRatio(videoMode) / getDisplayAspectRatio(videoMode); - } - SDL_Rect createCenteredRect(const SDL_Surface *screenSurface, int width, int height) { SDL_Rect destination {}; destination.w = width; @@ -77,16 +53,21 @@ namespace { } SDL_Rect calculateLogoDestination(const SDL_Surface *screenSurface, int logoWidth, int logoHeight, const VIDEO_MODE &videoMode) { - const float correctedLogoWidth = static_cast(logoWidth) * getLogoWidthAspectCorrection(videoMode); - const float maxLogoWidth = static_cast(screenSurface->w) * SPLASH_LOGO_MAX_WIDTH_RATIO; - const float maxLogoHeight = static_cast(screenSurface->h) * SPLASH_LOGO_MAX_HEIGHT_RATIO; - const float widthScale = maxLogoWidth / correctedLogoWidth; - const float heightScale = maxLogoHeight / static_cast(logoHeight); - const float scale = std::min(widthScale, heightScale); - const int scaledLogoWidth = static_cast(correctedLogoWidth * scale) > 0 ? static_cast(correctedLogoWidth * scale) : 1; - const float scaledLogoHeightFloat = static_cast(logoHeight) * scale; - const int scaledLogoHeight = static_cast(scaledLogoHeightFloat) > 0 ? static_cast(scaledLogoHeightFloat) : 1; - return createCenteredRect(screenSurface, scaledLogoWidth, scaledLogoHeight); + const splash::SplashLayout layout = splash::calculate_logo_destination( + screenSurface->w, + screenSurface->h, + logoWidth, + logoHeight, + videoMode, + XVideoGetEncoderSettings() + ); + + SDL_Rect destination {}; + destination.x = layout.x; + destination.y = layout.y; + destination.w = layout.width; + destination.h = layout.height; + return destination; } Uint32 readSurfacePixel(const SDL_Surface *surface, int x, int y) { @@ -311,8 +292,7 @@ namespace splash { } } - const Uint32 backgroundColor = SDL_MapRGB(screenSurface->format, SPLASH_BACKGROUND_RED, SPLASH_BACKGROUND_GREEN, SPLASH_BACKGROUND_BLUE); - if (SDL_FillRect(screenSurface, nullptr, backgroundColor) < 0) { + if (const Uint32 backgroundColor = SDL_MapRGB(screenSurface->format, SPLASH_BACKGROUND_RED, SPLASH_BACKGROUND_GREEN, SPLASH_BACKGROUND_BLUE); SDL_FillRect(screenSurface, nullptr, backgroundColor) < 0) { cleanupSplashScreen(window, imageSurface); printSDLErrorAndReboot(); } diff --git a/src/startup/video_mode.cpp b/src/startup/video_mode.cpp index 58bf5d1..347b58f 100644 --- a/src/startup/video_mode.cpp +++ b/src/startup/video_mode.cpp @@ -6,34 +6,49 @@ namespace startup { - VideoModeSelection select_best_video_mode(int bpp, int refresh) { - VideoModeSelection selection {}; - selection.bestVideoMode = {0, 0, 0, 0}; + bool is_preferred_video_mode(const VIDEO_MODE &candidateVideoMode, const VIDEO_MODE ¤tBestVideoMode) { + if (candidateVideoMode.height < currentBestVideoMode.height) { + return false; + } - VIDEO_MODE videoMode; - void *position = nullptr; - while (XVideoListModes(&videoMode, bpp, refresh, &position)) { - selection.availableVideoModes.push_back(videoMode); + if (candidateVideoMode.width < currentBestVideoMode.width) { + return false; + } - if (videoMode.height < selection.bestVideoMode.height) { - continue; - } + if (candidateVideoMode.bpp < currentBestVideoMode.bpp) { + return false; + } - if (videoMode.width < selection.bestVideoMode.width) { - continue; - } + if (candidateVideoMode.refresh < currentBestVideoMode.refresh) { + return false; + } - if (videoMode.bpp < selection.bestVideoMode.bpp) { - continue; - } + return true; + } + + VIDEO_MODE choose_best_video_mode(const std::vector &availableVideoModes) { + VIDEO_MODE bestVideoMode {0, 0, 0, 0}; - if (videoMode.refresh < selection.bestVideoMode.refresh) { - continue; + for (const VIDEO_MODE &videoMode : availableVideoModes) { + if (is_preferred_video_mode(videoMode, bestVideoMode)) { + bestVideoMode = videoMode; } + } - selection.bestVideoMode = videoMode; + return bestVideoMode; + } + + VideoModeSelection select_best_video_mode(int bpp, int refresh) { + VideoModeSelection selection {}; + + VIDEO_MODE videoMode; + void *position = nullptr; + while (XVideoListModes(&videoMode, bpp, refresh, &position)) { + selection.availableVideoModes.push_back(videoMode); } + selection.bestVideoMode = choose_best_video_mode(selection.availableVideoModes); + return selection; } diff --git a/src/startup/video_mode.h b/src/startup/video_mode.h index 4573300..87cb5cb 100644 --- a/src/startup/video_mode.h +++ b/src/startup/video_mode.h @@ -36,6 +36,27 @@ namespace startup { VIDEO_MODE bestVideoMode; }; + /** + * @brief Return whether a candidate mode should replace the current best mode. + * + * A candidate must be at least as good as the current best mode in width, + * height, color depth, and refresh rate. + * + * @param candidateVideoMode The mode being evaluated. + * @param currentBestVideoMode The currently selected best mode. + * @return true if the candidate should replace the current best mode. + */ + bool is_preferred_video_mode(const VIDEO_MODE &candidateVideoMode, const VIDEO_MODE ¤tBestVideoMode); + + /** + * @brief Choose the best video mode from an already collected list. + * + * @param availableVideoModes The list of candidate modes to evaluate. + * @return The preferred mode, or a default-initialized VIDEO_MODE when the + * input list is empty. + */ + VIDEO_MODE choose_best_video_mode(const std::vector &availableVideoModes); + /** * @brief Detect and choose the best available video mode. * diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c211570..3540f72 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,40 +1,21 @@ cmake_minimum_required(VERSION 3.13) # https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md#foundational-c-support -project(test_moonlight) +project(test_moonlight LANGUAGES CXX) -include_directories("${CMAKE_SOURCE_DIR}") -include_directories(SYSTEM ${MOONLIGHT_INCLUDE_DIRS}) - -# nxdk include dirs, these are set by the toolchain for ALL targets except this one since we don't use the toolchain -include_directories(SYSTEM - "${NXDK_DIR}/lib/libcxx/include" - "${NXDK_DIR}/lib" - "${NXDK_DIR}/lib/xboxrt/libc_extensions" - "${NXDK_DIR}/lib/pdclib/include" - "${NXDK_DIR}/lib/pdclib/platform/xbox/include" - "${NXDK_DIR}/lib/winapi" - "${NXDK_DIR}/lib/winapi/ws2_32" - "${NXDK_DIR}/lib/xboxrt/vcruntime" -) - -enable_testing() - -# Add GoogleTest directory to the project set(GTEST_SOURCE_DIR "${CMAKE_SOURCE_DIR}/third-party/googletest") -set(INSTALL_GTEST OFF) -set(INSTALL_GMOCK OFF) +set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) +set(INSTALL_GMOCK OFF CACHE BOOL "" FORCE) add_subdirectory("${GTEST_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/googletest" EXCLUDE_FROM_ALL) -include_directories(SYSTEM "${GTEST_SOURCE_DIR}/googletest/include" "${GTEST_SOURCE_DIR}") -# coverage -# https://gcovr.com/en/stable/guide/compiling.html#compiler-options -set(CMAKE_CXX_FLAGS "-fprofile-arcs -ftest-coverage -ggdb -O0") -set(CMAKE_C_FLAGS "-fprofile-arcs -ftest-coverage -ggdb -O0") +set(TEST_COVERAGE_COMPILE_OPTIONS) +set(TEST_COVERAGE_LINK_OPTIONS) +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + list(APPEND TEST_COVERAGE_COMPILE_OPTIONS -fprofile-arcs -ftest-coverage -ggdb -O0) + list(APPEND TEST_COVERAGE_LINK_OPTIONS -fprofile-arcs -ftest-coverage) +endif() -# if windows if (WIN32) - # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # cmake-lint: disable=C0103 endif () @@ -42,32 +23,31 @@ file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/*.h ${CMAKE_SOURCE_DIR}/tests/*.cpp) -# remove main.cpp from the list of sources -# TODO -# list(REMOVE_ITEM MOONLIGHT_SOURCES ${CMAKE_SOURCE_DIR}/src/main.cpp) - -# Stop lots of warning spam -add_compile_options(${MOONLIGHT_COMPILE_OPTIONS}) -add_definitions(${MOONLIGHT_DEFINITIONS}) +set(MOONLIGHT_TESTABLE_SOURCES + ${CMAKE_SOURCE_DIR}/src/splash/splash_layout.cpp + ${CMAKE_SOURCE_DIR}/src/startup/video_mode.cpp) add_executable(${PROJECT_NAME} ${TEST_SOURCES} - ${MOONLIGHT_SOURCES}) - -foreach(dep ${MOONLIGHT_TARGET_DEPENDENCIES}) - add_dependencies(${PROJECT_NAME} ${dep}) # compile these before Moonlight -endforeach() - -set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20) + ${MOONLIGHT_TESTABLE_SOURCES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) +target_include_directories(${PROJECT_NAME} + PRIVATE + ${CMAKE_SOURCE_DIR} + ${GTEST_SOURCE_DIR}/googletest/include + ${GTEST_SOURCE_DIR}/googlemock/include) target_link_libraries(${PROJECT_NAME} - ${MOONLIGHT_EXTERNAL_LIBRARIES} - gtest - gtest_main # if we use this we don't need our own main function -) -target_compile_definitions(${PROJECT_NAME} PUBLIC ${MOONLIGHT_DEFINITIONS} ${TEST_DEFINITIONS}) -target_compile_options(${PROJECT_NAME} PRIVATE $<$:${MOONLIGHT_COMPILE_OPTIONS}>) -target_link_options(${PROJECT_NAME} PRIVATE) + PRIVATE + gtest_main) +target_compile_options(${PROJECT_NAME} + PRIVATE + ${TEST_COVERAGE_COMPILE_OPTIONS}) +target_link_options(${PROJECT_NAME} + PRIVATE + ${TEST_COVERAGE_LINK_OPTIONS}) add_dependencies(${PROJECT_NAME} gtest gtest_main gmock gmock_main) -add_test(NAME ${PROJECT_NAME} COMMAND moonlight_test) +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/splash/splash_layout_test.cpp b/tests/splash/splash_layout_test.cpp new file mode 100644 index 0000000..5d6bead --- /dev/null +++ b/tests/splash/splash_layout_test.cpp @@ -0,0 +1,32 @@ +// test include +#include "src/splash/splash_layout.h" + +// lib includes +#include + +namespace { + + TEST(SplashLayoutTest, AppliesWidescreenCorrectionToStandardDefinitionModes) { + const VIDEO_MODE videoMode {720, 480, 32, 60}; + + EXPECT_NEAR(splash::get_display_aspect_ratio(videoMode, VIDEO_WIDESCREEN), 16.0f / 9.0f, 0.001f); + } + + TEST(SplashLayoutTest, KeepsFramebufferAspectRatioWhenCorrectionIsNotNeeded) { + const VIDEO_MODE videoMode {1280, 720, 32, 60}; + + EXPECT_NEAR(splash::get_display_aspect_ratio(videoMode, VIDEO_WIDESCREEN), 1280.0f / 720.0f, 0.001f); + } + + TEST(SplashLayoutTest, ScalesAndCentersTheLogoInsideTheConfiguredBounds) { + const VIDEO_MODE videoMode {640, 480, 32, 60}; + + const splash::SplashLayout layout = splash::calculate_logo_destination(640, 480, 1000, 200, videoMode, 0UL); + + EXPECT_EQ(layout.width, 460); + EXPECT_EQ(layout.height, 92); + EXPECT_EQ(layout.x, 90); + EXPECT_EQ(layout.y, 194); + } + +} // namespace diff --git a/tests/startup/video_mode_test.cpp b/tests/startup/video_mode_test.cpp new file mode 100644 index 0000000..9758cdc --- /dev/null +++ b/tests/startup/video_mode_test.cpp @@ -0,0 +1,44 @@ +// test includes +#include "src/startup/video_mode.h" + +// standard includes +#include + +// lib includes +#include + +namespace { + + TEST(VideoModeTest, ReturnsDefaultModeWhenNoModesAreAvailable) { + const VIDEO_MODE bestVideoMode = startup::choose_best_video_mode({}); + + EXPECT_EQ(bestVideoMode.width, 0); + EXPECT_EQ(bestVideoMode.height, 0); + EXPECT_EQ(bestVideoMode.bpp, 0); + EXPECT_EQ(bestVideoMode.refresh, 0); + } + + TEST(VideoModeTest, PrefersModesThatAreNotWorseInAnyDimension) { + const std::vector availableVideoModes = { + {640, 480, 16, 60}, + {640, 480, 32, 60}, + {720, 480, 32, 60}, + {720, 480, 32, 75}, + }; + + const VIDEO_MODE bestVideoMode = startup::choose_best_video_mode(availableVideoModes); + + EXPECT_EQ(bestVideoMode.width, 720); + EXPECT_EQ(bestVideoMode.height, 480); + EXPECT_EQ(bestVideoMode.bpp, 32); + EXPECT_EQ(bestVideoMode.refresh, 75); + } + + TEST(VideoModeTest, RejectsModesThatLoseWidthEvenWhenRefreshImproves) { + const VIDEO_MODE currentBestVideoMode {1280, 720, 32, 60}; + const VIDEO_MODE candidateVideoMode {1024, 720, 32, 120}; + + EXPECT_FALSE(startup::is_preferred_video_mode(candidateVideoMode, currentBestVideoMode)); + } + +} // namespace From 32c508940c4278e91527670aa994969d184f48c2 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:59:03 -0400 Subject: [PATCH 2/2] Switch to nxdk hal headers and update tests Replace local nxdk wrapper headers with direct nxdk includes and remove the deleted wrapper files. Add test support stubs (tests/support/hal/debug.h and tests/support/hal/video.h) and move the video HAL stub out of src to tests/support for unit builds; adjust a few constants/return types in the stub. Update CMakeLists to exclude runtime-only sources from testable sources, add tests/support to include dirs, and relocate unit tests under tests/unit. Misc: shorten Sleep in main from 10000ms to 4000ms and remove verbose debug prints when loading splash assets to reduce startup noise. --- src/main.cpp | 6 +++-- src/nxdk/hal/debug.h | 8 ------ src/nxdk/hal/video.h | 26 ------------------- src/nxdk/hal/xbox.h | 10 ------- src/nxdk/windows.h | 11 -------- src/os.h | 10 +++---- src/splash/splash_layout.h | 7 ++++- src/splash/splash_screen.cpp | 24 +++++++++++++---- src/splash/splash_screen.h | 4 +-- src/startup/memory_stats.cpp | 7 ++--- src/startup/video_mode.cpp | 4 +-- src/startup/video_mode.h | 4 +-- tests/CMakeLists.txt | 11 +++++--- tests/support/hal/debug.h | 5 ++++ tests/support/hal/video.h | 21 +++++++++++++++ .../{ => unit}/splash/splash_layout_test.cpp | 0 tests/{ => unit}/startup/video_mode_test.cpp | 0 17 files changed, 74 insertions(+), 84 deletions(-) delete mode 100644 src/nxdk/hal/debug.h delete mode 100644 src/nxdk/hal/video.h delete mode 100644 src/nxdk/hal/xbox.h delete mode 100644 src/nxdk/windows.h create mode 100644 tests/support/hal/debug.h create mode 100644 tests/support/hal/video.h rename tests/{ => unit}/splash/splash_layout_test.cpp (100%) rename tests/{ => unit}/startup/video_mode_test.cpp (100%) diff --git a/src/main.cpp b/src/main.cpp index 152731f..b9adef7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,7 @@ +// nxdk includes +#include + // local includes -#include "src/nxdk/windows.h" #include "src/splash/splash_screen.h" #include "src/startup/memory_stats.h" #include "src/startup/video_mode.h" @@ -13,7 +15,7 @@ int main() { startup::log_video_modes(videoModeSelection); startup::log_memory_statistics(); - Sleep(10000); + Sleep(4000); XVideoSetMode(bestVideoMode.width, bestVideoMode.height, bestVideoMode.bpp, bestVideoMode.refresh); diff --git a/src/nxdk/hal/debug.h b/src/nxdk/hal/debug.h deleted file mode 100644 index 19c5232..0000000 --- a/src/nxdk/hal/debug.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#if defined(NXDK) - #include -#else - #include - - #define debugPrint(...) std::printf(__VA_ARGS__) -#endif diff --git a/src/nxdk/hal/video.h b/src/nxdk/hal/video.h deleted file mode 100644 index a6a2185..0000000 --- a/src/nxdk/hal/video.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#if defined(NXDK) - #include -#else -struct VIDEO_MODE { - int width; - int height; - int bpp; - int refresh; -}; - -inline constexpr int REFRESH_DEFAULT = 0; -inline constexpr unsigned long VIDEO_WIDESCREEN = 0x00000001UL; - -inline bool XVideoListModes(VIDEO_MODE *, int, int, void **) { - return false; -} - -inline unsigned long XVideoGetEncoderSettings() { - return 0; -} - -inline void XVideoSetMode(int, int, int, int) {} - -inline void XVideoWaitForVBlank() {} -#endif diff --git a/src/nxdk/hal/xbox.h b/src/nxdk/hal/xbox.h deleted file mode 100644 index 3889667..0000000 --- a/src/nxdk/hal/xbox.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -#if defined(NXDK) - #include -#else - #include - -[[noreturn]] inline void XReboot() { - std::exit(555); -} -#endif diff --git a/src/nxdk/windows.h b/src/nxdk/windows.h deleted file mode 100644 index 3e15eb2..0000000 --- a/src/nxdk/windows.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#if defined(NXDK) - #include -#else - #include - #include - -inline void Sleep(unsigned long milliseconds) { - std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); -} -#endif diff --git a/src/os.h b/src/os.h index 5a5778d..4531ced 100644 --- a/src/os.h +++ b/src/os.h @@ -1,8 +1,4 @@ #pragma once -#if defined(NXDK) - #define PATH_SEP "\\" - #define DATA_PATH "D:" PATH_SEP -#else - #define PATH_SEP "/" - #define DATA_PATH "." PATH_SEP -#endif + +inline constexpr auto PATH_SEP = "\\"; +inline constexpr auto DATA_PATH = "D:\\"; diff --git a/src/splash/splash_layout.h b/src/splash/splash_layout.h index a49e8bf..e3aa292 100644 --- a/src/splash/splash_layout.h +++ b/src/splash/splash_layout.h @@ -1,6 +1,7 @@ #pragma once -#include "src/nxdk/hal/video.h" +// nxdk includes +#include namespace splash { @@ -8,9 +9,13 @@ namespace splash { * @brief Rectangle used for splash image placement. */ struct SplashLayout { + /** @brief Horizontal offset from the left edge of the destination surface. */ int x; + /** @brief Vertical offset from the top edge of the destination surface. */ int y; + /** @brief Width of the scaled splash logo in destination pixels. */ int width; + /** @brief Height of the scaled splash logo in destination pixels. */ int height; }; diff --git a/src/splash/splash_screen.cpp b/src/splash/splash_screen.cpp index 06bbf82..453aa37 100644 --- a/src/splash/splash_screen.cpp +++ b/src/splash/splash_screen.cpp @@ -9,13 +9,13 @@ #include // nxdk includes +#include +#include #include #include +#include // local includes -#include "src/nxdk/hal/debug.h" -#include "src/nxdk/hal/xbox.h" -#include "src/nxdk/windows.h" #include "src/os.h" #include "src/splash/splash_layout.h" @@ -45,6 +45,10 @@ namespace { SDL_Rect createCenteredRect(const SDL_Surface *screenSurface, int width, int height) { SDL_Rect destination {}; + if (screenSurface == nullptr) { + return destination; + } + destination.w = width; destination.h = height; destination.x = (screenSurface->w - destination.w) / 2; @@ -53,6 +57,10 @@ namespace { } SDL_Rect calculateLogoDestination(const SDL_Surface *screenSurface, int logoWidth, int logoHeight, const VIDEO_MODE &videoMode) { + if (screenSurface == nullptr) { + return {}; + } + const splash::SplashLayout layout = splash::calculate_logo_destination( screenSurface->w, screenSurface->h, @@ -206,8 +214,6 @@ namespace { for (const char *assetName : assetNames) { const std::string assetPath = buildAssetPath(assetName); if (SDL_Surface *loadedSurface = IMG_Load(assetPath.c_str()); loadedSurface != nullptr) { - debugPrint("Loaded splash asset: %s\n", assetPath.c_str()); - debugPrint("Loaded splash asset format: %s\n", SDL_GetPixelFormatName(loadedSurface->format->format)); if (SDL_Surface *normalizedSurface = normalizeSplashLogoSurface(loadedSurface); normalizedSurface != nullptr) { return normalizedSurface; } @@ -252,6 +258,7 @@ namespace splash { if (SDL_VideoInit(nullptr) < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL video.\n"); printSDLErrorAndReboot(); + return; } window = SDL_CreateWindow("splash", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, videoMode.width, videoMode.height, SDL_WINDOW_SHOWN); @@ -259,6 +266,7 @@ namespace splash { debugPrint("Window could not be created!\n"); SDL_VideoQuit(); printSDLErrorAndReboot(); + return; } if ((initializedImageFlags & imageInitFlags) != imageInitFlags) { @@ -269,18 +277,21 @@ namespace splash { if (!screenSurface) { cleanupSplashScreen(window, nullptr); printSDLErrorAndReboot(); + return; } imageSurface = loadSplashLogoSurface(); if (!imageSurface) { cleanupSplashScreen(window, nullptr); printIMGErrorAndReboot(); + return; } imageSurface = createScaledSplashLogoSurface(screenSurface, imageSurface, videoMode); if (!imageSurface) { cleanupSplashScreen(window, nullptr); printSDLErrorAndReboot(); + return; } SDL_Rect logoDestination = createCenteredRect(screenSurface, imageSurface->w, imageSurface->h); @@ -295,16 +306,19 @@ namespace splash { if (const Uint32 backgroundColor = SDL_MapRGB(screenSurface->format, SPLASH_BACKGROUND_RED, SPLASH_BACKGROUND_GREEN, SPLASH_BACKGROUND_BLUE); SDL_FillRect(screenSurface, nullptr, backgroundColor) < 0) { cleanupSplashScreen(window, imageSurface); printSDLErrorAndReboot(); + return; } if (SDL_BlitSurface(imageSurface, nullptr, screenSurface, &logoDestination) < 0) { cleanupSplashScreen(window, imageSurface); printSDLErrorAndReboot(); + return; } if (SDL_UpdateWindowSurface(window) < 0) { cleanupSplashScreen(window, imageSurface); printSDLErrorAndReboot(); + return; } Sleep(1000); diff --git a/src/splash/splash_screen.h b/src/splash/splash_screen.h index 5aaf810..524318a 100644 --- a/src/splash/splash_screen.h +++ b/src/splash/splash_screen.h @@ -1,7 +1,7 @@ #pragma once -// local includes -#include "src/nxdk/hal/video.h" +// nxdk includes +#include namespace splash { diff --git a/src/startup/memory_stats.cpp b/src/startup/memory_stats.cpp index 9ae5b5a..7d7390a 100644 --- a/src/startup/memory_stats.cpp +++ b/src/startup/memory_stats.cpp @@ -2,11 +2,9 @@ #include "src/startup/memory_stats.h" // nxdk includes +#include #include -// local includes -#include "src/nxdk/hal/debug.h" - namespace startup { namespace { @@ -18,8 +16,7 @@ namespace startup { MM_STATISTICS memoryStatistics {}; memoryStatistics.Length = sizeof(memoryStatistics); - const NTSTATUS status = MmQueryStatistics(&memoryStatistics); - if (!NT_SUCCESS(status)) { + if (const NTSTATUS status = MmQueryStatistics(&memoryStatistics); !NT_SUCCESS(status)) { debugPrint("Failed to query memory statistics. NTSTATUS: 0x%08lx\n", static_cast(status)); return; } diff --git a/src/startup/video_mode.cpp b/src/startup/video_mode.cpp index 347b58f..78569a2 100644 --- a/src/startup/video_mode.cpp +++ b/src/startup/video_mode.cpp @@ -1,8 +1,8 @@ // class header include #include "src/startup/video_mode.h" -// local includes -#include "src/nxdk/hal/debug.h" +// nxdk includes +#include namespace startup { diff --git a/src/startup/video_mode.h b/src/startup/video_mode.h index 87cb5cb..772ee5b 100644 --- a/src/startup/video_mode.h +++ b/src/startup/video_mode.h @@ -3,8 +3,8 @@ // standard includes #include -// local includes -#include "src/nxdk/hal/video.h" +// nxdk includes +#include namespace startup { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3540f72..3dc7639 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,9 +23,13 @@ file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/*.h ${CMAKE_SOURCE_DIR}/tests/*.cpp) -set(MOONLIGHT_TESTABLE_SOURCES - ${CMAKE_SOURCE_DIR}/src/splash/splash_layout.cpp - ${CMAKE_SOURCE_DIR}/src/startup/video_mode.cpp) +set(MOONLIGHT_TEST_EXCLUDED_SOURCES + ${CMAKE_SOURCE_DIR}/src/main.cpp + ${CMAKE_SOURCE_DIR}/src/splash/splash_screen.cpp + ${CMAKE_SOURCE_DIR}/src/startup/memory_stats.cpp) + +set(MOONLIGHT_TESTABLE_SOURCES ${MOONLIGHT_SOURCES}) +list(REMOVE_ITEM MOONLIGHT_TESTABLE_SOURCES ${MOONLIGHT_TEST_EXCLUDED_SOURCES}) add_executable(${PROJECT_NAME} ${TEST_SOURCES} @@ -34,6 +38,7 @@ add_executable(${PROJECT_NAME} set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_SOURCE_DIR}/tests/support ${CMAKE_SOURCE_DIR} ${GTEST_SOURCE_DIR}/googletest/include ${GTEST_SOURCE_DIR}/googlemock/include) diff --git a/tests/support/hal/debug.h b/tests/support/hal/debug.h new file mode 100644 index 0000000..e4d4757 --- /dev/null +++ b/tests/support/hal/debug.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +#define debugPrint(...) std::printf(__VA_ARGS__) diff --git a/tests/support/hal/video.h b/tests/support/hal/video.h new file mode 100644 index 0000000..13cc227 --- /dev/null +++ b/tests/support/hal/video.h @@ -0,0 +1,21 @@ +#pragma once + +struct VIDEO_MODE { + int width; + int height; + int bpp; + int refresh; +}; + +using VideoModeEnumerationContext = void; + +inline constexpr int REFRESH_DEFAULT = 0; +inline constexpr unsigned long VIDEO_WIDESCREEN = 0x010000UL; + +inline bool XVideoListModes(VIDEO_MODE *, int, int, VideoModeEnumerationContext **) { // NOSONAR(cpp:S5008): This host-only shim must remain source-compatible with nxdk's void** enumeration context. + return false; +} + +inline unsigned long XVideoGetEncoderSettings() { + return 0UL; +} diff --git a/tests/splash/splash_layout_test.cpp b/tests/unit/splash/splash_layout_test.cpp similarity index 100% rename from tests/splash/splash_layout_test.cpp rename to tests/unit/splash/splash_layout_test.cpp diff --git a/tests/startup/video_mode_test.cpp b/tests/unit/startup/video_mode_test.cpp similarity index 100% rename from tests/startup/video_mode_test.cpp rename to tests/unit/startup/video_mode_test.cpp