From 296672b06eaed2e5eb6ab2b6121ed8106b9fbf0d Mon Sep 17 00:00:00 2001 From: Yuri Pourre Date: Wed, 15 Jul 2026 16:08:34 -0700 Subject: [PATCH] Add cross-platform bgfx + GLFW graphics sample. Introduce docs/how/cmake_bgfx as a mixed TypeScript/C++ graphics sample using the TSLANG CMake language, GLFW, and FetchContent bgfx.cmake. Control flow matches cmake_winapp and cmake_vulkan: C++ owns the event loop (GLFW polling); TypeScript constructs AppWindow and handles onMessage for frame rendering and input. Targets Windows, Linux (X11), and macOS via GLFW native window handles. --- docs/how/cmake_bgfx/CMakeLists.txt | 101 +++++++ docs/how/cmake_bgfx/CMakePresets.json | 41 +++ docs/how/cmake_bgfx/README.md | 125 ++++++++ .../cmake/CMakeDetermineTSLANGCompiler.cmake | 23 ++ .../cmake/CMakeTSLANGCompiler.cmake.in | 6 + .../cmake/CMakeTSLANGInformation.cmake | 20 ++ .../cmake/CMakeTestTSLANGCompiler.cmake | 1 + docs/how/cmake_bgfx/cmake/LocateTSLang.cmake | 132 +++++++++ docs/how/cmake_bgfx/cmake/tslang_compile.sh | 14 + docs/how/cmake_bgfx/native/bgfx_bridge.cpp | 268 ++++++++++++++++++ docs/how/cmake_bgfx/native/main_entry.cpp | 10 + docs/how/cmake_bgfx/src/application.ts | 9 + docs/how/cmake_bgfx/src/appwindow.ts | 36 +++ docs/how/cmake_bgfx/src/bgfx_glfw.d.ts | 27 ++ docs/how/cmake_bgfx/src/main.ts | 6 + 15 files changed, 819 insertions(+) create mode 100644 docs/how/cmake_bgfx/CMakeLists.txt create mode 100644 docs/how/cmake_bgfx/CMakePresets.json create mode 100644 docs/how/cmake_bgfx/README.md create mode 100644 docs/how/cmake_bgfx/cmake/CMakeDetermineTSLANGCompiler.cmake create mode 100644 docs/how/cmake_bgfx/cmake/CMakeTSLANGCompiler.cmake.in create mode 100644 docs/how/cmake_bgfx/cmake/CMakeTSLANGInformation.cmake create mode 100644 docs/how/cmake_bgfx/cmake/CMakeTestTSLANGCompiler.cmake create mode 100644 docs/how/cmake_bgfx/cmake/LocateTSLang.cmake create mode 100755 docs/how/cmake_bgfx/cmake/tslang_compile.sh create mode 100644 docs/how/cmake_bgfx/native/bgfx_bridge.cpp create mode 100644 docs/how/cmake_bgfx/native/main_entry.cpp create mode 100644 docs/how/cmake_bgfx/src/application.ts create mode 100644 docs/how/cmake_bgfx/src/appwindow.ts create mode 100644 docs/how/cmake_bgfx/src/bgfx_glfw.d.ts create mode 100644 docs/how/cmake_bgfx/src/main.ts diff --git a/docs/how/cmake_bgfx/CMakeLists.txt b/docs/how/cmake_bgfx/CMakeLists.txt new file mode 100644 index 000000000..d69299535 --- /dev/null +++ b/docs/how/cmake_bgfx/CMakeLists.txt @@ -0,0 +1,101 @@ +cmake_minimum_required(VERSION 3.20) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +project(cmake_bgfx CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(TSLANG_ROOT "" CACHE PATH "TypeScriptCompiler __build folder (contains tslang/, llvm/, gc/)") + +include(LocateTSLang) +locate_tslang_compiler() +set(CMAKE_TSLANG_COMPILER "${CMAKE_TSLANG_COMPILER}" CACHE FILEPATH "TSLANG compiler" FORCE) + +include(FetchContent) + +set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) +set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(GLFW_BUILD_WAYLAND OFF CACHE BOOL "" FORCE) + +FetchContent_Declare( + glfw + GIT_REPOSITORY https://github.com/glfw/glfw.git + GIT_TAG 3.4 + GIT_SHALLOW TRUE) + +set(BGFX_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(BGFX_BUILD_TOOLS OFF CACHE BOOL "" FORCE) +set(BGFX_INSTALL OFF CACHE BOOL "" FORCE) + +FetchContent_Declare( + bgfx + GIT_REPOSITORY https://github.com/bkaradzic/bgfx.cmake.git + GIT_TAG v1.150.9365-558 + GIT_SHALLOW TRUE) + +FetchContent_MakeAvailable(glfw bgfx) + +enable_language(TSLANG) + +setup_tslang_link_paths() + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set(CMAKE_TSLANG_FLAGS "--opt --opt_level=3") +else() + set(CMAKE_TSLANG_FLAGS "--di --opt_level=0") +endif() + +if(WIN32) +else() + set(CMAKE_TSLANG_FLAGS "${CMAKE_TSLANG_FLAGS} -relocation-model=pic") +endif() + +add_executable(${PROJECT_NAME} + src/main.ts + src/application.ts + src/appwindow.ts + native/bgfx_bridge.cpp + native/main_entry.cpp) + +target_include_directories(${PROJECT_NAME} PRIVATE native) + +set(TSLANG_LINK_LIBS + TypeScriptDefaultLib + TypeScriptAsyncRuntime + gc + LLVMSupport) + +if(WIN32) + list(APPEND TSLANG_LINK_LIBS ntdll) +else() + find_library(TSLANG_TINFO_LIB NAMES tinfo) + list(APPEND TSLANG_LINK_LIBS LLVMDemangle stdc++ m pthread dl rt) + if(TSLANG_TINFO_LIB) + list(APPEND TSLANG_LINK_LIBS ${TSLANG_TINFO_LIB}) + else() + list(APPEND TSLANG_LINK_LIBS tinfo) + endif() +endif() + +target_link_libraries(${PROJECT_NAME} + PRIVATE + glfw + bgfx + bx + bimg + ${TSLANG_LINK_LIBS}) + +if(UNIX AND NOT APPLE) + find_package(X11 REQUIRED) + target_link_libraries(${PROJECT_NAME} PRIVATE X11::X11) +endif() + +add_custom_target(run + COMMAND "$" + DEPENDS ${PROJECT_NAME} + USES_TERMINAL + COMMENT "Running ${PROJECT_NAME}") diff --git a/docs/how/cmake_bgfx/CMakePresets.json b/docs/how/cmake_bgfx/CMakePresets.json new file mode 100644 index 000000000..119c69c86 --- /dev/null +++ b/docs/how/cmake_bgfx/CMakePresets.json @@ -0,0 +1,41 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "displayName": "Default (Ninja)", + "description": "TSLANG custom language requires the Ninja generator.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "TSLANG_ROOT": "${sourceDir}/../../../__build" + } + }, + { + "name": "debug", + "displayName": "Debug (Ninja)", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "TSLANG_ROOT": "${sourceDir}/../../../__build" + } + } + ], + "buildPresets": [ + { + "name": "default", + "configurePreset": "default" + }, + { + "name": "debug", + "configurePreset": "debug" + } + ] +} diff --git a/docs/how/cmake_bgfx/README.md b/docs/how/cmake_bgfx/README.md new file mode 100644 index 000000000..f6d2cb917 --- /dev/null +++ b/docs/how/cmake_bgfx/README.md @@ -0,0 +1,125 @@ +# bgfx + GLFW TypeScript sample + +Cross-platform graphics sample for the TypeScript native compiler (`tslang`). Application logic is written in TypeScript; GLFW windowing and bgfx rendering live in a thin C++ bridge exposed through `.d.ts` FFI declarations. + +Control flow matches [`cmake_vulkan`](../cmake_vulkan/) and [`cmake_winapp`](../cmake_winapp/): **C++ owns the event loop**, TypeScript creates the window and reacts via an `onMessage` callback. + +## What it does + +- Opens an 800x600 GLFW window (no client API — bgfx owns rendering) +- Initializes bgfx with the native window handle (Win32, X11, or Cocoa) +- C++ `run_loop()` polls GLFW and dispatches `Messages.Frame` each iteration +- TypeScript `onMessage` calls `run_bgfx_frame()` on Frame (like `run_vulkan()` on Paint) +- Renders an animated clear color and on-screen debug text: "Hello from tslang" +- Quits on Escape or window close + +## Prerequisites + +1. **Built `tslang`** — follow the main [README](../../../README.md) to build the compiler and install the default library (`tslang --install-default-lib`). +2. **CMake 3.20+** and **Ninja** (required for the custom `TSLANG` CMake language). +3. **C++20** toolchain (GCC, Clang, or MSVC). +4. **Graphics drivers** — bgfx auto-selects an available backend (OpenGL, Vulkan, DirectX on Windows). + +### Linux packages (Fedora example) + +```bash +sudo dnf install cmake ninja-build gcc-c++ ncurses-devel \ + libX11-devel libXcursor-devel libXi-devel libXrandr-devel \ + libXinerama-devel libXxf86vm-devel mesa-libGL-devel +``` + +### Windows + +- Visual Studio 2022+ with C++ workload, CMake, and Ninja. +- Ensure `tslang.exe` is on `PATH` or pass `-DTSLANG_ROOT=...` pointing at the folder that contains `bin/tslang.exe`. + +## Layout + +``` +cmake_bgfx/ +├── CMakeLists.txt # FetchContent GLFW + bgfx, mixed TS/C++ target +├── CMakePresets.json +├── cmake/ # TSLANG custom language modules +├── src/ +│ ├── main.ts # entry point (Main) +│ ├── application.ts # constructs AppWindow only +│ ├── appwindow.ts # onMessage handler (Frame / KeyDown / Close / Destroy) +│ └── bgfx_glfw.d.ts # FFI declarations +└── native/ + ├── bgfx_bridge.cpp # extern "C" GLFW + bgfx glue + run_loop() + └── main_entry.cpp # main() -> Main() -> run_loop() +``` + +## Build and run + +### Linux + +```bash +cd docs/how/cmake_bgfx + +cmake --preset default +# or explicitly: +# cmake --preset default -DTSLANG_ROOT=/path/to/TypeScriptCompiler/__build + +cmake --build --preset default +cmake --build --target run +``` + +Binary: `build/cmake_bgfx` + +If CMake reports `tslang compiler not found`, build the compiler from the repo root first: + +```bash +./prepare_3rdParty_release.sh +cd tslang && ./config_tslang_release.sh && ./build_tslang_release.sh +./bin/tslang --install-default-lib +``` + +Then re-run `cmake --preset default` from `docs/how/cmake_bgfx`. + +### Windows + +```bat +cd docs\how\cmake_bgfx + +cmake --preset default -DTSLANG_ROOT=C:\path\to\TypeScriptCompiler\__build +cmake --build --preset default +cmake --build --target run +``` + +Binary: `build\cmake_bgfx.exe` + +### macOS + +Same CMake flow as Linux. Requires Xcode command-line tools and a working OpenGL/Metal backend for bgfx. + +### Debug build + +```bash +cmake --preset debug +cmake --build --preset debug +``` + +## How it works + +1. **`main_entry.cpp`** calls TypeScript `Main()`, then C++ `run_loop()`. +2. **`Main()`** constructs `AppWindow`, which registers `onMessage` and calls `create_bgfx`. +3. **`run_loop()`** polls GLFW, dispatches `Messages.Frame` each tick, then `Messages.Destroy` on exit. +4. **CMake `TSLANG` language** compiles `.ts` sources to object files with `tslang --emit=obj`. +5. **`bgfx_bridge.cpp`** implements the flat `extern "C"` API declared in `bgfx_glfw.d.ts`. +6. **FetchContent** downloads GLFW 3.4 and [bgfx.cmake](https://github.com/bkaradzic/bgfx.cmake) on first configure. + +## Platform notes + +| Platform | Window backend | Notes | +|----------|----------------|-------| +| Windows | Win32 (`glfwGetWin32Window`) | bgfx D3D11/D3D12/OpenGL/Vulkan | +| Linux | X11 (`glfwGetX11Display` / `glfwGetX11Window`) | Wayland disabled (`GLFW_BUILD_WAYLAND=OFF`) | +| macOS | Cocoa (`glfwGetCocoaWindow`) | OpenGL/Metal via bgfx | + +## Related samples + +- [`cmake_winapp`](../cmake_winapp/) — Win32 window only (C++ message loop) +- [`cmake_vulkan`](../cmake_vulkan/) — Win32 + Vulkan cube (same onMessage pattern) +- [`cmake_tslang`](../cmake_tslang/) — mixed C++/TypeScript with custom CMake language +- [`c-cpp-header-import.md`](../../c-cpp-header-import.md) — FFI / native binding design diff --git a/docs/how/cmake_bgfx/cmake/CMakeDetermineTSLANGCompiler.cmake b/docs/how/cmake_bgfx/cmake/CMakeDetermineTSLANGCompiler.cmake new file mode 100644 index 000000000..1baf2e6d8 --- /dev/null +++ b/docs/how/cmake_bgfx/cmake/CMakeDetermineTSLANGCompiler.cmake @@ -0,0 +1,23 @@ +include(${CMAKE_CURRENT_LIST_DIR}/LocateTSLang.cmake) + +if(NOT CMAKE_TSLANG_COMPILER OR NOT EXISTS "${CMAKE_TSLANG_COMPILER}") + locate_tslang_compiler() + set(CMAKE_TSLANG_COMPILER "${CMAKE_TSLANG_COMPILER}" CACHE FILEPATH "TSLANG compiler" FORCE) +endif() + +cmake_path(GET CMAKE_TSLANG_COMPILER PARENT_PATH CMAKE_TSLANG_DIR) + +mark_as_advanced(CMAKE_TSLANG_COMPILER) +mark_as_advanced(CMAKE_TSLANG_DIR) + +set(CMAKE_TSLANG_SOURCE_FILE_EXTENSIONS ts) +if(NOT WIN32) + set(CMAKE_TSLANG_OUTPUT_EXTENSION .o) +else() + set(CMAKE_TSLANG_OUTPUT_EXTENSION .obj) +endif() +set(CMAKE_TSLANG_COMPILER_ENV_VAR "TSLANG") + +configure_file( + ${CMAKE_CURRENT_LIST_DIR}/CMakeTSLANGCompiler.cmake.in + ${CMAKE_PLATFORM_INFO_DIR}/CMakeTSLANGCompiler.cmake @ONLY) diff --git a/docs/how/cmake_bgfx/cmake/CMakeTSLANGCompiler.cmake.in b/docs/how/cmake_bgfx/cmake/CMakeTSLANGCompiler.cmake.in new file mode 100644 index 000000000..90131f6ae --- /dev/null +++ b/docs/how/cmake_bgfx/cmake/CMakeTSLANGCompiler.cmake.in @@ -0,0 +1,6 @@ +set(CMAKE_TSLANG_COMPILER "@CMAKE_TSLANG_COMPILER@") +set(CMAKE_TSLANG_DIR "@CMAKE_TSLANG_DIR@") +set(CMAKE_TSLANG_SOURCE_FILE_EXTENSIONS @CMAKE_TSLANG_SOURCE_FILE_EXTENSIONS@) +set(CMAKE_TSLANG_OUTPUT_EXTENSION @CMAKE_TSLANG_OUTPUT_EXTENSION@) +set(CMAKE_TSLANG_COMPILER_LOADED 1) +set(CMAKE_TSLANG_COMPILER_WORKS TRUE) diff --git a/docs/how/cmake_bgfx/cmake/CMakeTSLANGInformation.cmake b/docs/how/cmake_bgfx/cmake/CMakeTSLANGInformation.cmake new file mode 100644 index 000000000..d672c2268 --- /dev/null +++ b/docs/how/cmake_bgfx/cmake/CMakeTSLANGInformation.cmake @@ -0,0 +1,20 @@ +# bx propagates -msse4.2 via PUBLIC compile options on GCC/Clang. tslang does +# not accept those flags, so filter them through a small wrapper on Unix. +get_filename_component(_TSLANG_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) +set(_TSLANG_COMPILE_TEMPLATE + " --default-lib-path=${CMAKE_TSLANG_DIR} --emit=obj --export=none -o= ") +if(NOT CMAKE_TSLANG_COMPILE_OBJECT) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") + set(CMAKE_TSLANG_COMPILE_OBJECT + "${_TSLANG_CMAKE_DIR}/tslang_compile.sh ${_TSLANG_COMPILE_TEMPLATE}") + else() + set(CMAKE_TSLANG_COMPILE_OBJECT "${_TSLANG_COMPILE_TEMPLATE}") + endif() +endif() + +if(NOT CMAKE_TSLANG_LINK_EXECUTABLE) + set(CMAKE_TSLANG_LINK_EXECUTABLE + " -o ") +endif() + +set(CMAKE_TSLANG_INFORMATION_LOADED 1) diff --git a/docs/how/cmake_bgfx/cmake/CMakeTestTSLANGCompiler.cmake b/docs/how/cmake_bgfx/cmake/CMakeTestTSLANGCompiler.cmake new file mode 100644 index 000000000..ebf0d5027 --- /dev/null +++ b/docs/how/cmake_bgfx/cmake/CMakeTestTSLANGCompiler.cmake @@ -0,0 +1 @@ +set(CMAKE_TSLANG_COMPILER_WORKS TRUE) diff --git a/docs/how/cmake_bgfx/cmake/LocateTSLang.cmake b/docs/how/cmake_bgfx/cmake/LocateTSLang.cmake new file mode 100644 index 000000000..2984d8e5e --- /dev/null +++ b/docs/how/cmake_bgfx/cmake/LocateTSLang.cmake @@ -0,0 +1,132 @@ +# Locate the tslang compiler and derive install prefix paths. +# +# Cache variables: +# TSLANG_ROOT - TypeScriptCompiler __build folder (same as hello-cmake) +# CMAKE_TSLANG_COMPILER - full path to tslang (optional override) +# +# Output variables: +# CMAKE_TSLANG_COMPILER +# TSLANG_BIN_DIR - directory containing the tslang binary +# TSLANG_PREFIX - tslang build prefix (parent of bin/ and lib/) + +function(_tslang_try_prefix out_var base_dir) + if(EXISTS "${base_dir}/bin/tslang" OR EXISTS "${base_dir}/bin/tslang.exe") + list(APPEND ${out_var} "${base_dir}") + set(${out_var} "${${out_var}}" PARENT_SCOPE) + endif() +endfunction() + +function(locate_tslang_compiler) + if(CMAKE_TSLANG_COMPILER AND EXISTS "${CMAKE_TSLANG_COMPILER}") + cmake_path(GET CMAKE_TSLANG_COMPILER PARENT_PATH _bin_dir) + cmake_path(GET _bin_dir PARENT_PATH _prefix) + set(TSLANG_BIN_DIR "${_bin_dir}" PARENT_SCOPE) + set(TSLANG_PREFIX "${_prefix}" PARENT_SCOPE) + return() + endif() + + if(NOT TSLANG_ROOT) + set(_default_root "${CMAKE_SOURCE_DIR}/../../../__build") + if(EXISTS "${_default_root}") + set(TSLANG_ROOT "${_default_root}" CACHE PATH + "TypeScriptCompiler __build folder (contains tslang/, llvm/, gc/)") + else() + set(TSLANG_ROOT "" CACHE PATH + "TypeScriptCompiler __build folder (contains tslang/, llvm/, gc/)") + endif() + endif() + + set(_prefixes) + if(TSLANG_ROOT) + if(WIN32) + foreach(_preset IN ITEMS + windows-msbuild-2026-release + windows-msbuild-2022-release + windows-msbuild-release) + _tslang_try_prefix(_prefixes "${TSLANG_ROOT}/tslang/${_preset}") + endforeach() + else() + foreach(_preset IN ITEMS + linux-ninja-gcc-release + linux-ninja-clang-release + linux-ninja-gcc-debug + linux-ninja-clang-debug + ninja/release + ninja/debug) + _tslang_try_prefix(_prefixes "${TSLANG_ROOT}/tslang/${_preset}") + endforeach() + endif() + endif() + + set(_hint_bins) + foreach(_prefix IN LISTS _prefixes) + list(APPEND _hint_bins "${_prefix}/bin") + endforeach() + + find_program(_tslang_compiler + NAMES tslang tslang.exe + HINTS ${_hint_bins} + DOC "TSLANG compiler") + + if(NOT _tslang_compiler) + if(WIN32) + message(FATAL_ERROR + "tslang compiler not found.\n" + "\n" + "Build TypeScriptCompiler first (from the repo root):\n" + " prepare_3rdParty.bat\n" + " cd tslang && config_tslang_release.bat && build_tslang_release.bat\n" + " bin\\tslang.exe --install-default-lib\n" + "\n" + "Then configure this sample with:\n" + " cmake --preset default -DTSLANG_ROOT=C:/path/to/TypeScriptCompiler/__build\n" + "\n" + "Or put tslang on PATH, or pass:\n" + " -DCMAKE_TSLANG_COMPILER=C:/path/to/tslang.exe") + else() + message(FATAL_ERROR + "tslang compiler not found.\n" + "\n" + "Build TypeScriptCompiler first (from the repo root):\n" + " ./prepare_3rdParty_release.sh\n" + " cd tslang && ./config_tslang_release.sh && ./build_tslang_release.sh\n" + " ./bin/tslang --install-default-lib\n" + "\n" + "Then configure this sample with:\n" + " cmake --preset default -DTSLANG_ROOT=/path/to/TypeScriptCompiler/__build\n" + "\n" + "Or put tslang on PATH, or pass:\n" + " -DCMAKE_TSLANG_COMPILER=/path/to/tslang") + endif() + endif() + + cmake_path(GET _tslang_compiler PARENT_PATH _bin_dir) + cmake_path(GET _bin_dir PARENT_PATH _prefix) + + set(CMAKE_TSLANG_COMPILER "${_tslang_compiler}" PARENT_SCOPE) + set(TSLANG_BIN_DIR "${_bin_dir}" PARENT_SCOPE) + set(TSLANG_PREFIX "${_prefix}" PARENT_SCOPE) +endfunction() + +function(setup_tslang_link_paths) + if(NOT TSLANG_PREFIX) + message(FATAL_ERROR "setup_tslang_link_paths: TSLANG_PREFIX is not set") + endif() + + set(_link_dirs + "${TSLANG_BIN_DIR}" + "${TSLANG_PREFIX}/lib" + "${TSLANG_BIN_DIR}/defaultlib/lib") + + if(TSLANG_ROOT) + if(EXISTS "${TSLANG_ROOT}/gc/release") + list(APPEND _link_dirs "${TSLANG_ROOT}/gc/release") + endif() + if(EXISTS "${TSLANG_ROOT}/llvm/release/lib") + list(APPEND _link_dirs "${TSLANG_ROOT}/llvm/release/lib") + endif() + endif() + + include_directories("${TSLANG_BIN_DIR}/defaultlib") + link_directories(${_link_dirs}) +endfunction() diff --git a/docs/how/cmake_bgfx/cmake/tslang_compile.sh b/docs/how/cmake_bgfx/cmake/tslang_compile.sh new file mode 100755 index 000000000..a5311ee48 --- /dev/null +++ b/docs/how/cmake_bgfx/cmake/tslang_compile.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Filter C++ CPU flags that linked targets (bx/bgfx) export via PUBLIC +# compile options. tslang does not accept those flags. +set -euo pipefail + +filtered=() +for arg in "$@"; do + case "${arg}" in + -msse4.2|-msse4.1|-mavx|-mavx2) ;; + *) filtered+=("${arg}") ;; + esac +done + +exec "${filtered[@]}" diff --git a/docs/how/cmake_bgfx/native/bgfx_bridge.cpp b/docs/how/cmake_bgfx/native/bgfx_bridge.cpp new file mode 100644 index 000000000..b165ce826 --- /dev/null +++ b/docs/how/cmake_bgfx/native/bgfx_bridge.cpp @@ -0,0 +1,268 @@ +#include +#include + +#include + +#if defined(_WIN32) +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#elif defined(__linux__) +#define GLFW_EXPOSE_NATIVE_X11 +#include +#elif defined(__APPLE__) +#define GLFW_EXPOSE_NATIVE_COCOA +#include +#endif + +#include +#include +#include + +namespace { + +constexpr uint16_t kMainViewId = 0; + +constexpr uint32_t kMessageDestroy = 0x0002; +constexpr uint32_t kMessageSize = 0x0005; +constexpr uint32_t kMessageFrame = 0x000f; +constexpr uint32_t kMessageClose = 0x0010; +constexpr uint32_t kMessageKeyDown = 0x0100; + +typedef uint32_t (*MethodPtr)(void*, uint32_t, uint64_t, uint64_t); + +struct CallbackFunction { + MethodPtr method = nullptr; + void* thisVal = nullptr; +}; + +GLFWwindow* g_window = nullptr; +CallbackFunction g_callback{}; +uint32_t g_width = 0; +uint32_t g_height = 0; +uint32_t g_frameCounter = 0; +bool g_bgfxInitialized = false; +bool g_glfwInitialized = false; + +void dispatchMessage(uint32_t uMsg, uint64_t wParam, uint64_t lParam) +{ + if (g_callback.method != nullptr) { + g_callback.method(g_callback.thisVal, uMsg, wParam, lParam); + } +} + +void setPlatformData(bgfx::PlatformData& platformData, GLFWwindow* window) +{ +#if defined(_WIN32) + platformData.nwh = glfwGetWin32Window(window); +#elif defined(__linux__) + platformData.ndt = glfwGetX11Display(); + platformData.nwh = reinterpret_cast(static_cast(glfwGetX11Window(window))); +#elif defined(__APPLE__) + platformData.nwh = glfwGetCocoaWindow(window); +#else + (void)window; + platformData.ndt = nullptr; + platformData.nwh = nullptr; +#endif +} + +uint32_t mapGlfwKeyToVirtualKey(int key) +{ + switch (key) { + case GLFW_KEY_ESCAPE: + return 0x1b; + case GLFW_KEY_SPACE: + return 0x20; + default: + return static_cast(key); + } +} + +uint32_t colorChannel(uint32_t frame, uint32_t channelOffset) +{ + const float phase = static_cast((frame + channelOffset) % 256) / 255.0f; + return static_cast(phase * 255.0f); +} + +void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + (void)window; + (void)scancode; + (void)mods; + + if (action == GLFW_PRESS) { + dispatchMessage(kMessageKeyDown, mapGlfwKeyToVirtualKey(key), 0); + } +} + +void closeCallback(GLFWwindow* window) +{ + (void)window; + dispatchMessage(kMessageClose, 0, 0); +} + +void framebufferSizeCallback(GLFWwindow* window, int width, int height) +{ + (void)window; + dispatchMessage(kMessageSize, static_cast(width), static_cast(height)); +} + +} // namespace + +extern "C" { + +intptr_t create_window(const char* title, uint32_t width, uint32_t height, MethodPtr method, void* thisVal) +{ + if (!g_glfwInitialized) { + if (!glfwInit()) { + return 0; + } + g_glfwInitialized = true; + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow( + static_cast(width), + static_cast(height), + title, + nullptr, + nullptr); + + if (window == nullptr) { + return 0; + } + + g_window = window; + g_width = width; + g_height = height; + g_callback.method = method; + g_callback.thisVal = thisVal; + + glfwSetWindowUserPointer(window, &g_callback); + glfwSetKeyCallback(window, keyCallback); + glfwSetWindowCloseCallback(window, closeCallback); + glfwSetFramebufferSizeCallback(window, framebufferSizeCallback); + + return reinterpret_cast(window); +} + +void close_window(uint32_t exitCode) +{ + (void)exitCode; + if (g_window != nullptr) { + glfwSetWindowShouldClose(g_window, GLFW_TRUE); + } +} + +void destroy_window(intptr_t hwnd) +{ + GLFWwindow* window = reinterpret_cast(hwnd); + if (window != nullptr) { + glfwDestroyWindow(window); + } + + if (g_window == window) { + g_window = nullptr; + g_callback.method = nullptr; + g_callback.thisVal = nullptr; + } + + if (g_glfwInitialized) { + glfwTerminate(); + g_glfwInitialized = false; + } +} + +void create_bgfx(intptr_t hwnd, uint32_t width, uint32_t height) +{ + GLFWwindow* window = reinterpret_cast(hwnd); + if (window == nullptr) { + return; + } + + g_window = window; + g_width = width; + g_height = height; + g_frameCounter = 0; + + bgfx::PlatformData platformData{}; + setPlatformData(platformData, window); + + bgfx::Init init{}; + init.type = bgfx::RendererType::Count; + init.resolution.width = width; + init.resolution.height = height; + init.resolution.reset = BGFX_RESET_VSYNC; + init.platformData = platformData; + + if (!bgfx::init(init)) { + return; + } + + bgfx::setViewClear(kMainViewId, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x303030ff, 1.0f, 0); + bgfx::setViewRect(kMainViewId, 0, 0, static_cast(width), static_cast(height)); + g_bgfxInitialized = true; +} + +void run_bgfx_frame() +{ + if (!g_bgfxInitialized || g_window == nullptr) { + return; + } + + int framebufferWidth = 0; + int framebufferHeight = 0; + glfwGetFramebufferSize(g_window, &framebufferWidth, &framebufferHeight); + + const uint32_t width = static_cast(framebufferWidth); + const uint32_t height = static_cast(framebufferHeight); + + if (width != g_width || height != g_height) { + g_width = width; + g_height = height; + bgfx::reset(g_width, g_height, BGFX_RESET_VSYNC); + bgfx::setViewRect(kMainViewId, 0, 0, static_cast(g_width), static_cast(g_height)); + } + + const uint32_t red = colorChannel(g_frameCounter, 0); + const uint32_t green = colorChannel(g_frameCounter, 85); + const uint32_t blue = colorChannel(g_frameCounter, 170); + const uint32_t clearColor = 0xff000000u | (red << 16) | (green << 8) | blue; + + bgfx::setViewClear(kMainViewId, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, clearColor, 1.0f, 0); + bgfx::touch(kMainViewId); + + bgfx::dbgTextClear(); + bgfx::dbgTextPrintf(0, 1, 0x0f, "Hello from tslang"); + bgfx::dbgTextPrintf(0, 2, 0x0a, "bgfx + GLFW cross-platform sample"); + bgfx::dbgTextPrintf(0, 3, 0x0c, "Press Escape to quit"); + + bgfx::frame(); + ++g_frameCounter; +} + +void cleanup_bgfx() +{ + if (g_bgfxInitialized) { + bgfx::shutdown(); + g_bgfxInitialized = false; + } +} + +int run_loop() +{ + if (g_window == nullptr) { + return 1; + } + + while (!glfwWindowShouldClose(g_window)) { + glfwPollEvents(); + dispatchMessage(kMessageFrame, 0, 0); + } + + dispatchMessage(kMessageDestroy, 0, 0); + destroy_window(reinterpret_cast(g_window)); + return 0; +} + +} // extern "C" diff --git a/docs/how/cmake_bgfx/native/main_entry.cpp b/docs/how/cmake_bgfx/native/main_entry.cpp new file mode 100644 index 000000000..c25935241 --- /dev/null +++ b/docs/how/cmake_bgfx/native/main_entry.cpp @@ -0,0 +1,10 @@ +// CRT entry for all platforms: TypeScript exports Main; C++ owns the GLFW loop. + +extern "C" void Main(); +extern "C" int run_loop(); + +int main() +{ + Main(); + return run_loop(); +} diff --git a/docs/how/cmake_bgfx/src/application.ts b/docs/how/cmake_bgfx/src/application.ts new file mode 100644 index 000000000..a098af9fb --- /dev/null +++ b/docs/how/cmake_bgfx/src/application.ts @@ -0,0 +1,9 @@ +import "./appwindow"; + +export class Application { + static appWindow: AppWindow; + + export static run() { + this.appWindow = new AppWindow(); + } +} diff --git a/docs/how/cmake_bgfx/src/appwindow.ts b/docs/how/cmake_bgfx/src/appwindow.ts new file mode 100644 index 000000000..9ce01cb8b --- /dev/null +++ b/docs/how/cmake_bgfx/src/appwindow.ts @@ -0,0 +1,36 @@ +/// + +const WINDOW_WIDTH: uint32_t = 800; +const WINDOW_HEIGHT: uint32_t = 600; + +export class AppWindow { + private handler_window: intptr_t; + + export constructor() { + this.handler_window = create_window('tslang bgfx sample', WINDOW_WIDTH, WINDOW_HEIGHT, this.onMessage); + create_bgfx(this.handler_window, WINDOW_WIDTH, WINDOW_HEIGHT); + } + + protected onMessage(uMsg: uint32_t, wParam: uint64_t, lParam: uint64_t): uint32_t { + switch (uMsg) { + case Messages.Close: + close_window(0); + break; + case Messages.Frame: + run_bgfx_frame(); + break; + case Messages.Destroy: + cleanup_bgfx(); + break; + case Messages.KeyDown: + switch (wParam) { + case Keys.Escape: + close_window(0); + break; + } + return 0; + } + + return 0; + } +} diff --git a/docs/how/cmake_bgfx/src/bgfx_glfw.d.ts b/docs/how/cmake_bgfx/src/bgfx_glfw.d.ts new file mode 100644 index 000000000..3d45dc897 --- /dev/null +++ b/docs/how/cmake_bgfx/src/bgfx_glfw.d.ts @@ -0,0 +1,27 @@ +// all declarations here will be ignored + +type uint32_t = TypeOf<1>; +type uint64_t = TypeOf<4294967297>; +type intptr_t = TypeOf<4294967297>; + +type callback_function = (uMsg: uint32_t, wParam: uint64_t, lParam: uint64_t) => uint32_t; + +declare function create_window(title: string, width: uint32_t, height: uint32_t, handler: callback_function): intptr_t; +declare function close_window(exitCode: uint32_t): void; +declare function destroy_window(hwnd: intptr_t): void; +declare function create_bgfx(hwnd: intptr_t, width: uint32_t, height: uint32_t): void; +declare function run_bgfx_frame(): void; +declare function cleanup_bgfx(): void; + +enum Messages { + Destroy = 0x0002, + Size = 0x0005, + Frame = 0x000f, + Close = 0x0010, + KeyDown = 0x0100 +} + +enum Keys { + Escape = 0x1b, + Space = 0x20 +} diff --git a/docs/how/cmake_bgfx/src/main.ts b/docs/how/cmake_bgfx/src/main.ts new file mode 100644 index 000000000..99d6f2651 --- /dev/null +++ b/docs/how/cmake_bgfx/src/main.ts @@ -0,0 +1,6 @@ +import "./application"; + +export function Main() +{ + Application.run(); +}