mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[wpigui] Add wpigui wrappers for GLFW+imgui
These hide the platform specifics behind a common C++ API. Platforms: - Windows: DirectX 11 (with 10 backwards compatibility) - Linux: OpenGL 3 - Mac: Metal
This commit is contained in:
@@ -26,6 +26,7 @@ includeOtherLibs {
|
||||
^drake/
|
||||
^hal/
|
||||
^imgui
|
||||
^implot
|
||||
^mockdata/
|
||||
^networktables/
|
||||
^ntcore
|
||||
|
||||
@@ -138,6 +138,7 @@ endif()
|
||||
|
||||
if (WITH_SIMULATION_MODULES AND NOT USE_EXTERNAL_HAL)
|
||||
add_subdirectory(imgui)
|
||||
add_subdirectory(wpigui)
|
||||
add_subdirectory(simulation)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -54,6 +54,15 @@ file(GLOB imgui_sources ${imgui_srcdir}/*.cpp)
|
||||
set(implot_srcdir ${CMAKE_CURRENT_BINARY_DIR}/implot-src)
|
||||
file(GLOB implot_sources ${implot_srcdir}/*.cpp)
|
||||
add_library(imgui STATIC ${imgui_sources} ${implot_sources} ${imgui_srcdir}/examples/imgui_impl_glfw.cpp ${imgui_srcdir}/examples/imgui_impl_opengl3.cpp ${CMAKE_CURRENT_BINARY_DIR}/imgui_ProggyDotted.cpp ${CMAKE_CURRENT_BINARY_DIR}/stb_image.cpp)
|
||||
if (MSVC)
|
||||
target_sources(imgui PRIVATE ${imgui_srcdir}/examples/imgui_impl_directx11.cpp)
|
||||
else()
|
||||
if (APPLE)
|
||||
target_sources(imgui PRIVATE ${imgui_srcdir}/examples/imgui_impl_metal.mm)
|
||||
else()
|
||||
#target_sources(imgui PRIVATE ${imgui_srcdir}/examples/imgui_impl_opengl3.cpp)
|
||||
endif()
|
||||
endif()
|
||||
target_link_libraries(imgui PUBLIC gl3w glfw)
|
||||
target_include_directories(imgui PUBLIC "$<BUILD_INTERFACE:${imgui_srcdir}>" "$<BUILD_INTERFACE:${implot_srcdir}>" "$<BUILD_INTERFACE:${imgui_srcdir}/examples>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/stb-src>")
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ ExternalProject_Add(imgui
|
||||
)
|
||||
ExternalProject_Add(implot
|
||||
GIT_REPOSITORY https://github.com/epezent/implot.git
|
||||
GIT_TAG 4d4cac629b0edcda6f8d99d5f34225a7d0878509
|
||||
GIT_TAG db16011e7398e6d9ef062fbd59338ddb689e99c6
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/implot-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/implot-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
|
||||
@@ -20,6 +20,7 @@ include 'wpiutil'
|
||||
include 'ntcore'
|
||||
include 'hal'
|
||||
include 'cscore'
|
||||
include 'wpigui'
|
||||
include 'wpimath'
|
||||
include 'wpilibc'
|
||||
include 'wpilibcExamples'
|
||||
|
||||
@@ -11,7 +11,7 @@ nativeUtils {
|
||||
niLibVersion = "2020.10.1"
|
||||
opencvVersion = "3.4.7-3"
|
||||
googleTestVersion = "1.9.0-4-437e100-1"
|
||||
imguiVersion = "1.76-2"
|
||||
imguiVersion = "1.76-6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
wpigui/.styleguide
Normal file
25
wpigui/.styleguide
Normal file
@@ -0,0 +1,25 @@
|
||||
cppHeaderFileInclude {
|
||||
\.h$
|
||||
}
|
||||
|
||||
cppSrcFileInclude {
|
||||
\.cpp$
|
||||
}
|
||||
|
||||
repoRootNameOverride {
|
||||
wpigui
|
||||
}
|
||||
|
||||
includeGuardRoots {
|
||||
wpigui/src/main/native/cpp/
|
||||
wpigui/src/main/native/include/
|
||||
wpigui/src/main/native/directx11/
|
||||
wpigui/src/main/native/metal/
|
||||
wpigui/src/main/native/opengl3/
|
||||
}
|
||||
|
||||
includeOtherLibs {
|
||||
^imgui
|
||||
^implot
|
||||
^stb
|
||||
}
|
||||
44
wpigui/CMakeLists.txt
Normal file
44
wpigui/CMakeLists.txt
Normal file
@@ -0,0 +1,44 @@
|
||||
project(wpigui)
|
||||
|
||||
include(CompileWarnings)
|
||||
|
||||
file(GLOB wpigui_src src/main/native/cpp/*.cpp)
|
||||
file(GLOB wpigui_windows_src src/main/native/directx11/*.cpp)
|
||||
file(GLOB wpigui_mac_src src/main/native/metal/*.mm)
|
||||
file(GLOB wpigui_unix_src src/main/native/opengl3/*.cpp)
|
||||
|
||||
add_library(wpigui ${wpigui_src})
|
||||
set_target_properties(wpigui PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
set_property(TARGET wpigui PROPERTY FOLDER "libraries")
|
||||
|
||||
target_compile_features(wpigui PUBLIC cxx_std_17)
|
||||
wpilib_target_warnings(wpigui)
|
||||
target_link_libraries(wpigui imgui)
|
||||
|
||||
target_include_directories(wpigui PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/wpigui>)
|
||||
|
||||
if (MSVC)
|
||||
target_sources(wpigui PRIVATE ${wpigui_windows_src})
|
||||
else()
|
||||
if (APPLE)
|
||||
target_sources(wpigui PRIVATE ${wpigui_mac_src})
|
||||
else()
|
||||
target_sources(wpigui PRIVATE ${wpigui_unix_src})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(TARGETS wpigui EXPORT wpigui DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/wpigui")
|
||||
|
||||
#if (MSVC OR FLAT_INSTALL_WPILIB)
|
||||
# set (wpigui_config_dir ${wpilib_dest})
|
||||
#else()
|
||||
# set (wpigui_config_dir share/wpigui)
|
||||
#endif()
|
||||
|
||||
#configure_file(wpigui-config.cmake.in ${CMAKE_BINARY_DIR}/wpigui-config.cmake )
|
||||
#install(FILES ${CMAKE_BINARY_DIR}/wpigui-config.cmake DESTINATION ${wpigui_config_dir})
|
||||
#install(EXPORT wpigui DESTINATION ${wpigui_config_dir})
|
||||
125
wpigui/build.gradle
Normal file
125
wpigui/build.gradle
Normal file
@@ -0,0 +1,125 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxraspbian') && !project.hasProperty('onlylinuxaarch64bionic')) {
|
||||
|
||||
apply plugin: 'cpp'
|
||||
if (OperatingSystem.current().isMacOsX()) {
|
||||
apply plugin: 'objective-cpp'
|
||||
}
|
||||
apply plugin: 'visual-studio'
|
||||
apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
|
||||
ext {
|
||||
nativeName = 'wpigui'
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/config.gradle"
|
||||
|
||||
nativeUtils.exportsConfigs {
|
||||
wpigui {
|
||||
x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
|
||||
'_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
|
||||
x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
|
||||
'_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
|
||||
}
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
"${nativeName}"(NativeLibrarySpec) {
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs "src/main/native/cpp"
|
||||
include '*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
if (it.targetPlatform.operatingSystem.isWindows()) {
|
||||
it.sources {
|
||||
wpiguiWindowsCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/directx11'
|
||||
include '*.cpp'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
it.sources {
|
||||
wpiguiMacObjectiveCpp(ObjectiveCppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/metal'
|
||||
include '*.mm'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
it.sources {
|
||||
wpiguiUnixCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/opengl3'
|
||||
include '*.cpp'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
it.sources.each {
|
||||
it.exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// By default, a development executable will be generated. This is to help the case of
|
||||
// testing specific functionality of the library.
|
||||
"${nativeName}Dev"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs 'src/dev/native/cpp'
|
||||
include '**/*.cpp'
|
||||
lib library: 'wpigui'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/dev/native/include'
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries {
|
||||
all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
if (it.targetPlatform.operatingSystem.isWindows()) {
|
||||
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
|
||||
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: 'publish.gradle'
|
||||
}
|
||||
71
wpigui/publish.gradle
Normal file
71
wpigui/publish.gradle
Normal file
@@ -0,0 +1,71 @@
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
def baseArtifactId = 'wpigui-cpp'
|
||||
def artifactGroupId = 'edu.wpi.first.wpigui'
|
||||
def zipBaseName = '_GROUP_edu_wpi_first_wpigui_ID_wpigui-cpp_CLS'
|
||||
|
||||
def outputsFolder = file("$project.buildDir/outputs")
|
||||
|
||||
task cppSourcesZip(type: Zip) {
|
||||
destinationDirectory = outputsFolder
|
||||
archiveBaseName = zipBaseName
|
||||
classifier = "sources"
|
||||
|
||||
from(licenseFile) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
from('src/main/native/cpp') {
|
||||
into '/'
|
||||
}
|
||||
from('src/main/native/directx11') {
|
||||
into '/'
|
||||
}
|
||||
from('src/main/native/metal') {
|
||||
into '/'
|
||||
}
|
||||
from('src/main/native/opengl3') {
|
||||
into '/'
|
||||
}
|
||||
}
|
||||
|
||||
task cppHeadersZip(type: Zip) {
|
||||
destinationDirectory = outputsFolder
|
||||
archiveBaseName = zipBaseName
|
||||
classifier = "headers"
|
||||
|
||||
from(licenseFile) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
from('src/main/native/include') {
|
||||
into '/'
|
||||
}
|
||||
}
|
||||
|
||||
build.dependsOn cppHeadersZip
|
||||
build.dependsOn cppSourcesZip
|
||||
|
||||
addTaskToCopyAllOutputs(cppHeadersZip)
|
||||
addTaskToCopyAllOutputs(cppSourcesZip)
|
||||
|
||||
model {
|
||||
publishing {
|
||||
def wpiguiTaskList = createComponentZipTasks($.components, ['wpigui'], zipBaseName, Zip, project, includeStandardZipFormat)
|
||||
|
||||
publications {
|
||||
cpp(MavenPublication) {
|
||||
wpiguiTaskList.each {
|
||||
artifact it
|
||||
}
|
||||
|
||||
artifact cppHeadersZip
|
||||
artifact cppSourcesZip
|
||||
|
||||
artifactId = baseArtifactId
|
||||
groupId artifactGroupId
|
||||
version wpilibVersioning.version.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
wpigui/src/dev/native/cpp/main.cpp
Normal file
14
wpigui/src/dev/native/cpp/main.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "wpigui.h"
|
||||
|
||||
int main() {
|
||||
wpi::gui::CreateContext();
|
||||
wpi::gui::Initialize("Hello World", 1024, 768);
|
||||
wpi::gui::Main();
|
||||
}
|
||||
357
wpigui/src/main/native/cpp/wpigui.cpp
Normal file
357
wpigui/src/main/native/cpp/wpigui.cpp
Normal file
@@ -0,0 +1,357 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "wpigui.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_ProggyDotted.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <implot.h>
|
||||
|
||||
#include "wpigui_internal.h"
|
||||
|
||||
using namespace wpi::gui;
|
||||
|
||||
namespace wpi {
|
||||
|
||||
Context* gui::gContext;
|
||||
|
||||
static void ErrorCallback(int error, const char* description) {
|
||||
std::fprintf(stderr, "GLFW Error %d: %s\n", error, description);
|
||||
}
|
||||
|
||||
static void WindowSizeCallback(GLFWwindow* window, int width, int height) {
|
||||
if (!gContext->maximized) {
|
||||
gContext->width = width;
|
||||
gContext->height = height;
|
||||
}
|
||||
}
|
||||
|
||||
static void WindowMaximizeCallback(GLFWwindow* window, int maximized) {
|
||||
gContext->maximized = maximized;
|
||||
}
|
||||
|
||||
static void WindowPosCallback(GLFWwindow* window, int xpos, int ypos) {
|
||||
if (!gContext->maximized) {
|
||||
gContext->xPos = xpos;
|
||||
gContext->yPos = ypos;
|
||||
}
|
||||
}
|
||||
|
||||
static void* IniReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
const char* name) {
|
||||
if (std::strcmp(name, "GLOBAL") != 0) return nullptr;
|
||||
return static_cast<SavedSettings*>(gContext);
|
||||
}
|
||||
|
||||
static void IniReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
void* entry, const char* lineStr) {
|
||||
auto impl = static_cast<SavedSettings*>(entry);
|
||||
const char* value = std::strchr(lineStr, '=');
|
||||
if (!value) return;
|
||||
++value;
|
||||
int num = std::atoi(value);
|
||||
if (std::strncmp(lineStr, "width=", 6) == 0) {
|
||||
impl->width = num;
|
||||
impl->loadedWidthHeight = true;
|
||||
} else if (std::strncmp(lineStr, "height=", 7) == 0) {
|
||||
impl->height = num;
|
||||
impl->loadedWidthHeight = true;
|
||||
} else if (std::strncmp(lineStr, "maximized=", 10) == 0) {
|
||||
impl->maximized = num;
|
||||
} else if (std::strncmp(lineStr, "xpos=", 5) == 0) {
|
||||
impl->xPos = num;
|
||||
} else if (std::strncmp(lineStr, "ypos=", 5) == 0) {
|
||||
impl->yPos = num;
|
||||
} else if (std::strncmp(lineStr, "userScale=", 10) == 0) {
|
||||
impl->userScale = num;
|
||||
} else if (std::strncmp(lineStr, "style=", 6) == 0) {
|
||||
impl->style = num;
|
||||
}
|
||||
}
|
||||
|
||||
static void IniWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
ImGuiTextBuffer* out_buf) {
|
||||
if (!gContext) return;
|
||||
out_buf->appendf(
|
||||
"[MainWindow][GLOBAL]\nwidth=%d\nheight=%d\nmaximized=%d\n"
|
||||
"xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\n\n",
|
||||
gContext->width, gContext->height, gContext->maximized, gContext->xPos,
|
||||
gContext->yPos, gContext->userScale, gContext->style);
|
||||
}
|
||||
|
||||
void gui::CreateContext() {
|
||||
gContext = new Context;
|
||||
AddFont("ProggyDotted", [](ImGuiIO& io, float size, const ImFontConfig* cfg) {
|
||||
return ImGui::AddFontProggyDotted(io, size, cfg);
|
||||
});
|
||||
PlatformCreateContext();
|
||||
}
|
||||
|
||||
void gui::DestroyContext() {
|
||||
PlatformDestroyContext();
|
||||
delete gContext;
|
||||
gContext = nullptr;
|
||||
}
|
||||
|
||||
bool gui::Initialize(const char* title, int width, int height) {
|
||||
gContext->title = title;
|
||||
gContext->width = width;
|
||||
gContext->height = height;
|
||||
gContext->defaultWidth = width;
|
||||
gContext->defaultHeight = height;
|
||||
|
||||
// Setup window
|
||||
glfwSetErrorCallback(ErrorCallback);
|
||||
glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
|
||||
PlatformGlfwInitHints();
|
||||
if (!glfwInit()) return false;
|
||||
|
||||
PlatformGlfwWindowHints();
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImPlot::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// Hook ini handler to save settings
|
||||
ImGuiSettingsHandler iniHandler;
|
||||
iniHandler.TypeName = "MainWindow";
|
||||
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
|
||||
iniHandler.ReadOpenFn = IniReadOpen;
|
||||
iniHandler.ReadLineFn = IniReadLine;
|
||||
iniHandler.WriteAllFn = IniWriteAll;
|
||||
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
|
||||
|
||||
for (auto&& initialize : gContext->initializers) {
|
||||
if (initialize) initialize();
|
||||
}
|
||||
|
||||
// Load INI file
|
||||
ImGui::LoadIniSettingsFromDisk(io.IniFilename);
|
||||
|
||||
// Set initial window settings
|
||||
glfwWindowHint(GLFW_MAXIMIZED, gContext->maximized ? GLFW_TRUE : GLFW_FALSE);
|
||||
|
||||
if (gContext->width == 0 || gContext->height == 0) {
|
||||
gContext->width = gContext->defaultWidth;
|
||||
gContext->height = gContext->defaultHeight;
|
||||
gContext->loadedWidthHeight = false;
|
||||
}
|
||||
|
||||
float windowScale = 1.0;
|
||||
if (!gContext->loadedWidthHeight) {
|
||||
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
|
||||
// get the primary monitor work area to see if we have a reasonable initial
|
||||
// window size; if not, maximize, and default scaling to smaller
|
||||
if (GLFWmonitor* primary = glfwGetPrimaryMonitor()) {
|
||||
int monWidth, monHeight;
|
||||
glfwGetMonitorWorkarea(primary, nullptr, nullptr, &monWidth, &monHeight);
|
||||
if (monWidth < gContext->width || monHeight < gContext->height) {
|
||||
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
|
||||
windowScale = (std::min)(monWidth * 1.0 / gContext->width,
|
||||
monHeight * 1.0 / gContext->height);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gContext->xPos != -1 && gContext->yPos != -1)
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
|
||||
// Create window with graphics context
|
||||
gContext->window =
|
||||
glfwCreateWindow(gContext->width, gContext->height,
|
||||
gContext->title.c_str(), nullptr, nullptr);
|
||||
if (!gContext->window) return false;
|
||||
|
||||
if (!gContext->loadedWidthHeight) {
|
||||
if (windowScale == 1.0)
|
||||
glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
|
||||
// force user scale if window scale is smaller
|
||||
if (windowScale <= 0.5)
|
||||
gContext->userScale = 0;
|
||||
else if (windowScale <= 0.75)
|
||||
gContext->userScale = 1;
|
||||
if (windowScale != 1.0) {
|
||||
for (auto&& func : gContext->windowScalers) func(windowScale);
|
||||
}
|
||||
}
|
||||
|
||||
// Update window settings
|
||||
if (gContext->xPos != -1 && gContext->yPos != -1) {
|
||||
glfwSetWindowPos(gContext->window, gContext->xPos, gContext->yPos);
|
||||
glfwShowWindow(gContext->window);
|
||||
}
|
||||
|
||||
// Set window callbacks
|
||||
glfwGetWindowSize(gContext->window, &gContext->width, &gContext->height);
|
||||
glfwSetWindowSizeCallback(gContext->window, WindowSizeCallback);
|
||||
glfwSetWindowMaximizeCallback(gContext->window, WindowMaximizeCallback);
|
||||
glfwSetWindowPosCallback(gContext->window, WindowPosCallback);
|
||||
|
||||
// Setup Dear ImGui style
|
||||
SetStyle(static_cast<Style>(gContext->style));
|
||||
|
||||
// Load Fonts
|
||||
// this range is based on 13px being the "nominal" 100% size and going from
|
||||
// ~0.5x (7px) to ~2.0x (25px)
|
||||
for (auto&& makeFont : gContext->makeFonts) {
|
||||
if (makeFont.second) {
|
||||
auto& font = gContext->fonts.emplace_back();
|
||||
for (int i = 0; i < Font::kScaledLevels; ++i) {
|
||||
float size = 7.0f + i * 3.0f;
|
||||
ImFontConfig cfg;
|
||||
std::snprintf(cfg.Name, sizeof(cfg.Name), "%s-%d", makeFont.first,
|
||||
static_cast<int>(size));
|
||||
font.scaled[i] = makeFont.second(io, size, &cfg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PlatformInitRenderer()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gui::Main() {
|
||||
// Main loop
|
||||
while (!glfwWindowShouldClose(gContext->window) && !gContext->exit) {
|
||||
// Poll and handle events (inputs, window resize, etc.)
|
||||
glfwPollEvents();
|
||||
PlatformRenderFrame();
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
PlatformShutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImPlot::DestroyContext();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
glfwDestroyWindow(gContext->window);
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
void gui::CommonRenderFrame() {
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
|
||||
// Start the Dear ImGui frame
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Scale based on OS window content scaling
|
||||
float windowScale = 1.0;
|
||||
glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
|
||||
// map to closest font size: 0 = 0.5x, 1 = 0.75x, 2 = 1.0x, 3 = 1.25x,
|
||||
// 4 = 1.5x, 5 = 1.75x, 6 = 2x
|
||||
gContext->fontScale = std::clamp(
|
||||
gContext->userScale + static_cast<int>((windowScale - 1.0) * 4), 0,
|
||||
Font::kScaledLevels - 1);
|
||||
ImGui::GetIO().FontDefault = gContext->fonts[0].scaled[gContext->fontScale];
|
||||
|
||||
for (size_t i = 0; i < gContext->earlyExecutors.size(); ++i) {
|
||||
auto& execute = gContext->earlyExecutors[i];
|
||||
if (execute) execute();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < gContext->lateExecutors.size(); ++i) {
|
||||
auto& execute = gContext->lateExecutors[i];
|
||||
if (execute) execute();
|
||||
}
|
||||
|
||||
// Rendering
|
||||
ImGui::Render();
|
||||
}
|
||||
|
||||
void gui::Exit() { gContext->exit = true; }
|
||||
|
||||
void gui::AddInit(std::function<void()> initialize) {
|
||||
if (initialize) gContext->initializers.emplace_back(std::move(initialize));
|
||||
}
|
||||
|
||||
void gui::AddWindowScaler(std::function<void(float scale)> windowScaler) {
|
||||
if (windowScaler)
|
||||
gContext->windowScalers.emplace_back(std::move(windowScaler));
|
||||
}
|
||||
|
||||
void gui::AddEarlyExecute(std::function<void()> execute) {
|
||||
if (execute) gContext->earlyExecutors.emplace_back(std::move(execute));
|
||||
}
|
||||
|
||||
void gui::AddLateExecute(std::function<void()> execute) {
|
||||
if (execute) gContext->lateExecutors.emplace_back(std::move(execute));
|
||||
}
|
||||
|
||||
GLFWwindow* gui::GetSystemWindow() { return gContext->window; }
|
||||
|
||||
int gui::AddFont(
|
||||
const char* name,
|
||||
std::function<ImFont*(ImGuiIO& io, float size, const ImFontConfig* cfg)>
|
||||
makeFont) {
|
||||
if (makeFont) gContext->makeFonts.emplace_back(name, std::move(makeFont));
|
||||
return gContext->makeFonts.size() - 1;
|
||||
}
|
||||
|
||||
ImFont* gui::GetFont(int font) {
|
||||
return gContext->fonts[font].scaled[gContext->fontScale];
|
||||
}
|
||||
|
||||
void gui::SetStyle(Style style) {
|
||||
gContext->style = static_cast<int>(style);
|
||||
switch (style) {
|
||||
case kStyleClassic:
|
||||
ImGui::StyleColorsClassic();
|
||||
break;
|
||||
case kStyleDark:
|
||||
ImGui::StyleColorsDark();
|
||||
break;
|
||||
case kStyleLight:
|
||||
ImGui::StyleColorsLight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void gui::SetClearColor(ImVec4 color) { gContext->clearColor = color; }
|
||||
|
||||
void gui::EmitViewMenu() {
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
if (ImGui::BeginMenu("Style")) {
|
||||
bool selected;
|
||||
selected = gContext->style == kStyleClassic;
|
||||
if (ImGui::MenuItem("Classic", nullptr, &selected, true))
|
||||
SetStyle(kStyleClassic);
|
||||
selected = gContext->style == kStyleDark;
|
||||
if (ImGui::MenuItem("Dark", nullptr, &selected, true))
|
||||
SetStyle(kStyleDark);
|
||||
selected = gContext->style == kStyleLight;
|
||||
if (ImGui::MenuItem("Light", nullptr, &selected, true))
|
||||
SetStyle(kStyleLight);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Zoom")) {
|
||||
for (int i = 0; i < Font::kScaledLevels && (25 * (i + 2)) <= 200; ++i) {
|
||||
char label[20];
|
||||
std::snprintf(label, sizeof(label), "%d%%", 25 * (i + 2));
|
||||
bool selected = gContext->userScale == i;
|
||||
bool enabled = (gContext->fontScale - gContext->userScale + i) >= 0 &&
|
||||
(gContext->fontScale - gContext->userScale + i) <
|
||||
Font::kScaledLevels;
|
||||
if (ImGui::MenuItem(label, nullptr, &selected, enabled))
|
||||
gContext->userScale = i;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wpi
|
||||
203
wpigui/src/main/native/directx11/wpigui_directx11.cpp
Normal file
203
wpigui/src/main/native/directx11/wpigui_directx11.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <d3d11.h>
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <GLFW/glfw3native.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_dx11.h>
|
||||
#include <stb_image.h>
|
||||
|
||||
#include "wpigui.h"
|
||||
#include "wpigui_internal.h"
|
||||
|
||||
using namespace wpi::gui;
|
||||
|
||||
namespace {
|
||||
struct PlatformContext {
|
||||
ID3D11Device* pd3dDevice = nullptr;
|
||||
ID3D11DeviceContext* pd3dDeviceContext = nullptr;
|
||||
IDXGISwapChain* pSwapChain = nullptr;
|
||||
ID3D11RenderTargetView* mainRenderTargetView = nullptr;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static PlatformContext* gPlatformContext;
|
||||
|
||||
static void CreateRenderTarget() {
|
||||
ID3D11Texture2D* pBackBuffer;
|
||||
gPlatformContext->pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
|
||||
gPlatformContext->pd3dDevice->CreateRenderTargetView(
|
||||
pBackBuffer, nullptr, &gPlatformContext->mainRenderTargetView);
|
||||
pBackBuffer->Release();
|
||||
}
|
||||
|
||||
static bool CreateDeviceD3D(HWND hWnd) {
|
||||
// Setup swap chain
|
||||
DXGI_SWAP_CHAIN_DESC sd;
|
||||
ZeroMemory(&sd, sizeof(sd));
|
||||
sd.BufferCount = 2;
|
||||
sd.BufferDesc.Width = 0;
|
||||
sd.BufferDesc.Height = 0;
|
||||
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
sd.BufferDesc.RefreshRate.Numerator = 60;
|
||||
sd.BufferDesc.RefreshRate.Denominator = 1;
|
||||
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
sd.OutputWindow = hWnd;
|
||||
sd.SampleDesc.Count = 1;
|
||||
sd.SampleDesc.Quality = 0;
|
||||
sd.Windowed = TRUE;
|
||||
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
||||
|
||||
UINT createDeviceFlags = 0;
|
||||
// createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
D3D_FEATURE_LEVEL featureLevel;
|
||||
const D3D_FEATURE_LEVEL featureLevelArray[2] = {
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
D3D_FEATURE_LEVEL_10_0,
|
||||
};
|
||||
if (D3D11CreateDeviceAndSwapChain(
|
||||
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags,
|
||||
featureLevelArray, 2, D3D11_SDK_VERSION, &sd,
|
||||
&gPlatformContext->pSwapChain, &gPlatformContext->pd3dDevice,
|
||||
&featureLevel, &gPlatformContext->pd3dDeviceContext) != S_OK)
|
||||
return false;
|
||||
|
||||
CreateRenderTarget();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void CleanupRenderTarget() {
|
||||
if (gPlatformContext->mainRenderTargetView) {
|
||||
gPlatformContext->mainRenderTargetView->Release();
|
||||
gPlatformContext->mainRenderTargetView = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void CleanupDeviceD3D() {
|
||||
CleanupRenderTarget();
|
||||
if (gPlatformContext->pSwapChain) {
|
||||
gPlatformContext->pSwapChain->Release();
|
||||
gPlatformContext->pSwapChain = nullptr;
|
||||
}
|
||||
if (gPlatformContext->pd3dDeviceContext) {
|
||||
gPlatformContext->pd3dDeviceContext->Release();
|
||||
gPlatformContext->pd3dDeviceContext = nullptr;
|
||||
}
|
||||
if (gPlatformContext->pd3dDevice) {
|
||||
gPlatformContext->pd3dDevice->Release();
|
||||
gPlatformContext->pd3dDevice = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
namespace wpi {
|
||||
|
||||
void gui::PlatformCreateContext() { gPlatformContext = new PlatformContext; }
|
||||
|
||||
void gui::PlatformDestroyContext() {
|
||||
CleanupDeviceD3D();
|
||||
delete gPlatformContext;
|
||||
gPlatformContext = nullptr;
|
||||
}
|
||||
|
||||
void gui::PlatformGlfwInitHints() {}
|
||||
|
||||
void gui::PlatformGlfwWindowHints() {
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||
}
|
||||
|
||||
bool gui::PlatformInitRenderer() {
|
||||
// Initialize Direct3D
|
||||
if (!CreateDeviceD3D(glfwGetWin32Window(gContext->window))) {
|
||||
CleanupDeviceD3D();
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui_ImplGlfw_InitForOpenGL(gContext->window, true);
|
||||
ImGui_ImplDX11_Init(gPlatformContext->pd3dDevice,
|
||||
gPlatformContext->pd3dDeviceContext);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gui::PlatformRenderFrame() {
|
||||
ImGui_ImplDX11_NewFrame();
|
||||
|
||||
CommonRenderFrame();
|
||||
|
||||
gPlatformContext->pd3dDeviceContext->OMSetRenderTargets(
|
||||
1, &gPlatformContext->mainRenderTargetView, nullptr);
|
||||
gPlatformContext->pd3dDeviceContext->ClearRenderTargetView(
|
||||
gPlatformContext->mainRenderTargetView,
|
||||
reinterpret_cast<float*>(&gContext->clearColor));
|
||||
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
gPlatformContext->pSwapChain->Present(1, 0); // Present with vsync
|
||||
// gPlatformContext->pSwapChain->Present(0, 0); // Present without vsync
|
||||
}
|
||||
|
||||
void gui::PlatformShutdown() { ImGui_ImplDX11_Shutdown(); }
|
||||
|
||||
bool gui::LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
int* out_width, int* out_height) {
|
||||
// Load from disk into a raw RGBA buffer
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
|
||||
if (!data) return false;
|
||||
|
||||
// Create texture
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
ZeroMemory(&desc, sizeof(desc));
|
||||
desc.Width = width;
|
||||
desc.Height = height;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
desc.CPUAccessFlags = 0;
|
||||
|
||||
ID3D11Texture2D* pTexture = nullptr;
|
||||
D3D11_SUBRESOURCE_DATA subResource;
|
||||
subResource.pSysMem = data;
|
||||
subResource.SysMemPitch = desc.Width * 4;
|
||||
subResource.SysMemSlicePitch = 0;
|
||||
gPlatformContext->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);
|
||||
|
||||
// Create texture view
|
||||
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
|
||||
ZeroMemory(&srvDesc, sizeof(srvDesc));
|
||||
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
||||
srvDesc.Texture2D.MipLevels = desc.MipLevels;
|
||||
srvDesc.Texture2D.MostDetailedMip = 0;
|
||||
ID3D11ShaderResourceView* srv;
|
||||
gPlatformContext->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc,
|
||||
&srv);
|
||||
pTexture->Release();
|
||||
|
||||
*out_texture = srv;
|
||||
*out_width = width;
|
||||
*out_height = height;
|
||||
stbi_image_free(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gui::DeleteTexture(ImTextureID texture) {
|
||||
if (texture) static_cast<ID3D11ShaderResourceView*>(texture)->Release();
|
||||
}
|
||||
|
||||
} // namespace wpi
|
||||
151
wpigui/src/main/native/include/wpigui.h
Normal file
151
wpigui/src/main/native/include/wpigui.h
Normal file
@@ -0,0 +1,151 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
extern "C" struct GLFWwindow;
|
||||
|
||||
namespace wpi::gui {
|
||||
|
||||
/**
|
||||
* Creates GUI context. Must be called prior to calling any other functions.
|
||||
*/
|
||||
void CreateContext();
|
||||
|
||||
/**
|
||||
* Destroys GUI context.
|
||||
*/
|
||||
void DestroyContext();
|
||||
|
||||
/**
|
||||
* Initializes the GUI.
|
||||
*
|
||||
* @param title main application window title
|
||||
* @param width main application window width
|
||||
* @param height main application window height
|
||||
*/
|
||||
bool Initialize(const char* title, int width, int height);
|
||||
|
||||
/**
|
||||
* Runs main GUI loop. On some OS'es this must be called from the main thread.
|
||||
* Does not return until Exit() is called.
|
||||
*/
|
||||
void Main();
|
||||
|
||||
/**
|
||||
* Exits main GUI loop when current loop iteration finishes.
|
||||
* Safe to call from any thread, including from within main GUI loop.
|
||||
*/
|
||||
void Exit();
|
||||
|
||||
/**
|
||||
* Adds initializer to GUI. The passed function is called once, immediately
|
||||
* after the GUI (both GLFW and Dear ImGui) are initialized in Initialize().
|
||||
* To have any effect, must be called prior to Initialize().
|
||||
*
|
||||
* @param initialize initialization function
|
||||
*/
|
||||
void AddInit(std::function<void()> initialize);
|
||||
|
||||
/**
|
||||
* Adds window scaler function. The passed function is called once during
|
||||
* Initialize() if the window scale is not 1.0. To have any effect, must
|
||||
* be called prior to Initialize().
|
||||
*
|
||||
* @param windowScaler window scaler function
|
||||
*/
|
||||
void AddWindowScaler(std::function<void(float scale)> windowScaler);
|
||||
|
||||
/**
|
||||
* Adds per-frame executor to GUI. The passed function is called on each
|
||||
* Dear ImGui frame prior to any of the late execute functions.
|
||||
*
|
||||
* @param execute frame execution function
|
||||
*/
|
||||
void AddEarlyExecute(std::function<void()> execute);
|
||||
|
||||
/**
|
||||
* Adds per-frame executor to GUI. The passed function is called on each
|
||||
* Dear ImGui frame after all of the early execute functions.
|
||||
*
|
||||
* @param execute frame execution function
|
||||
*/
|
||||
void AddLateExecute(std::function<void()> execute);
|
||||
|
||||
/**
|
||||
* Gets GLFW window handle.
|
||||
*/
|
||||
GLFWwindow* GetSystemWindow();
|
||||
|
||||
/**
|
||||
* Adds a font to the GUI. The passed function is called during
|
||||
* initialization as many times as necessary to create a range of sizes.
|
||||
*
|
||||
* @param name font name
|
||||
* @param makeFont font creation / loader function
|
||||
* @return Font index for later use with GetFont()
|
||||
*/
|
||||
int AddFont(
|
||||
const char* name,
|
||||
std::function<ImFont*(ImGuiIO& io, float size, const ImFontConfig* cfg)>
|
||||
makeFont);
|
||||
|
||||
/**
|
||||
* Gets a font added with AddFont() with the appropriate font size for
|
||||
* the current scaling of the GUI.
|
||||
*
|
||||
* @param font font index returned by AddFont()
|
||||
* @return Font pointer
|
||||
*/
|
||||
ImFont* GetFont(int font);
|
||||
|
||||
enum Style { kStyleClassic = 0, kStyleDark, kStyleLight };
|
||||
|
||||
/**
|
||||
* Sets the ImGui style. Using this function makes this setting persistent.
|
||||
*
|
||||
* @param style Style
|
||||
*/
|
||||
void SetStyle(Style style);
|
||||
|
||||
/**
|
||||
* Sets the clear (background) color.
|
||||
*
|
||||
* @param color Color
|
||||
*/
|
||||
void SetClearColor(ImVec4 color);
|
||||
|
||||
/**
|
||||
* Emits a View menu (e.g. for a main menu bar) that allows setting of
|
||||
* style and zoom. Internally starts with ImGui::BeginMenu("View").
|
||||
*/
|
||||
void EmitViewMenu();
|
||||
|
||||
/**
|
||||
* Loads a texture from a file.
|
||||
*
|
||||
* @param filename filename
|
||||
* @param out_texture texture (output)
|
||||
* @param out_width image width (output)
|
||||
* @param out_height image height (output)
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
int* out_width, int* out_height);
|
||||
|
||||
/**
|
||||
* Deletes a texture.
|
||||
*
|
||||
* @param texture texture
|
||||
*/
|
||||
void DeleteTexture(ImTextureID texture);
|
||||
|
||||
} // namespace wpi::gui
|
||||
73
wpigui/src/main/native/include/wpigui_internal.h
Normal file
73
wpigui/src/main/native/include/wpigui_internal.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace wpi::gui {
|
||||
|
||||
struct SavedSettings {
|
||||
bool loadedWidthHeight = false;
|
||||
int width;
|
||||
int height;
|
||||
int maximized = 0;
|
||||
int xPos = -1;
|
||||
int yPos = -1;
|
||||
int userScale = 2;
|
||||
int style = 0;
|
||||
};
|
||||
|
||||
struct Font {
|
||||
static constexpr int kScaledLevels = 9;
|
||||
ImFont* scaled[kScaledLevels];
|
||||
};
|
||||
|
||||
struct Context : public SavedSettings {
|
||||
std::atomic_bool exit{false};
|
||||
|
||||
std::string title;
|
||||
int defaultWidth;
|
||||
int defaultHeight;
|
||||
|
||||
GLFWwindow* window = nullptr;
|
||||
|
||||
std::vector<std::function<void()>> initializers;
|
||||
std::vector<std::function<void(float scale)>> windowScalers;
|
||||
std::vector<std::pair<
|
||||
const char*,
|
||||
std::function<ImFont*(ImGuiIO& io, float size, const ImFontConfig* cfg)>>>
|
||||
makeFonts;
|
||||
|
||||
ImVec4 clearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
|
||||
std::vector<std::function<void()>> earlyExecutors;
|
||||
std::vector<std::function<void()>> lateExecutors;
|
||||
|
||||
int fontScale = 2; // updated by main loop
|
||||
std::vector<Font> fonts;
|
||||
};
|
||||
|
||||
extern Context* gContext;
|
||||
|
||||
void PlatformCreateContext();
|
||||
void PlatformDestroyContext();
|
||||
void PlatformGlfwInitHints();
|
||||
void PlatformGlfwWindowHints();
|
||||
bool PlatformInitRenderer();
|
||||
void PlatformRenderFrame();
|
||||
void PlatformShutdown();
|
||||
|
||||
void CommonRenderFrame();
|
||||
|
||||
} // namespace wpi::gui
|
||||
140
wpigui/src/main/native/metal/wpigui_metal.mm
Normal file
140
wpigui/src/main/native/metal/wpigui_metal.mm
Normal file
@@ -0,0 +1,140 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#define GLFW_EXPOSE_NATIVE_COCOA
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <GLFW/glfw3native.h>
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_metal.h>
|
||||
#include <stb_image.h>
|
||||
|
||||
#include "wpigui.h"
|
||||
#include "wpigui_internal.h"
|
||||
|
||||
using namespace wpi::gui;
|
||||
|
||||
namespace {
|
||||
struct PlatformContext {
|
||||
CAMetalLayer* layer;
|
||||
MTLRenderPassDescriptor *renderPassDescriptor;
|
||||
id <MTLCommandQueue> commandQueue;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static PlatformContext* gPlatformContext;
|
||||
|
||||
namespace wpi {
|
||||
|
||||
void gui::PlatformCreateContext() {
|
||||
gPlatformContext = new PlatformContext;
|
||||
}
|
||||
|
||||
void gui::PlatformDestroyContext() {
|
||||
delete gPlatformContext;
|
||||
gPlatformContext = nullptr;
|
||||
}
|
||||
|
||||
void gui::PlatformGlfwInitHints() {}
|
||||
|
||||
void gui::PlatformGlfwWindowHints() {
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||
}
|
||||
|
||||
bool gui::PlatformInitRenderer() {
|
||||
id <MTLDevice> device = MTLCreateSystemDefaultDevice();
|
||||
gPlatformContext->commandQueue = [device newCommandQueue];
|
||||
|
||||
ImGui_ImplGlfw_InitForOpenGL(gContext->window, true);
|
||||
ImGui_ImplMetal_Init(device);
|
||||
|
||||
NSWindow *nswin = glfwGetCocoaWindow(gContext->window);
|
||||
gPlatformContext->layer = [CAMetalLayer layer];
|
||||
gPlatformContext->layer.device = device;
|
||||
gPlatformContext->layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
nswin.contentView.layer = gPlatformContext->layer;
|
||||
nswin.contentView.wantsLayer = YES;
|
||||
|
||||
gPlatformContext->renderPassDescriptor = [MTLRenderPassDescriptor new];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gui::PlatformRenderFrame() {
|
||||
@autoreleasepool
|
||||
{
|
||||
int width, height;
|
||||
glfwGetFramebufferSize(gContext->window, &width, &height);
|
||||
gPlatformContext->layer.drawableSize = CGSizeMake(width, height);
|
||||
id<CAMetalDrawable> drawable = [gPlatformContext->layer nextDrawable];
|
||||
|
||||
id<MTLCommandBuffer> commandBuffer = [gPlatformContext->commandQueue commandBuffer];
|
||||
auto renderPassDescriptor = gPlatformContext->renderPassDescriptor;
|
||||
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(gContext->clearColor.x, gContext->clearColor.y, gContext->clearColor.z, gContext->clearColor.w);
|
||||
renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
|
||||
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||||
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
|
||||
[renderEncoder pushDebugGroup:@"WPI GUI"];
|
||||
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplMetal_NewFrame(renderPassDescriptor);
|
||||
|
||||
CommonRenderFrame();
|
||||
|
||||
ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), commandBuffer, renderEncoder);
|
||||
|
||||
[renderEncoder popDebugGroup];
|
||||
[renderEncoder endEncoding];
|
||||
|
||||
[commandBuffer presentDrawable:drawable];
|
||||
[commandBuffer commit];
|
||||
}
|
||||
}
|
||||
|
||||
void gui::PlatformShutdown() {
|
||||
ImGui_ImplMetal_Shutdown();
|
||||
}
|
||||
|
||||
bool gui::LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
int* out_width, int* out_height) {
|
||||
// Load from file
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
|
||||
if (!data) return false;
|
||||
|
||||
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:width height:height mipmapped:NO];
|
||||
textureDescriptor.usage = MTLTextureUsageShaderRead;
|
||||
#if TARGET_OS_OSX
|
||||
textureDescriptor.storageMode = MTLStorageModeManaged;
|
||||
#else
|
||||
textureDescriptor.storageMode = MTLStorageModeShared;
|
||||
#endif
|
||||
id <MTLTexture> texture = [gPlatformContext->layer.device newTextureWithDescriptor:textureDescriptor];
|
||||
[texture replaceRegion:MTLRegionMake2D(0, 0, width, height) mipmapLevel:0 withBytes:data bytesPerRow:width * 4];
|
||||
|
||||
*out_texture = (__bridge_retained void *)texture;
|
||||
*out_width = width;
|
||||
*out_height = height;
|
||||
stbi_image_free(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gui::DeleteTexture(ImTextureID texture) {
|
||||
if (!texture) return;
|
||||
id <MTLTexture> mtlTexture = (__bridge_transfer id <MTLTexture>)texture;
|
||||
(void)mtlTexture;
|
||||
}
|
||||
|
||||
} // namespace wpi
|
||||
132
wpigui/src/main/native/opengl3/wpigui_opengl3.cpp
Normal file
132
wpigui/src/main/native/opengl3/wpigui_opengl3.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include <GL/gl3w.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <stb_image.h>
|
||||
|
||||
#include "wpigui.h"
|
||||
#include "wpigui_internal.h"
|
||||
|
||||
using namespace wpi::gui;
|
||||
|
||||
namespace wpi {
|
||||
|
||||
void gui::PlatformCreateContext() {}
|
||||
|
||||
void gui::PlatformDestroyContext() {}
|
||||
|
||||
void gui::PlatformGlfwInitHints() {}
|
||||
|
||||
void gui::PlatformGlfwWindowHints() {
|
||||
// Decide GL versions
|
||||
#if __APPLE__
|
||||
// GL 3.2 + GLSL 150
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, GLFW_TRUE);
|
||||
#else
|
||||
// GL 3.0 + GLSL 130
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
// glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+
|
||||
// glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+
|
||||
#endif
|
||||
|
||||
// enable 4xMSAA
|
||||
glfwWindowHint(GLFW_SAMPLES, 4);
|
||||
}
|
||||
|
||||
bool gui::PlatformInitRenderer() {
|
||||
glfwMakeContextCurrent(gContext->window);
|
||||
|
||||
glfwSwapInterval(1); // Enable vsync
|
||||
|
||||
// Initialize OpenGL loader
|
||||
if (gl3wInit() != 0) {
|
||||
std::fprintf(stderr, "Failed to initialize OpenGL loader!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Turn on multisampling
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
|
||||
// Setup Platform/Renderer bindings
|
||||
ImGui_ImplGlfw_InitForOpenGL(gContext->window, true);
|
||||
|
||||
// Decide GLSL versions
|
||||
#if __APPLE__
|
||||
const char* glsl_version = "#version 150";
|
||||
#else
|
||||
const char* glsl_version = "#version 130";
|
||||
#endif
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gui::PlatformRenderFrame() {
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
|
||||
CommonRenderFrame();
|
||||
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(gContext->window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
glClearColor(gContext->clearColor.x, gContext->clearColor.y,
|
||||
gContext->clearColor.z, gContext->clearColor.w);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapBuffers(gContext->window);
|
||||
}
|
||||
|
||||
void gui::PlatformShutdown() { ImGui_ImplOpenGL3_Shutdown(); }
|
||||
|
||||
bool gui::LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
int* out_width, int* out_height) {
|
||||
// Load from file
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
|
||||
if (!data) return false;
|
||||
|
||||
// Create a OpenGL texture identifier
|
||||
GLuint texture;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
// Setup filtering parameters for display
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
// Upload pixels into texture
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, data);
|
||||
stbi_image_free(data);
|
||||
|
||||
*out_texture = reinterpret_cast<ImTextureID>(static_cast<uintptr_t>(texture));
|
||||
if (out_width) *out_width = width;
|
||||
if (out_height) *out_height = height;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gui::DeleteTexture(ImTextureID texture) {
|
||||
GLuint glTexture = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture));
|
||||
if (glTexture != 0) glDeleteTextures(1, &glTexture);
|
||||
}
|
||||
|
||||
} // namespace wpi
|
||||
Reference in New Issue
Block a user